SQL Server 2008 Cumulative Sum that resets value - sql

I want to have the last column cumulative based on ROW_ID that resets every time it starts again with '1'.
Initially my table doesn't have the ROW_ID, this was created using partition so at least I can segregate my records.
It should add the Amt + CumulativeSum (except for the first record) all the way down and reset every time the Row_ID = 1.
I have tried several queries but it doesn't give me the desired result. I am trying to read answers from several forums but to no avail.
Can someone advise the best approach to do this?
For the sake of representation, I made the sample table as straightforward as possible.
ID ROW-ID Amt RunningTotal(Amt)
1 1 2 2
2 2 4 6
3 3 6 12
4 1 2 2
5 2 4 6
6 3 6 12
7 4 8 20
8 5 10 30
9 1 2 2
10 2 4 6
11 3 6 12
12 4 8 20

try this
declare #tb table(ID int, [ROW-ID] int, Amt money)
insert into #tb(ID, [ROW-ID], Amt) values
(1,1,2),
(2,2,4),
(3,3,6),
(4,1,2),
(5,2,4),
(7,4,8),
(8,5,10),
(9,1,2),
(10,2,4),
(11,3,6),
(12,4,8)
select *,sum(amt) over(partition by ([id]-[row-id]) order by id,[row-id]) AS cum from #tb
other version
select *,(select sum(amt) from #tb t where
(t.id-t.[row-id])=(t1.id-t1.[ROW-ID]) and (t.id<=t1.id) ) as cum
from #tb t1 order by t1.id,t1.[row-id]

Try this
SELECT distinct (T1.ID),
T1.ROW_ID,
T1.Amt,
CumulativeSum =
CASE
WHEN T1.RoW_ID=1 THEN T1.Amt
ELSE T1.Amt+ T2.Amt
END
FROM TestSum T1, TestSum T2
WHERE T1.ID = T2.ID+1
http://sqlfiddle.com/#!6/8b2a2/2

The idea is to create partitions from R column. First leave 1 if R = 1, else put 0. Then cumulative sum on that column. When you have partitions you can finally calculate cumulative sums on S column in those partitions:
--- --- ---
| 1 | | 1 | | 1 |
| 2 | | 0 | | 1 | --prev 1 + 0
| 3 | | 0 | | 1 | --prev 1 + 0
| 1 | | 1 | | 2 | --prev 1 + 1
| 2 | => | 0 | => | 2 | --prev 2 + 0
| 3 | | 0 | | 2 | --prev 2 + 0
| 4 | | 0 | | 2 | --prev 2 + 0
| 5 | | 0 | | 2 | --prev 2 + 0
| 1 | | 1 | | 3 | --prev 2 + 1
| 2 | | 0 | | 3 | --prev 3 + 0
--- --- ---
DECLARE #t TABLE ( ID INT, R INT, S INT )
INSERT INTO #t
VALUES ( 1, 1, 2 ),
( 2, 2, 4 ),
( 3, 3, 6 ),
( 4, 1, 2 ),
( 5, 2, 4 ),
( 6, 3, 6 ),
( 7, 4, 8 ),
( 8, 5, 10 ),
( 9, 1, 2 ),
( 10, 2, 4 ),
( 11, 3, 6 ),
( 12, 4, 8 );
For MSSQL 2008:
WITH cte1
AS ( SELECT ID ,
CASE WHEN R = 1 THEN 1
ELSE 0
END AS R ,
S
FROM #t
),
cte2
AS ( SELECT ID ,
( SELECT SUM(R)
FROM cte1 ci
WHERE ci.ID <= co.ID
) AS R ,
S
FROM cte1 co
)
SELECT * ,
( SELECT SUM(S)
FROM cte2 ci
WHERE ci.R = co.R
AND ci.ID <= co.ID
)
FROM cte2 co
For MSSQL 2012:
WITH cte
AS ( SELECT ID ,
SUM(CASE WHEN R = 1 THEN 1
ELSE 0
END) OVER ( ORDER BY ID ) AS R ,
S
FROM #t
)
SELECT * ,
SUM(s) OVER ( PARTITION BY R ORDER BY ID ) AS T
FROM cte
Output:
ID R S T
1 1 2 2
2 1 4 6
3 1 6 12
4 2 2 2
5 2 4 6
6 2 6 12
7 2 8 20
8 2 10 30
9 3 2 2
10 3 4 6
11 3 6 12
12 3 8 20
EDIT:
One more way. This looks way better by execution plan then first example:
SELECT * ,
CASE WHEN R = 1 THEN S
ELSE ( SELECT SUM(S)
FROM #t it
WHERE it.ID <= ot.ID
AND it.ID >= ( SELECT MAX(ID)
FROM #t iit
WHERE iit.ID < ot.ID
AND iit.R = 1
)
)
END
FROM #t ot

