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

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;

Related

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.

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

Get next row value based on returned list of rows

In Oracle, suppose I have a query that returns the following list:
ID Sequence#
12 1
15 3
25 5
All I know in this case is the ID of some row (let's suppose 12), I need to return the ID of a row with the next sequence number which in this case is 3 (id = 15)
How can I do it? I know there's a Oracle function lead, but I wasn't able to successfully impement is.
Yes, you can use lead function to get the next value. Here is an example of how it can be done.
-- sample of data from your question
SQL> with t1(ID, Sequence#) as
2 (
3 select 12, 1 from dual union all
4 select 15, 3 from dual union all
5 select 25, 5 from dual
6 )
7 select *
8 from (select id
9 , sequence#
10 , lead(sequence#) over(order by id) next_sequence#
11 , lead(id) over(order by id) next_id#
12 from t1
13 )
14 where id = 12
15 ;
ID SEQUENCE# NEXT_SEQUENCE# NEXT_ID#
---------- ---------- -------------- ----------
12 1 3 15
SELECT * FROM table1 where ID in (SELECT min(ID) FROM table1 WHERE ID > 12)
Select sequence from my_ table where id=(select min(id) from my_table where sequence> 1)
Replace (1) in the above query with any value that you are searching for its next

Oracle SQL: Select at least the first n rows, continue until a column value is different from the last one

given a table foo of the following structure (Oracle 11g):
ID | GROUP_ID
1 | 100
2 | 100
3 | 100
4 | 200
5 | 300
6 | 300
7 | 400
I want to select the first n rows (ordered by ID) or more, such that I always get a complete group.
Example:
n = 2: I want to get at least the first two rows, but since ID 3 also belongs to group 100, I want to get that as well.
n = 4: Give me the first four rows and I am happy ;-)
n = 5: Rows 1-6 are requested.
Your help is highly appreciated!
Solution using rank():
select id, group_id
from (select t.*, rank() over (order by group_id) as rnk
from t)
where rnk <= :n;
Building test data:
SQL> create table t (id number not null primary key
2 , group_id number not null);
Table created.
SQL> insert into t values (1, 100);
1 row created.
SQL> insert into t values (2, 100);
1 row created.
SQL> insert into t values (3, 100);
1 row created.
SQL> insert into t values (4, 200);
1 row created.
SQL> insert into t values (5, 300);
1 row created.
SQL> insert into t values (6, 300);
1 row created.
SQL> insert into t values (7, 400);
1 row created.
SQL> commit;
Commit complete.
SQL>
Running...
SQL> var n number
SQL> exec :n := 2;
PL/SQL procedure successfully completed.
SQL> select id, group_id
2 from (select t.*, rank() over (order by group_id) as rnk
3 from t)
4 where rnk <= :n;
ID GROUP_ID
---------- ----------
1 100
2 100
3 100
SQL> exec :n := 4;
PL/SQL procedure successfully completed.
SQL> select id, group_id
2 from (select t.*, rank() over (order by group_id) as rnk
3 from t)
4 where rnk <= :n;
ID GROUP_ID
---------- ----------
1 100
2 100
3 100
4 200
SQL> exec :n := 5;
PL/SQL procedure successfully completed.
SQL> select id, group_id
2 from (select t.*, rank() over (order by group_id) as rnk
3 from t)
4 where rnk <= :n;
ID GROUP_ID
---------- ----------
1 100
2 100
3 100
4 200
5 300
6 300
6 rows selected.
EDIT Here is version that includes the for update clause (:n = 2):
SQL> select id, group_id
2 from T
3 where rowid in (select RID
4 from (select t.rowid as RID, t.*, rank() over (order by group_id) as rnk
5 from t)
6 where rnk <= :n)
7 for update;
ID GROUP_ID
---------- ----------
1 100
2 100
3 100
If it is always true that GROUP_ID is contiguous and ascending, then this is easily solved with SQL using an analytical ROW_NUMBER() function:
SQL> select id
2 , group_id
3 from foo
4 where group_id <= ( select group_id
5 from (
6 select f.group_id
7 , row_number() over (order by f.id asc) rn
8 from foo f
9 )
10 where rn = &n )
11 order by id
12 /
Enter value for n: 2
old 10: where rn = &n )
new 10: where rn = 2 )
ID GROUP_ID
---------- ----------
1 100
2 100
3 100
SQL> r
1 select id
2 , group_id
3 from foo
4 where group_id <= ( select group_id
5 from (
6 select f.group_id
7 , row_number() over (order by f.id asc) rn
8 from foo f
9 )
10 where rn = &n )
11* order by id
Enter value for n: 4
old 10: where rn = &n )
new 10: where rn = 4 )
ID GROUP_ID
---------- ----------
1 100
2 100
3 100
4 200
SQL> r
1 select id
2 , group_id
3 from foo
4 where group_id <= ( select group_id
5 from (
6 select f.group_id
7 , row_number() over (order by f.id asc) rn
8 from foo f
9 )
10 where rn = &n )
11* order by id
Enter value for n: 5
old 10: where rn = &n )
new 10: where rn = 5 )
ID GROUP_ID
---------- ----------
1 100
2 100
3 100
4 200
5 300
6 300
6 rows selected.
SQL>
If your IDs are always sequential (without gaps) from 1. And if your Group_IDs never occur as a second group elsewhere. And if your Group_IDs are always ascending in value...
SELECT
*
FROM
foo
WHERE
Group_ID <= (SELECT Group_ID FROM foo WHERE ID = n)
ORDER BY
ID
You'll benefit here from having separate indexes on ID and Group_ID
If we assume that the group_id's are contiguous and ascending, then #Shannon's answer works perfectly. If we do not make that assumption, and we have data that looks like this, for example:
SQL> select * from foo order by id;
ID GROUP_ID
-- --------
1 100
2 100
3 100
4 200
6 100
7 400
9 500
10 500
11 500
12 600
Then it's a stickier problem. For example, if N = 3, 4, or 5, then we need to get the rows through ID = 6. For N = 6, we need up to ID = 7. For N = 7, we need through ID = 11.
I believe this query works regardless of the order of group_id:
For N = 7:
WITH q AS (SELECT ID, group_id
, row_number() OVER (ORDER BY ID) rn
, MAX(id) OVER (PARTITION BY group_id) rn2
FROM foo)
SELECT ID, group_id FROM q
WHERE ID <= (SELECT max(rn2) FROM q WHERE rn <= :N)
ORDER BY ID;
ID GROUP_ID
-- --------
1 100
2 100
3 100
4 200
6 100
7 400
9 500
10 500
11 500
9 rows selected
For N = 6:
ID GROUP_ID
-- --------
1 100
2 100
3 100
4 200
6 100
7 400
For N = 1:
ID GROUP_ID
-- --------
1 100
2 100
3 100
4 200
6 100

