How to get row number for each null value? - sql

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

Related

GROUP by Largest String for all the substrings

I have a table like this where some rows have the same grp but different names. I want to group them by name such that all the substrings after removing nonalphanumeric characters are aggregated together and grouped by the largest string. The null value is considered the substring of all the strings.
grp
name
value
1
ab&c
10
1
abc d e
56
1
ab
21
1
a
23
1
xy
34
1
[null]
1
2
fgh
87
Desired result
grp
name
value
1
abcde
111
1
xy
34
2
fgh
87
My query-
Select grp,
regexp_replace(name,'[^a-zA-Z0-9]+', '', 'g') name, sum(value) value
from table
group by grp,
regexp_replace(name,'[^a-zA-Z0-9]+', '', 'g');
Result
grp
name
value
1
abc
10
1
abcde
56
1
ab
21
1
a
23
1
xy
34
1
[null]
1
2
fgh
87
What changes should I make in my query?
To solve this problem, I did the following (all of the code below is available on the fiddle here).
CREATE TABLE test
(
grp SMALLINT NOT NULL,
name TEXT NULL,
value SMALLINT NOT NULL
);
and populate it using your data + extra for testing:
INSERT INTO test VALUES
(1, 'ab&c', 10),
(1, 'abc d e', 56),
(1, 'ab', 21),
(1, 'a', 23),
(1, NULL, 1000000),
(1, 'r*&%$s', 100), -- added for testing.
(1, 'rs__t', 101),
(1, 'rs__tu', 101),
(1, 'xy', 1111),
(1, NULL, 1000000),
(2, 'fgh', 87),
(2, 'fgh', 13), -- For Charlieface
(2, NULL, 1000000),
(2, 'x', 50),
(2, 'x', 150),
(2, 'x----y', 100);
Then, you can use this query:
WITH t1 AS
(
SELECT
grp, n_str,
LAG(n_str) OVER (PARTITION BY grp ORDER BY grp, n_str),
CASE
WHEN
LAG(n_str) OVER (PARTITION BY grp ORDER BY grp, n_str) IS NULL
OR
POSITION
(
LAG(n_str) OVER (PARTITION BY grp ORDER BY grp, n_str)
IN
n_str
) = 0
THEN 1
ELSE 0
END AS change,
value
FROM
test t1
CROSS JOIN LATERAL
(
VALUES
(
REGEXP_REPLACE(name,'[^a-zA-Z0-9]+', '', 'g')
)
) AS v(n_str)
WHERE n_str IS NOT NULL
), t2 AS
(
SELECT
ROW_NUMBER() OVER (PARTITION BY grp, s_change ORDER BY grp, n_str DESC) AS rn,
grp, n_str,
SUM(value) OVER (PARTITION BY grp, s_change) AS s_val,
MAX(LENGTH(n_str)) OVER (PARTITION BY grp) AS max_nom
FROM
(
SELECT
grp, n_str, change,
SUM(change) OVER (ORDER BY grp, n_str) AS s_change,
value
FROM
t1
ORDER BY grp, n_str DESC
) AS sub1
), t3 AS
(
SELECT
grp, SUM(value) AS null_sum
FROM
test
WHERE name IS NULL
GROUP BY grp
)
SELECT x.grp, x.n_str, x.s_val + y.null_sum
FROM t2 x
JOIN t3 y
ON x.max_nom = LENGTH(x.n_str) AND x.grp = y.grp
UNION
SELECT grp, n_str, s_val
FROM
t2 WHERE max_nom != LENGTH(n_str) AND rn = 1
ORDER BY grp, n_str;
Result:
grp n_str ?column?
1 abcde 2000110
1 rstu 302
1 xy 1111
2 fgh 1000100
2 xy 300
A few points to note:
Please always provide a fiddle when you ask questions such as this one with tables and data - it provides a single source of truth for the question and eliminates duplication of effort on the part of those trying to help you!
You haven't been very clear about what, exactly, should happen with NULLs - do the values count towards the SUM()? You can vary the CASE statement as required.
What happens when there's a tie in the number of characters in the string? I've included an example in the fiddle, where you get the draws - but you may wish to sort alphabetically (or some other method)?
There appears to be an error in your provided sums for the values (even taking account of counting or not values for NULL for the name field).
Finally, you don't want to GROUP BY the largest string - you want to GROUP BY the grp fields + the SUM() of the values in the the given grp records and then pick out the longest alphanumeric string in that grouping. It would be interesting to know why you want to do this?

