SQL - Sum values when there is null - sql

I have the following table:
RowID Column1 Column2
1 3 2
2 5 2
3 2 9
4 5 NULL
5 8 NULL
6 9 3
7 1 NULL
I need first row of Column1 to Sum every time there is a NULL value in Column2. And it would continue the logic down the rows.
So, the result should look like:
RowID Column1 Column2
1 3 2
2 5 2
3 15 9
4 5 NULL
5 8 NULL
6 10 3
7 1 NULL
Notice Row 3 summed 2+5+8 =15 and Row 6 summed 9+1 =10. So, basically the row prior to Null value in Column2 summed the values in column1 until there was no more NULL values in column2. Then it resumed in row 6 where the next value was NULL.

This will do it. I have set up the data in a table variable for demo.
declare #t table(RowID int, C1 int, C2 int)
insert #t values (1, 3, 2)
,(2, 5, 2)
,(3, 2, 9)
,(4, 5, NULL)
,(5, 8, NULL)
,(6, 9, 3)
,(7, 1, NULL)
select RowID, sum(C1), max(C2)
from (
select RowID, C1, C2 from #t
union all
select T1.RowID, T2.C1, null
from #t t1
join #t t2 on t2.RowID>t1.RowID and t2.C2 is null
and not exists(
select * from #t t3
where t3.RowID>t1.RowID and t3.c2 is not null and t3.RowID<t2.RowID
)
where T1.C2 is not null
) q group by RowID
Result:
RowID C1 C2
1 3 2
2 5 2
3 15 9
4 5 NULL
5 8 NULL
6 10 3
7 1 NULL

I got it. You need to look at the rows in reverse order, assigning the NULL values to the value before them.
The idea is to assign a group to the rows to sum. This is the number of non-NULL values following the row. With this, you can then use a window function to aggregate:
select t.*,
(case when c2 is null then c1
else sum(c1) over (partition by grp)
end) as new_c1
from (select t.*, count(c2) over (order by rowid rows between 1 following and unbounded following) as grp
from t
) t
order by rowid;
Here is a db<>fiddle.

Related

Get data as row per row

How get result as:
Get all from table_1 where ORACLE (ID = 10)
ID DAY ID2
---------------
1 1 10
2 2 10
3 3 10
4 4 10
Structure:
Create table table_1 (
id number primary key,
day_1 number,
day_2 number,
day_3 number,
day_4 number,
day_5 number
)
Insert into table_1 (id,day_1,day_2,day_3,day_4,day_5) values (1,10,null,null,null,null);
Insert into table_1 (id,day_1,day_2,day_3,day_4,day_5) values (2,20,10,20,null,null);
Insert into table_1 (id,day_1,day_2,day_3,day_4,day_5) values (3,null,null,10,null,null);
Insert into table_1 (id,day_1,day_2,day_3,day_4,day_5) values (4,null,null,null,10,null);
Insert into table_1 (id,day_1,day_2,day_3,day_4,day_5) values (5,30,null,null,null,null);
--Note
10 - ORACLE
20 - MSSQL
30 - MYSQL
Use UNPIVOT:
SELECT *
FROM table_1
UNPIVOT (
id2 FOR day IN (
day_1 AS 1,
day_2 AS 2,
day_3 AS 3,
day_4 AS 4,
day_5 AS 5
)
)
WHERE id2 = 10;
Which, for your sample data, outputs:
ID
DAY
ID2
1
1
10
2
2
10
3
3
10
4
4
10
db<>fiddle here

How to get row number for each null value?