Oracle: enumerate groups of similar rows

I have the following table:
ID | X
1 | 1
2 | 2
3 | 5
4 | 6
5 | 7
6 | 9
I need to enumerate groups of rows in such way that if row i and i-1 differ in column X by less than 2 they should have the same group number N. See example below.
ID | X | N
1 | 1 | 1
2 | 2 | 1
3 | 5 | 2
4 | 6 | 2
5 | 7 | 2
6 | 9 | 3
Note that rows X(2)-X(1)=1 so they are grouped in the first group. Than X(3)-X(2)=3 so the 3rd row goes to 2nd group with 3rd and 4th row. X(6)-X(5)=2 so 6th row is in the 3rd group.
Can anybody help me with writing SQL query that will return the second table?
This should do it:
select id, x, sum(new_group) over (order by id) as group_no
from
( select id, x, case when x-prev_x = 1 then 0 else 1 end new_group
from
( select id, x, lag(x) over (order by id) prev_x
from mytable
)
);
I get the correct answer for your data with that query.
SQL> create table mytable (id,x)
2 as
3 select 1, 1 from dual union all
4 select 2, 2 from dual union all
5 select 3, 5 from dual union all
6 select 4, 6 from dual union all
7 select 5, 7 from dual union all
8 select 6, 9 from dual
9 /
Table created.
SQL> select id
2 , x
3 , sum(y) over (order by id) n
4 from ( select id
5 , x
6 , case x - lag(x) over (order by id)
7 when 1 then 0
8 else 1
9 end y
10 from mytable
11 )
12 order by id
13 /
ID X N
---------- ---------- ----------
1 1 1
2 2 1
3 5 2
4 6 2
5 7 2
6 9 3
6 rows selected.
Which is essentially the same as Tony's answer, only one inline view less.
Regards,
Rob.
Using basic operations only:
create table test(id int, x int);
insert into test values(1, 1), (2, 2), (3, 5), (4, 6), (5, 7), (6, 9);
create table temp as
select rownum() r, 0 min, x max
from test t
where not exists(select * from test t2 where t2.x = t.x + 1);
update temp t set min = select max + 1 from temp t2 where t2.r = t.r - 1;
update temp t set min = 0 where min is null;
select * from temp order by r;
select t.id, t.x, x.r from test t, temp x where t.x between x.min and x.max;
drop table test;
drop table temp;