Related

SQL: subset data: select id when time_id for id satisfy a condition from another column

I have a data (dt) in SQL like the following:
ID time_id act rd
11 1 1 1
11 2 4 1
11 3 7 0
12 1 8 1
12 2 2 0
12 3 4 1
12 4 3 1
12 5 4 1
13 1 4 1
13 2 1 0
15 1 3 1
16 1 8 0
16 2 8 0
16 3 8 0
16 4 8 0
16 5 8 0
and I want to take the subset of this data such that only ids (and their corresponding time_id, act, rd) that has time_id == 5 is retained. The desired output is the following
ID time_id act rd
12 1 8 1
12 2 2 0
12 3 4 1
12 4 3 1
12 5 4 1
16 1 8 0
16 2 8 0
16 3 8 0
16 4 8 0
16 5 8 0
I know I should use having clause somehow but have not been successful so far (returns me empty outputs). below is my attempt:
SELECT * FROM dt
GROUP BY ID
Having min(time_id) == 5;
This query:
select id from tablename where time_id = 5
returns all the ids that you want in the results.
Use it with the operator IN:
select *
from tablename
where id in (select id from tablename where time_id = 5)
You can use a correlated subquery with exists:
select t.*
from t
where exists (select 1 from t t2 where t2.id = t.id and t2.time_id = 5);
WITH temp AS
(
SELECT id FROM tab WHERE time_id = 5
)
SELECT * FROM tab t join temp tp on(t.id=tp.id);
check this query
select * from table t1 join (select distinct ID from table t where time_id = 5) t2 on t1.id =t2.id;

SQL Order By On two columns but same prority

I'm stuck on this simple select and don't know what to do.
I Have this:
ID | Group
===========
1 | NULL
2 | 100
3 | 100
4 | 100
5 | 200
6 | 200
7 | 100
8 | NULL
and want this:
ID | Group
===========
1 | NULL
2 | 100
3 | 100
4 | 100
7 | 100
5 | 200
6 | 200
8 | NULL
all group members keep together, but others order by ID.
I can not write this script because of that NULL records. NULL means that there is not any group for this record.
First you want to order your rows by the minimum ID of their group - or their own ID in case they belong to no group.Then you want to order by ID. That is:
order by min(id) over (partition by case when grp is null then id else grp end), id
If IDs and groups can overlap (i.e. the same number can be used for an ID and for a group, e.g. add a record for ID 9 / group 1 to your sample data) you should change the partition clause to something like
order by min(id) over (partition by case when grp is null
then 'ID' + cast(id as varchar)
else 'GRP' + cast(grp as varchar) end),
id;
Rextester demo: http://rextester.com/GPHBW5600
What about data after a null? In a comment you said don't sort the null.
declare #T table (ID int primary key, grp int);
insert into #T values
(1, NULL)
, (3, 100)
, (5, 200)
, (6, 200)
, (7, 100)
, (8, NULL)
, (9, 200)
, (10, 100)
, (11, NULL)
, (12, 150);
select ttt.*
from ( select tt.*
, sum(ff) over (order by tt.ID) as sGrp
from ( select t.*
, iif(grp is null or lag(grp) over (order by id) is null, 1, 0) as ff
from #T t
) tt
) ttt
order by ttt.sGrp, ttt.grp, ttt.id
ID grp ff sGrp
----------- ----------- ----------- -----------
1 NULL 1 1
3 100 1 2
7 100 0 2
5 200 0 2
6 200 0 2
8 NULL 1 3
10 100 0 4
9 200 1 4
11 NULL 1 5
12 150 1 6