I need to get row number for each record of null by sequence. Restart number when get a value in the row.
I have tried so far
select *
, ROW_NUMBER() over (order by id) rn
from #tbl
select *
, ROW_NUMBER() over (partition by value order by id) rn
from #tbl
declare #tbl table(id int, value int)
insert into #tbl values
(1, null), (2, null), (3, null), (4, 1),(5, null), (6, null), (7, 1), (8, null), (9, null), (10, null)
select *
, ROW_NUMBER() over (partition by value order by id) rn
from #tbl
I'm getting this:
id, value, rn
1 NULL 1
2 NULL 2
3 NULL 3
4 1 4
5 NULL 5
6 NULL 6
7 1 7
8 NULL 8
9 NULL 9
10 NULL 10
I want a result like this
id, value, rn
1 NULL 1
2 NULL 2
3 NULL 3
4 1 1
5 NULL 1
6 NULL 2
7 1 1
8 NULL 1
9 NULL 2
10 NULL 3
How can I get desired result with sql query?
This approach uses COUNT as an analytic function over the value column to generate "groups" for each block of NULL values. To see how this works, just run SELECT * FROM cte using the code below. Then, using this computed group, we use ROW_NUMBER to generate the sequences for the NULL values. We order ascending by the value, which would mean that each NULL row number sequence would always begin with 1, which is the behavior we want. For records with a non NULL value, we just pull that value across into the rn column.
WITH cte AS (
SELECT *, COUNT(value) OVER (ORDER BY id) vals
FROM #tbl
)
SELECT id, value,
CASE WHEN value IS NULL
THEN ROW_NUMBER() OVER (PARTITION BY vals ORDER BY value)
ELSE value END AS rn
FROM cte
ORDER BY id;
Demo

SQL Update doesn't work with Group by