SQL - Sum values when there is null

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.

Subtract previous row value to current row

I have the following table:
id value acc_no
-----------------
1 12 1
2 14 1
3 15 1
4 10 2
5 16 2
6 19 1
7 7 3
8 24 2
Expected output
id value acc_no result
------------------------------
1 12 1 12(current row values of acc_no=1)
2 14 1 2(14 (current row values)-12(previous row value of acc_no=1))
3 15 1 1(15-14)
4 10 2 10(current row values of acc_no=2)
5 16 2 6(16 (current row values)-12(previous row value of acc_no=2))
6 19 1 4(19(current row values)-15(previous row value of acc_no=1))
7 7 3 7(current row values of acc_no=3)
8 24 2 8(24(current row values)-16(previous row value of acc_no=2))
I tried this query:
select
id, value,
acc_no,
(value - (select value from tb_acc t1 where t1.id = t.id - 1)) as result
from
tb_acc t
But I didn't get the proper output as expected
DECLARE #Test TABLE (
id int,
value int,
acc_no int
)
INSERT #Test(id, value, acc_no)
VALUES
(1, 12, 1),
(2, 14, 1),
(3, 15, 1),
(4, 10, 2),
(5, 16, 2),
(6, 19, 1),
(7, 7, 3),
(8, 24, 2)
SELECT id, t.value, acc_no, t.value - ISNULL(v.value, 0) AS result
FROM #Test t
OUTER APPLY (
SELECT TOP (1) value
FROM #Test
WHERE id < t.id
AND acc_no = t.acc_no
ORDER by id DESC
) v
You can do like
Option one: Using LAG() function (I just notice you are using 2008 but I post it for other readers as well)
SELECT *,
Value - LAG(Value, 1, 0) OVER(PARTITION BY acc_no ORDER BY ID) Result
FROM T
ORDER BY ID;
Option two: Using a CTE and a window function + ISNULL()
WITH CTE AS
(
SELECT *,
ROW_NUMBER() OVER(PARTITION BY acc_no ORDER BY id) RN
FROM T
)
SELECT T1.id,
T1.value,
T1.acc_no,
T1.value - ISNULL(T2.value, 0) Result
FROM CTE T1 LEFT JOIN CTE T2
ON T1.acc_no = T2.acc_no
AND
T1.RN = T2.RN + 1
ORDER BY T1.id;
Live Demo
Using window functions:
;WITH CTE AS
(
SELECT id, value, acc_no,
ROW_NUMBER() OVER (PARTITION BY acc_no ORDER BY id) AS seq
FROM tb_acc
)
SELECT t1.*, t1.value - COALESCE(t2.value, 0)
FROM CTE AS t1
LEFT JOIN CTE AS t2 ON t1.acc_no = t2.acc_no AND t1.seq = t2.seq + 1
You just need a windowed SUM:
SELECT
id
,value
,acc_no
,value - isnull(sum([value]) over (partition by acc_no order by id rows between 1 preceding and 1 preceding ), 0) as result
FROM tb_acc t
order by id

Rows Columns Traverse