SQL Server: Increment row value depending on previous row

I have a table with the columns id and value. I'd like to create a column that groups the id. If a row's current value equals 0 then a new group in ideal_group will be created.
Table:
id | value | ideal_group
1 1 1
2 1 1
3 1 1
4 0 2
5 1 2
6 0 3
7 0 4
I'm thinking the solution should be something like:
SET #n = 1;
SELECT id,
CASE
WHEN value = 0 THEN #n = #n + 1
ELSE #n END AS ideal_group
But I'd prefer not to use an counter variable. Is there another way to go about this?
Try the below code, I assumed, that values in value column are only 1s and 0s:
select id,
value,
sum(1 - value) over (order by id rows between unbounded preceding and current row) + 1 [ideal_group]
from MY_TABLE
More general solution (without mentioned assumption):
select id,
value,
sum(case value when 0 then 1 else 0 end) over (order by id rows between unbounded preceding and current row) + 1 [ideal_group]
from MY_TABLE
create table tbl (id int, value int);
insert into tbl values
(1, 1),
(2, 1),
(3, 1),
(4, 0),
(5, 1),
(6, 0),
(7, 0);
GO
7 rows affected
select id,
value,
1 + sum(iif(value = 0, 1, 0)) over
(order by id rows between unbounded preceding and current row) as ideal_group
from tbl
GO
id | value | ideal_group
-: | ----: | ----------:
1 | 1 | 1
2 | 1 | 1
3 | 1 | 1
4 | 0 | 2
5 | 1 | 2
6 | 0 | 3
7 | 0 | 4
dbfiddle here
If you reversed the 1 and 0 and it was only 1 or 0 this would be easier.
declare #T table (id int primary key, val int);
insert into #T values
(1, 1)
, (2, 1)
, (3, 1)
, (4, 0)
, (5, 1)
, (6, 0)
, (7, 0);
select t.id, t.val
, case when t.val = 0 then 1 else 0 end as trig
, sum(case when t.val = 0 then 1 else 0 end) over (order by t.id) + 1 as grp
from #T t
order by t.id;
id val trig grp
----------- ----------- ----------- -----------
1 1 0 1
2 1 0 1
3 1 0 1
4 0 1 2
5 1 0 2
6 0 1 3
7 0 1 4

how to reset the column value

