How to calculate the running total of a column in SQL Server 2008 R2? - sql-server-2008-r2

I was trying to calculate the cumulative sum of a column in SQL Server 2008 R2 after sorting the column in ascending order. I cannot use rows unbounded preceding or following as it is only available for SQL Server 2012 and beyond. I used the following code :
select
sub_code,
Roll_no,
Total_marks,
sum (Total_marks) over (order by Total_marks ASC) as cumulative_Total
from table
But I get an error:
Incorrect syntax near 'order'
How to resolve this?

One option which doesn't use a window function would be to use a correlated subquery to calculate the running total:
select
t1.sub_code,
t1.Roll_no,
t1.Total_marks,
(select sum(Total_marks) from table t2
where t2.Total_marks <= t1.Total_marks) as cumulative_Total
from table t1
order by t1.Total_marks
This assumes that you really want to order the running total using the thing you are trying to sum, namely Total_marks. In general, you could use the following correlated subquery if you intend to use a different column for ordering:
(select sum(Total_marks) from table t2
where t2.some_col <= t1.some_col) as cumulative_Total

Check out Aaron Bertrand's excellent answer with multiple options including pros and cons.
https://stackoverflow.com/a/11313533/3266499

I managed to solve this issue by using a stored procedure which arranges the scores range in ascending order and displays number of student per range value after which it calculates the cumulative score .
USE [databasename]
GO
/****** Object: StoredProcedure [dbo].[sp_range] Script Date: 8/26/2019 12:30:10
PM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[sp_range]
#sub_code nvarchar(10)
AS
SET NOCOUNT ON;
;WITH data AS
(
SELECT CASE
WHEN Total_marks BETWEEN 10 and 20 THEN '10-20'
WHEN Total_marks BETWEEN 21 and 30 THEN '21-30'
WHEN Total_marks BETWEEN 31 and 40 THEN '31-40'
WHEN Total_marks BETWEEN 41 and 50 THEN '41-50'
WHEN Total_marks BETWEEN 51 and 60 THEN '51-60'
WHEN Total_marks BETWEEN 61 and 70 THEN '61-70'
WHEN Total_marks BETWEEN 71 and 80 THEN '71-80'
WHEN Total_marks BETWEEN 81 and 90 THEN '81-90'
WHEN Total_marks BETWEEN 91 and 100 THEN '91-100'
end as Score_Acheived,
No_of_Students=COUNT(1)
from tablename
where sub_code=#sub_code
GROUP BY
(
CASE
WHEN Total_marks BETWEEN 10 and 20 THEN '10-20'
WHEN Total_marks BETWEEN 21 and 30 THEN '21-30'
WHEN Total_marks BETWEEN 31 and 40 THEN '31-40'
WHEN Total_marks BETWEEN 41 and 50 THEN '41-50'
WHEN Total_marks BETWEEN 51 and 60 THEN '51-60'
WHEN Total_marks BETWEEN 61 and 70 THEN '61-70'
WHEN Total_marks BETWEEN 71 and 80 THEN '71-80'
WHEN Total_marks BETWEEN 81 and 90 THEN '81-90'
WHEN Total_marks BETWEEN 91 and 100 THEN '91-100' end
)
)
SELECT
d.Score_Acheived,
d.No_of_Students,
Accumulate_Total_Score = SUM(d2.No_of_Students)
FROM
data d
INNER JOIN
data AS d2 ON d.Score_Acheived >= d2.Score_Acheived
GROUP BY
d.Score_Acheived,
d.No_of_Students
ORDER BY
d.Score_Acheived;

Related

Trying to partition to remove rows where two columns don't match sql

How can I filter out rows within a group that do not have matching values in two columns?
I have a table A like:
CODE
US_ID
US_PRICE
NON_US_ID
NON_US_PRICE
5109
57
10
75
10
0206
85
11
58
11
0206
85
15
33
14
0206
85
41
22
70
T100
20
10
49
NULL
T100
20
38
64
38
Within each CODE group, I want to check whether US_PRICE = NON_US_PRICE and remove that row from the resulting table.
I tried:
SELECT *,
CASE WHEN US_PRICE != NON_US_PRICE OVER (PARTITION BY CODE) END
FROM A;
but I think I am missing something when I try to partition by CODE.
I want the resulting table to look like
CODE
US_ID
US_PRICE
NON_US_ID
NON_US_PRICE
0206
85
15
33
14
0206
85
41
22
70
T100
20
10
49
NULL
For provided sample, simple WHERE clause could produce such result:
SELECT *
FROM A
WHERE US_PRICE IS DISTINCT FROM NON_US_PRICE;
IS DISTINCT FROM handles NULLs comparing to != operator.

Usage of DISTINCT in reversed int pairs duplicates elimination

I have a following question:
create table memorization_word_translation
(
id serial not null
from_word_id integer not null
to_word_id integer not null
);
This table stores pairs of integers, that are often in reverse order, for example:
35 36
35 37
36 35
37 35
37 39
39 37
Question is - if I make a query, for example:
select * from memorization_word_translation
where from_word_id = 35 or to_word_id = 35
I would get
35 36
35 37
36 35 - duplicate of 35 36
37 35 - duplicate of 35 37
How is to use DISTINCT in this example to filter out all duplicates even if they are reversed?
I want to keep it only like this:
35 36
35 37
You can do it with ROW_NUMBER() window function:
select from_word_id, to_word_id
from (
select *,
row_number() over (
partition by least(from_word_id, to_word_id),
greatest(from_word_id, to_word_id)
order by (from_word_id > to_word_id)::int
) rn
from memorization_word_translation
where 35 in (from_word_id, to_word_id)
) t
where rn = 1
See the demo.
demo:db<>fiddle
You could try a it with a small sorting algorithm (here a comparison) in combination with DISTINCT ON.
The DISTINCT ON clause works an arbitrary columns or terms, e.g. on a tuple. This CASE clause sorts the two columns into tuples and removes tied (ordered) ones. The source columns can be returned in your SELECT statement:
select distinct on (
CASE
WHEN (from_word_id >= to_word_id) THEN (from_word_id, to_word_id)
ELSE (to_word_id, from_word_id)
END
)
*
from memorization_word_translation
where from_word_id = 35 or to_word_id = 35

Pivoting results from CTE in Postgres

I have a large SQL statements(PostgreSQL version 11) with many CTE's, i want to use the results from an intermediary CTE to create a PIVOTed set of results and join it with other CTE.
Below is a small part of my query and the CTE "previous_months_actual_sales" is the one i need to PIVOT.
,last_24 as
(
SELECT l_24m::DATE + (interval '1' month * generate_series(0,24)) as last_24m
FROM last_24_month_start LIMIT 24
)
,previous_months_actual_sales as
(
SELECT TO_CHAR(created_at,'YYYY-MM') as dates
,b.code,SUM(quantity) as qty
FROM base b
INNER JOIN products_sold ps ON ps.code=b.code
WHERE TO_CHAR(created_at,'YYYY-MM')
IN(SELECT TO_CHAR(last_24m,'YYYY-MM') FROM last_24)
GROUP BY b.code,TO_CHAR(created_at,'YYYY-MM')
)
SELECT * FROM previous_months_actual_sales
The results of this CTE "previous_months_actual_sales" is shown below,
dates code qty
"2018-04" "0009" 23
"2018-05" "0009" 77
"2018-06" "0008" 44
"2018-07" "0008" 1
"2018-08" "0009" 89
The expected output based on the above result is,
code. 2018-04. 2018-05. 2018-06. 2018-07. 2018-08
"0009". 23 77 89
"0008". 44 1
Is there a way to achieve this?

Adding a column to a table from the previous row in T-SQL

Given a row with a timestamp column and some value column (from a device) which are already in a table in Azure SQL database, I want to add a new column to the row from a most recent record which meets certain criteria (most recent will be defined by the timestamp column). The criteria is whether the value falls into a range (between 95 and 5). I want to do this for every row.
Here is an input table:
ts (Timestamp) value (integer)
------------------------------------
2019-09-22 00:00:00 90
2019-09-21 23:10:05 75
2019-09-21 23:09:00 85
2019-09-21 22:09:00 00
2019-09-21 14:09:00 70
Now I want to add a column to this table:
ts (Timestamp) value prev_value
---------------------------------------
2019-09-22 00:00:00 90 75
2019-09-21 23:10:05 75 85
2019-09-21 23:09:00 85 70
2019-09-21 22:09:00 00 70
2019-09-21 14:09:00 70 NULL
I have been trying different SQL statements but haven't bee successful so far.
So basically you want something like lag, but with a condition.
The easy way to do that is to use a correlated subquery.
First, create and populate sample table (Please save us this step in your future questions):
DECLARE #T AS TABLE
(
ts datetime2,
[value] int
)
INSERT INTO #T (ts, [value]) VALUES
('2019-09-22T00:00:00', 90),
('2019-09-21T23:10:05', 75),
('2019-09-21T23:09:00', 85),
('2019-09-21T22:09:00', 00),
('2019-09-21T14:09:00', 70);
The query:
SELECT ts,
value,
(
SELECT TOP 1 value
FROM #T T1
WHERE T0.ts > T1.ts
AND T1.value >= 5
AND T1.value <= 95
ORDER BY t1.ts DESC
) As prev_value
FROM #T T0
ORDER BY ts DESC
Results:
ts value prev_value
2019-09-22 00:00:00 90 75
2019-09-21 23:10:05 75 85
2019-09-21 23:09:00 85 70
2019-09-21 22:09:00 0 70
2019-09-21 14:09:00 70 NULL

Postgres: Aggregate Average for each min of data

I currently have a query that returns the datetime, and value from a table.
Select time, value from table;
2014-02-05 15:49:54.458 70
2014-02-05 15:49:55.46 70
2014-02-05 15:49:56.483 70
2014-02-05 15:49:57.487 70
2014-02-05 15:49:58.503 70
2014-02-05 15:50:00.042 70
2014-02-05 15:50:01.546 70
2014-02-05 15:50:03.056 70
2014-02-05 15:50:04.093 70
What I am looking to do is something like
select average(value) from table group by (cast time to min resolution)time;
2014-02-05 15:49 70
2014-02-05 15:50 70
This should do it:
select date_trunc('minute', time),
avg(value)
from the_table
group by date_trunc('minute', time)
order by date_trunc('minute', time);
More details in the manual: http://www.postgresql.org/docs/current/static/functions-datetime.html#FUNCTIONS-DATETIME-TRUNC
Btw: time and value are horrible names for a column