Trying to make a table that will count the amount of questions I have and with the right WHERE clause
create table #test(
BatchNo int,
Q varchar(MAX),
number varchar(MAX),
DayNo varchar(MAX),
total int
)
INSERT INTO #test ( BatchNo, Q,number, DayNo, total ) VALUES
( 2, 'A','1', '1', NULL ),
( 2, 'A','1', '1', NULL ),
( 8, 'A','3', '1', NULL ),
( 8, 'A','3', '1', NULL ),
( 99, 'A','4', '1', NULL ),
( 200, 'A','3', '1', NULL ),
( 200, 'A','3', '1', NULL ),
( 200, 'A','3', '1', NULL )
I used this UPDATE because for some reason GROUP BY Batchno doesn't work with UPDATE
UPDATE #test set total= (select count(batchno)as total from #test where (number=1 or number=3) and DayNo=1)
select * from #test
drop table #test
I keep getting this for a result
batchno | Q | number | DayNo | total
2 A 1 1 7
2 A 1 1 7
8 A 3 1 7
8 A 3 1 7
99 A 4 1 7
200 A 3 1 7
200 A 3 1 7
200 A 3 1 7
I want to get something that looks like this when I use "SELECT * FROM #test"
batchno | Q | number | DayNo | total
2 A 1 1 2
2 A 1 1 2
8 A 3 1 2
8 A 3 1 2
99 A 4 1 null
200 A 3 1 3
200 A 3 1 3
200 A 3 1 3
I think you want:
UPDATE #test
set total = (select count(batchno)as total
from #test t2
where t2.batchno = t.batchno and (number=1 or number=3) and DayNo=1)
from #test t;
You are very close, just use a temptable keeping the counts for each BatchNo and use it. Please try this:
select BatchNo,count(*) as total
into #temp
from #test t1
where (number=1 or number=3) and DayNo=1
Group by BatchNo
UPDATE #test
set total= (select total
from #temp t
where t.BatchNo = #test.BatchNo)
I think you can use window function instead:
select t.*, count(*) over(partition by batchno) as total
from #test
In your subquery for apply the update on the set, your condition is setting all the values equal to that subquery. You may want to consider a where clause on the update OR utilizing a CTE that you can join back against the table.
;with cte as (
select count(batchno)as total --this will be sigma of all batchno, my bad...
from #test
where (number=1 or number=3) and DayNo=1
)
update #test
set total = (select total from cte)
where (number=1 or number=3) and DayNo=1

column update in sequence

I have a table that contains 3 columns as below:
col1 col2 col3
---- ---- ----
1 1 null
2 2 null
3 3 null
4 1 null
5 1 null
6 1 null
7 2 null
ETC
I need to update a third column in the same table as follows:
col1 col2 col3
---- ---- ----
1 1 1
2 2 1
3 3 1
4 1 2
5 1 3
6 1 4
7 2 4
The logic behind the update is that each time the 2nd column contains a 1 in it, the third has to increment. The first column is just a sequential integer column.
You can use the row_number analytical function to number the rows with col2 = 1 sequentially and then use a subquery to find to closest value with a lower col1 for the other rows.
So given a test table like this:
create table t (c1 int, c2 int, c3 int);
insert t (c1, c2) values
(1, 1),
(2, 2),
(3, 3),
(4, 1),
(5, 1),
(6, 1),
(7, 2);
A query like this:
;with cte as (
select t.c1, t.c2, x.c3
from t
left join (
select c1, c2, row_number() over (partition by c2 order by c1) c3
from t
where c2 = 1
) x on t.c1 = x.c1
)
update t
set t.c3 = coalesce(cte1.c3,(select max(cte2.c3) from cte cte2 where cte2.c1 < cte1.c1))
from cte cte1
where t.c1 = cte1.c1
Will give the following result:
c1 c2 c3
1 1 1
2 2 1
3 3 1
4 1 2
5 1 3
6 1 4
7 2 4
Another, possibly faster, way to do this would be:
update t1 set c3 = (select count(*) from t where c2 = 1 and c1 <= t1.c1) from t t1

MS SQL Set Group ID Without Looping

I would like create a query in MS-SQL to make a column containing an incrementing group number.
This is how I want my data to return:
Column 1 | Column 2 | Column 3
------------------------------
I | 1 | 1
O | 2 | 2
O | 2 | 3
I | 3 | 4
O | 4 | 5
O | 4 | 6
O | 4 | 7
O | 4 | 8
I | 5 | 9
O | 6 | 10
Column 1 is the I and O meaning In and Out.
Column 2 is the row Group (this should increment when Column 1 changes).
Column 3 is the Row-number.
So how can I write my query so that Column 2 increments every time Column 1 changes?
Firstly, to perform this kind of operation you need some column that can identify the order of the rows. If you have a column that determines this order, an identity column for example, it can be used to do something like this:
Runnable sample:
CREATE TABLE #Groups
(
id INT IDENTITY(1, 1) , -- added identity to provide order
Column1 VARCHAR(1)
)
INSERT INTO #Groups
( Column1 )
VALUES ( 'I' ),
( 'O' ),
( 'O' ),
( 'I' ),
( 'O' ),
( 'O' ),
( 'O' ),
( 'O' ),
( 'I' ),
( 'O' );
;
WITH cte
AS ( SELECT id ,
Column1 ,
1 AS Column2
FROM #Groups
WHERE id = 1
UNION ALL
SELECT g.id ,
g.Column1 ,
CASE WHEN g.Column1 = cte.Column1 THEN cte.Column2
ELSE cte.Column2 + 1
END AS Column2
FROM #Groups g
INNER JOIN cte ON cte.id + 1 = g.id
)
SELECT *
FROM cte
OPTION (MAXRECURSION 0) -- required to allow for more than 100 recursions
DROP TABLE #Groups
This code effectively loops through the records, comparing each row to the next and incrementing the value of Column2 if the value in Column1 changes.
If you don't have an identity column, then you might consider adding one.
Credit #AeroX:
With 30K records, the last line: OPTION (MAXRECURSION 0) is required to override the default of 100 recursions when using a Common Table Expression (CTE). Setting it to 0, means that it isn't limited.
This will work if you have sqlserver 2012+
DECLARE #t table(col1 char(1), col3 int identity(1,1))
INSERT #t values
('I'), ('O'), ('O'), ('I'), ('O'), ('O'), ('O'), ('O'), ('I'), ('O')
;WITH CTE AS
(
SELECT
case when lag(col1) over (order by col3) = col1
then 0 else 1 end increase,
col1,
col3
FROM #t
)
SELECT
col1,
sum(increase) over (order by col3) col2,
col3
FROM CTE
Result:
col1 col2 col3
I 1 1
O 2 2
O 2 3
I 3 4
O 4 5
O 4 6
O 4 7
O 4 8
I 5 9
O 6 10