To find the starting and ending points in a sequence - sql

I've a table (T1) with one column (C1) with the below values
1
2
3
5
6
8
9
10
I want the output to print the continuous sequences with start and ending points like below.
1-3
5-6
8-10
Could you please help?
Any Database is fine.

Oracle: sample data first, while code you really need begins at line #11.
SQL> with t1 (c1) as
2 (select 1 from dual union all
3 select 2 from dual union all
4 select 3 from dual union all
5 select 5 from dual union all
6 select 6 from dual union all
7 select 8 from dual union all
8 select 9 from dual union all
9 select 10 from dual
10 )
11 select min(c1), max(c1)
12 from (select c1, c1 - row_number() over (order by c1) rn
13 from t1
14 )
15 group by rn
16 order by rn;
MIN(C1) MAX(C1)
---------- ----------
1 3
5 6
8 10
SQL>

You can use the following query. I have tested it with SQL Server, but I think it will work without modifications in Oracle:
create table t1(c1 int);
insert into t1
select *
from (values(1),(2),(3),(5),(6),(8),(9),(10))t(x);
select case when count(*) >1 then
concat(min(c1),'-',max(c1))
else concat(max(c1),'')
end as concat_cs
from (
select c1
,ROW_NUMBER() over(order by c1 asc) as rnk
,c1 - ROW_NUMBER() over(order by c1 asc) as grp
from t1
)x
group by x.grp
Output
concat_cs
1-3
5-6
8-10

with stab as (
select 1 as val from dual union all
select 2 as val from dual union all
select 3 as val from dual union all
select 5 as val from dual union all
select 6 as val from dual union all
select 8 as val from dual union all
select 9 as val from dual union all
select 10 as val from dual union all
select 13 as val from dual union all
select 15 as val from dual union all
select 16 as val from dual union all
select 17 as val from dual union all
select 18 as val from dual union all
select 19 as val from dual union all
select 23 as val from dual
),sq2 as(
select
row_number() over(order by 1) as rownumber,val
from stab
)
select
a.val,b.val
from sq2 A
join sq2 b on b.rownumber = a.rownumber+2
where mod(A.rownumber,3)=1
Output:
1 3
5 8
9 13
15 17
18 23

Related

Get parent id from level with Oracle SQL