I have data in the below format
id idnew
1 2
3 4
2
4 7
6 8
7
Result Should be something like this
ID should be followed by idnew
1
2
3
4
2
4
7
6
8
7
Thanks in advance
This should maintain the order:
SELECT id
FROM (
SELECT id, ROW_NUMBER() OVER (ORDER BY id) AS RowNumber
FROM myTable
UNION ALL
SELECT idnew, ROW_NUMBER() OVER (ORDER BY idnew) +
(SELECT COUNT(*) FROM dbo.myTable) AS RowNumber
FROM myTable
WHERE idnew IS NOT NULL
) a
ORDER BY RowNumber
I am assuming the id column is NOT NULL-able.
NOTE: If you want to keep the NULL values from the idnew column AND maintain the order, then remove the WHERE clause and ORDER BY id in the second select:
SELECT id
FROM (
SELECT id, ROW_NUMBER() OVER (ORDER BY id) AS RowNumber
FROM myTable
UNION ALL
SELECT idnew, ROW_NUMBER() OVER (ORDER BY id) +
(SELECT COUNT(*) FROM dbo.myTable) AS RowNumber
FROM myTable
) a
ORDER BY RowNumber
This is fully tested, try it here: https://rextester.com/DVZXO21058
Setting up the table as you described:
CREATE TABLE myTable (id INT, idnew INT);
INSERT INTO myTable (id, idnew)
VALUES (1, 2),
(3, 4),
(2, NULL),
(4, 7),
(6, 8),
(7, NULL);
SELECT * FROM myTable;
Here is the query to do the trick:
SELECT mixed_id FROM
(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS row_num,
id,
idnew
FROM myTable
) AS x
UNPIVOT
(
mixed_id for item in (id, idnew)
) AS y
WHERE mixed_id IS NOT NULL
ORDER BY row_num, mixed_id;
In order not to further complicate the query, this is taking advantage of 'id' would rank ahead of 'idnew' as a string. I believe string ranking is not the key issue here.
Using Cross Apply
;WITH CTE (id,idnew)
AS
(
SELECT 1,2 UNION ALL
SELECT 3,4 UNION ALL
SELECT 2,NULL UNION ALL
SELECT 4,7 UNION ALL
SELECT 6,8 UNION ALL
SELECT 7,NULL
)
SELECT New
FROM CTE
CROSS APPLY ( VALUES (id),(idnew))AS Dt (New)
WHERE dt.New IS NOT NULL
Result
New
---
1
2
3
4
2
4
7
6
8
7

How to achive below output using sql query in oracle 11g.?

I have table with below data :
Block_id Value
1 5
2 5
3 5
4 0
5 0
6 4
7 4
And I have to write query that give me below output :
Block_id
1-3
6-7
How to achieve this with pl/sql in Oracle 11g ?
you can do something like:
SQL> select * from data order by block_id;
BLOCK_ID VALUE
---------- ----------
1 5
2 5
3 5
4 0
5 0
6 4
7 4
9 5
10 5
12 2
SQL> select min(block_id) || '-' || max(block_id) block_range, value
2 from (select block_id, value, max(grp) over (partition by value order by block_id) grp
3 from (select block_id, value,
4 case
5 when lag(block_id) over (partition by value order by block_id) < block_id - 1
6 then
7 row_number() over (partition by value order by block_id)
8 when row_number() over (partition by value order by block_id) = 1 then 1
9 else null
10 end grp
11 from data
12 where value != 0))
13 group by value, grp
14 order by min(block_id);
BLOCK_RANG VALUE
---------- ----------
1-3 5
6-7 4
9-10 5
12-12 2
You do not need PL/SQL to do this, a simple query will do:
CREATE TABLE test(
a INTEGER,
b INTEGER
);
INSERT INTO test VALUES (1, 5);
INSERT INTO test VALUES (2, 5);
INSERT INTO test VALUES (3, 5);
INSERT INTO test VALUES (4, 0);
INSERT INTO test VALUES (5, 0);
INSERT INTO test VALUES (6, 4);
INSERT INTO test VALUES (7, 4);
select min(a) || '-' || max(a) from test group by b order by 1;
An alternative approach is to use aggregate functions:
select distinct
min(block_id) over (partition by value) || '-' ||
max(block_id) over (partition by value)
from whatever_your_table_is_called
where value > 0;