i have table with 9 records that 2 records with _id = 1 was deleted:
id | name | index | _id
1 a 1 1
8 b 3 1
9 c 7 1
10 d 4 1
15 e 2 1
16 d 1 2
17 e 2 2
and I want to reset the index of _id = 1 like this:
id | name | index | _id
1 a 1 1
8 b 2 1
9 c 3 1
10 d 4 1
15 e 5 1
16 d 1 2
17 e 2 2
i 'd use this query
declare #_idCount int = (select count(*) from tbl where _id = 1),
#index int = 1
while(#_idCount > 0)
begin
update tbl
set code = #index
where _id = 1
set #index = #index + 1
set #picCount = #picCount - 1
end
but this code is set all of record with same code.
what can i do to solve it?
You can simply use ROW_NUMBER:
WITH CTE AS
(
SELECT id,
name,
[index],
[_id],
new_index = ROW_NUMBER() OVER(PARTITION BY [_id]
ORDER BY [id])
FROM dbo.tbl
)
UPDATE CTE
SET [index] = new_index;

SQL recursive hierarchy

I am struggling to get one recursive CTE to work as desired but still with no chance..
So, I have the following similar table structures:
tblMapping:
map_id | type_id | name | parent_id
1 1 A1 0
2 1 A2 0
3 1 A3 1
4 1 A4 3
5 2 B1 0
6 2 B2 5
7 2 B3 6
8 1 A5 4
9 2 B4 0
tblRoleGroup:
role_group_id | type_id | map_id | desc_id
1 1 0 null
1 2 0 null
2 1 3 1
2 2 6 0
3 1 8 1
3 2 9 1
In tblRoleGroup, the desc_id field means:
null - allow all (used only in combination with map_id=0)
0 - allow all from parent including parent
1 - allow only current node
Still in tblRoleGroup if map_id=0 then the query should get all elements from same type_id
The query result should look like this:
role_group_id | type_id | map_id | path
1 1 1 A1
1 1 2 A2
1 1 3 A1.A3
1 1 4 A1.A3.A4
1 1 8 A1.A3.A4.A5
1 2 5 B1
1 2 6 B1.B2
1 2 7 B1.B2.B3
1 2 9 B4
2 1 3 A1.A3
2 2 6 B1.B2
2 2 7 B1.B2.B3
3 1 8 A1.A3.A4.A5
3 2 9 B4
The query below solves only a part of the expected result, but I wasn't able to make it work as the expected result..
WITH Hierarchy(map_id, type_id, name, Path) AS
(
SELECT t.map_id, t.type_id, t.name, CAST(t.name AS varchar(MAX)) AS Expr1
FROM dbo.tblMapping AS t
LEFT JOIN dbo.tblMapping AS t1 ON t1.map_id = t.parent_id
WHERE (t1.parent_id=0)
UNION ALL
SELECT t.map_id, t.type_id, t.name, CAST(h.Path + '.' + t.name AS varchar(MAX)) AS Expr1
FROM Hierarchy AS h
JOIN dbo.tblMapping AS t ON t.parent_id = h.map_id
)
SELECT h.map_id, h.type_id, t.role_group_id, h.Path AS Path
FROM Hierarchy AS h
LEFT JOIN dbo.tblRoleGroup t ON t.map_id = h.map_id
Could someone help me on this?
Thank you
At first I create a function that brings all descendants of passed map_id:
CREATE FUNCTION mapping (#map_id int)
RETURNS TABLE
AS
RETURN
(
WITH rec AS (
SELECT map_id,
[type_id],
CAST(name as nvarchar(max)) as name,
parent_id
FROM tblMapping
WHERE map_id = #map_id
UNION ALL
SELECT m.map_id,
m.[type_id],
r.name+'.'+m.name,
m.parent_id
FROM rec r
INNER JOIN tblMapping m
ON m.parent_id = r.map_id
)
SELECT *
FROM rec
);
GO
Then run this:
;WITH rec AS (
SELECT map_id,
[type_id],
CAST(name as nvarchar(max)) as name,
parent_id
FROM tblMapping
WHERE parent_id=0
UNION ALL
SELECT m.map_id,
m.[type_id],
r.name+'.'+m.name,
m.parent_id
FROM rec r
INNER JOIN tblMapping m
ON m.parent_id = r.map_id
)
SELECT t.role_group_id,
r.[type_id],
r.map_id,
r.name as [path]
FROM tblRoleGroup t
CROSS JOIN rec r
WHERE r.[type_id] = CASE WHEN t.desc_id IS NULL AND t.map_id = 0 THEN t.[type_id] ELSE NULL END
OR r.map_id = CASE WHEN t.desc_id = 1 THEN t.map_id ELSE NULL END
OR r.map_id IN (
SELECT map_id
FROM dbo.mapping (CASE WHEN t.desc_id = 0 THEN t.map_id ELSE NULL END)
)
ORDER BY role_group_id, r.[type_id], r.map_id
Will give you:
role_group_id type_id map_id path
1 1 1 A1
1 1 2 A2
1 1 3 A1.A3
1 1 4 A1.A3.A4
1 1 8 A1.A3.A4.A5
1 2 5 B1
1 2 6 B1.B2
1 2 7 B1.B2.B3
1 2 9 B4
2 1 3 A1.A3
2 2 6 B1.B2
2 2 7 B1.B2.B3
3 1 8 A1.A3.A4.A5
3 2 9 B4