I have a hierarchical structure defined by level and order of elements. Is it possible to create "parent_id" column with Oracle SQL without using procedures?
I need to generate red values:
test data:
with t as
(
select 1 id, 'element1' name, 1 level_ from dual union all
select 2 id, 'element2' name, 2 level_ from dual union all
select 3 id, 'element3' name, 3 level_ from dual union all
select 4 id, 'element4' name, 3 level_ from dual union all
select 5 id, 'element5' name, 3 level_ from dual union all
select 6 id, 'element6' name, 3 level_ from dual union all
select 7 id, 'element7' name, 2 level_ from dual union all
select 8 id, 'element8' name, 3 level_ from dual union all
select 9 id, 'element9' name, 4 level_ from dual union all
select 10 id, 'element10' name, 4 level_ from dual union all
select 11 id, 'element11' name, 1 level_ from dual union all
select 12 id, 'element12' name, 2 level_ from dual union all
select 13 id, 'element13' name, 3 level_ from dual union all
select 14 id, 'element14' name, 4 level_ from dual union all
select 15 id, 'element15' name, 4 level_ from dual union all
select 16 id, 'element16' name, 3 level_ from dual union all
select 17 id, 'element17' name, 4 level_ from dual union all
select 18 id, 'element18' name, 4 level_ from dual union all
select 19 id, 'element19' name, 1 level_ from dual
)
select * from t
From Oracle 12, you can use MATCH_RECOGNIZE:
select *
from t
MATCH_RECOGNIZE (
ORDER BY id DESC
MEASURES
child.id AS id,
child.name AS name,
child.lvl AS lvl,
parent.id AS parent_id
ONE ROW PER MATCH
AFTER MATCH SKIP TO NEXT ROW
PATTERN (child ancestors*? (parent | $))
DEFINE
parent AS lvl = child.lvl - 1
)
ORDER BY id
Or, again from Oracle 12, a LATERAL join:
select *
from t c
LEFT OUTER JOIN LATERAL(
SELECT p.id AS parent_id
FROM t p
WHERE c.id > p.id
AND c.lvl = p.lvl + 1
ORDER BY id DESC
FETCH FIRST ROW ONLY
)
ON (1 = 1)
ORDER BY id
Or, in earlier versions:
SELECT id, name, lvl, parent_id
FROM (
SELECT c.*,
p.id AS parent_id,
ROW_NUMBER() OVER (PARTITION BY c.id ORDER BY p.id DESC) AS rn
FROM t c
LEFT OUTER JOIN t p
ON (c.id > p.id AND c.lvl = p.lvl + 1)
)
WHERE rn = 1
ORDER BY id
Which, for the sample data:
CREATE TABLE t (id, name, lvl ) as
select 1, 'element1', 1 from dual union all
select 2, 'element2', 2 from dual union all
select 3, 'element3', 3 from dual union all
select 4, 'element4', 3 from dual union all
select 5, 'element5', 3 from dual union all
select 6, 'element6', 3 from dual union all
select 7, 'element7', 2 from dual union all
select 8, 'element8', 3 from dual union all
select 9, 'element9', 4 from dual union all
select 10, 'element10', 4 from dual union all
select 11, 'element11', 1 from dual union all
select 12, 'element12', 2 from dual union all
select 13, 'element13', 3 from dual union all
select 14, 'element14', 4 from dual union all
select 15, 'element15', 4 from dual union all
select 16, 'element16', 3 from dual union all
select 17, 'element17', 4 from dual union all
select 18, 'element18', 4 from dual union all
select 19, 'element19', 1 from dual;
All output:
ID
NAME
LVL
PARENT_ID
1
element1
1
null
2
element2
2
1
3
element3
3
2
4
element4
3
2
5
element5
3
2
6
element6
3
2
7
element7
2
1
8
element8
3
7
9
element9
4
8
10
element10
4
8
11
element11
1
null
12
element12
2
11
13
element13
3
12
14
element14
4
13
15
element15
4
13
16
element16
3
12
17
element17
4
16
18
element18
4
16
19
element19
1
null
db<>fiddle here

How to group-by in Oracle

I have a table like [Original] in below.
I want to sum by group-by field like [result].
Does anyone have an idea to make this query?
Thank you in advance for your help.
WITH t1 as (
SELECT 1 AS ID, 'A' AS FIELD, 1 AS VAL FROM dual
UNION SELECT 2 AS ID, 'A' AS FIELD, 2 AS VAL FROM dual
UNION SELECT 3 AS ID, 'A' AS FIELD, 1 AS VAL FROM dual
UNION SELECT 4 AS ID, 'B' AS FIELD, 2 AS VAL FROM dual
UNION SELECT 5 AS ID, 'B' AS FIELD, 2 AS VAL FROM dual
UNION SELECT 6 AS ID, 'B' AS FIELD, 1 AS VAL FROM dual
UNION SELECT 7 AS ID, 'A' AS FIELD, 3 AS VAL FROM dual
UNION SELECT 8 AS ID, 'A' AS FIELD, 2 AS VAL FROM dual
UNION SELECT 9 AS ID, 'A' AS FIELD, 1 AS VAL FROM dual
)
SELECT *
FROM t1
[Original Data]
ID FIELD VAL
1 A 1
2 A 2
3 A 1
4 B 2
5 B 2
6 B 1
7 A 3
8 A 2
9 A 1
[Result]
ID FIELD VAL
1 A 4
4 B 5
7 A 6
This is island and gap issue and you can use analytical function as follows:
SQL> WITH t1 as (
2 SELECT 1 AS ID, 'A' AS FIELD, 1 AS VAL FROM dual
3 UNION SELECT 2 AS ID, 'A' AS FIELD, 2 AS VAL FROM dual
4 UNION SELECT 3 AS ID, 'A' AS FIELD, 1 AS VAL FROM dual
5 UNION SELECT 4 AS ID, 'B' AS FIELD, 2 AS VAL FROM dual
6 UNION SELECT 5 AS ID, 'B' AS FIELD, 2 AS VAL FROM dual
7 UNION SELECT 6 AS ID, 'B' AS FIELD, 1 AS VAL FROM dual
8 UNION SELECT 7 AS ID, 'A' AS FIELD, 3 AS VAL FROM dual
9 UNION SELECT 8 AS ID, 'A' AS FIELD, 2 AS VAL FROM dual
10 UNION SELECT 9 AS ID, 'A' AS FIELD, 1 AS VAL FROM dual
11 )
12 SELECT MIN(ID) AS ID, FIELD, SUM(VAL)
13 FROM (SELECT T1.*,
14 SUM(CASE WHEN LAG_FIELD = FIELD THEN 0 ELSE 1 END)
15 OVER (ORDER BY ID) AS SM
16 FROM (SELECT T1.*,
17 LAG(FIELD) OVER (ORDER BY ID) AS LAG_FIELD
18 FROM t1
19 ) T1
20 )
21 GROUP BY FIELD, SM
22 ORDER BY 1;
ID F SUM(VAL)
---------- - ----------
1 A 4
4 B 5
7 A 6
SQL>
This is indeed a gaps-and-islands problem. I think the simplest approach here is to use the difference between row numbers to identify groups of adjacent rows:
select min(id) as id, field, sum(val) as val
from (
select t1.*,
row_number() over(order by id) rn1,
row_number() over(partition by field order by id) rn2
from t1
) t
group by field, rn1 - rn2
order by min(id)
If id is always incrementing without gaps, this is even simpler:
select min(id) as id, field, sum(val) as val
from (
select t1.*,
row_number() over(partition by field order by id) rn
from t1
) t
group by field, id - rn
order by min(id)
From Oracle 12, you can do it quite simply using MATCH_RECOGNIZE:
WITH t1 as (
SELECT 1 AS ID, 'A' AS FIELD, 1 AS VAL FROM dual
UNION SELECT 2 AS ID, 'A' AS FIELD, 2 AS VAL FROM dual
UNION SELECT 3 AS ID, 'A' AS FIELD, 1 AS VAL FROM dual
UNION SELECT 4 AS ID, 'B' AS FIELD, 2 AS VAL FROM dual
UNION SELECT 5 AS ID, 'B' AS FIELD, 2 AS VAL FROM dual
UNION SELECT 6 AS ID, 'B' AS FIELD, 1 AS VAL FROM dual
UNION SELECT 7 AS ID, 'A' AS FIELD, 3 AS VAL FROM dual
UNION SELECT 8 AS ID, 'A' AS FIELD, 2 AS VAL FROM dual
UNION SELECT 9 AS ID, 'A' AS FIELD, 1 AS VAL FROM dual
)
SELECT *
FROM t1
MATCH_RECOGNIZE (
ORDER BY id
MEASURES
FIRST( id ) AS id,
FIRST( field ) AS field,
SUM( val ) AS total
ONE ROW PER MATCH
PATTERN( same_field+ )
DEFINE same_field AS FIRST(field) = field
)
Which outputs:
ID | FIELD | TOTAL
-: | :---- | ----:
1 | A | 4
4 | B | 5
7 | A | 6
db<>fiddle here

How to sort alphanumeric String in oracle?

Input is:
Section1
Section2
Section3
Section10
Section11
Section1A
Section1B
Section12
Section11A
Section11B
And I want output like:
Section1
Section1A
Section1B
Section2
Section3
Section10
Section11
Section11A
Section11B
Section12
I tried query :
select section_name
from sections
order by length(section_name),section_name
Assuming that the structure of your strings is fixed, as in your example, this could be a way:
SQL> select x,
2 to_number(regexp_substr(x, '[0-9]+')) numericPart,
3 regexp_substr(x, '([0-9]+)([A-Z])', 1, 1, '', 2) optionalChar
4 from (
5 select 'Section1' x from dual union all
6 select 'Section2' from dual union all
7 select 'Section3' from dual union all
8 select 'Section10' from dual union all
9 select 'Section11' from dual union all
10 select 'Section1A' from dual union all
11 select 'Section1B' from dual union all
12 select 'Section12' from dual union all
13 select 'Section11A' from dual union all
14 select 'Section11B' from dual
15 )
16 order by numericPart,
17 optionalChar nulls first
18 ;
X NUMERICPART OPTIONALCHAR
---------- ----------- ----------------------------------------
Section1 1
Section1A 1 A
Section1B 1 B
Section2 2
Section3 3
Section10 10
Section11 11
Section11A 11 A
Section11B 11 B
Section12 12
Here you first order by the numeric part, treating it as number, and then consider the (optional) character after the number.

Select values is a row as column values

How can I retrieve values in a row as column values?
Example:
Consider the output of below query as INPUT :
Select 1,2,3,4,5,6,7,8,9,10
from dual;
I need a query that can give below output:
COL1
----
1
2
3
4
5
6
7
8
9
10
SELECT 1 AS "COL1" FROM dual
UNION
SELECT 2 FROM dual
UNION
SELECT 3 FROM dual
UNION
SELECT 4 FROM dual
UNION
SELECT 5 FROM dual
UNION
SELECT 6 FROM dual
UNION
SELECT 7 FROM dual
UNION
SELECT 8 FROM dual
UNION
SELECT 9 FROM dual
UNION
SELECT 10 FROM dual ;
If you want to generate a sequence of numbers in Oracle:
with n as (
select level as n
from dual
connect by level <= 10
)
select *
from n;
Or, if you have 10 columns, you can do an unpivot. An easy way is with union all:
select col1 from t union all
select col2 from t union al
. . .
select col10 from t;

SQL/Oracle: How to find the key of the 'N' occurrence for EACH value?

Lets say a table like this (just for illustration):
KEY VALUE
1 A
2 A
3 B
4 C
5 A
6 B
7 C
8 A
9 C
I need a SINGLE SQL to get the key of the 3th (or less) occurrence for EACH value?
VALUE KEY
A 5 (more than 3 occurrences, so it gets the 3th occurrence)
B 6 (only 2 occurrences, so it gets the last one)
C 9
Update: Oracle 10g
I suspect there is a simpler way to write this without needing 3 layers of nesting. But this should work.
SQL> ed
Wrote file afiedt.buf
1 with x as (
2 select 1 key, 'A' val from dual union all
3 select 2, 'A' from dual union all
4 select 3, 'B' from dual union all
5 select 4, 'C' from dual union all
6 select 5, 'A' from dual union all
7 select 6, 'B' from dual union all
8 select 7, 'C' from dual union all
9 select 8, 'A' from dual union all
10 select 9, 'C' from dual)
11 select key,
12 val
13 from (select key,
14 val,
15 rnk,
16 max(rnk) over (partition by val) max_rnk
17 from (select key,
18 val,
19 rank() over (partition by val order by key) rnk
20 from x
21 )
22 where rnk <= 3
23 )
24* where rnk = max_rnk
SQL> /
KEY V
---------- -
5 A
6 B
9 C
SELECT t1.Value, t1."KEY"
FROM (SELECT ROW_NUMBER() OVER (PARTITION BY Value ORDER BY "KEY") AS RowNumber,
"KEY", Value
FROM MyTable) t1
LEFT JOIN
(SELECT ROW_NUMBER() OVER (PARTITION BY Value ORDER BY "KEY") AS RowNumber,
"KEY", Value
FROM MyTable) t2 ON t2.Value = t1.Value AND t2.RowNumber = t1.RowNumber + 1
WHERE t1.RowNumber = 3 OR (t1.RowNumber IN (1, 2) AND t2."Key" IS NULL)