How to query for non-consecutive values? - sql

I have a column of id: 1, 3, 4, 9, 10, 11 in the table called t_mark
How can I get the non-consecutive range? (e.g. [1, 3], [4, 9])

Alternatively, using LEAD analytic function, along with your fancy formatting. TEST CTE is what you already have; lines #9 onwards is what you need.
SQL> with test (col) as
2 (select 1 from dual union all
3 select 3 from dual union all
4 select 4 from dual union all
5 select 9 from dual union all
6 select 10 from dual union all
7 select 11 from dual
8 ),
9 temp as
10 (select col,
11 lead(col) over (order by col) lcol
12 from test
13 )
14 select '[' || col ||' - '|| lcol ||']' result
15 From temp
16 where lcol - col > 1
17 order by col;
RESULT
-------------------------------------------------------
[1 - 3]
[4 - 9]
SQL>
[EDIT: Adjusted so that you shouldn't have to think too much]
This is what you have:
SQL> select * From t_mark;
M_ID
----------
1
3
4
9
10
11
6 rows selected.
This is what you need:
SQL> with temp as
2 (select m_id,
3 lead(m_id) over (order by m_id) lm_id
4 from t_mark
5 )
6 select '[' || m_id ||' - '|| lm_id ||']' result
7 From temp
8 where lm_id - m_id > 1
9 order by m_id;
RESULT
------------------------------------------------------------------
[1 - 3]
[4 - 9]
SQL>
Basically, you should learn how to use a CTE (common table expression, a.k.a. the with factoring clause).

Assuming that by "list" you mean a table with a column, then you can do this with lag():
select prev_number, number
from (select t.*, lag(number) over (order by number) as prev_number
from t
) t
where prev_number <> number - 1;

This should do the trick :
WITH original_table(number_column) as (select 1 from dual union all
select 3 from dual union all
select 4 from dual union all
select 9 from dual union all
select 10 from dual union all
select 11 from dual),
numbers AS (
SELECT row_number() over (ORDER BY number_column ASC ) row_num,
number_column
FROM original_table
)
SELECT nb1.number_column AS lnumber,
nb2.number_column AS rnumber
FROM numbers nb1
INNER JOIN numbers nb2 ON nb1.row_num + 1 = nb2.row_num
AND nb1.number_column + 1 < nb2.number_column
Result :
| LNUMBER | RNUMBER |
|---------|---------|
| 1 | 3 |
| 4 | 9 |
Link to the dbfiddle for testing

Related

Compare before column in before row with next column in next row

My code is :
with x as
(
select 1 col from dual union all
select 2 col from dual union all
select 8 col from dual union all
select 4 col from dual union all
select 3 col from dual union all
select 2 col from dual
)
select col col1, col col2, col col3, rownum
from x
where col2.ROWNUM > col1.ROWNUM -1
and col2.ROWNUM > col3ROWNUM +1 ;
I want to compare col2.ROWNUM > col1.ROWNUM -1 and col2.ROWNUM > col3ROWNUM + 1 but that doesn't work and I got an error
ORA-01747: invalid user.table.column, table.column, or column specification
01747. 00000 - "invalid user.table.column, table.column, or column specification"
*Cause:
*Action:
Error at Line: 10 Column: 13
Please help me
It looks you got something wrong.
Result of that CTE is a single-column table whose only column is named col. There are no other columns.
SQL> with x as (
2 select 1 col from dual union all --> in UNION, all columns are
3 select 2 col from dual union all named by column name(s) from the
4 select 8 col from dual union all first SELECT statement
5 select 4 col from dual union all
6 select 3 col from dual union all
7 select 2 col from dual)
8 select x.*, rownum
9 from x;
COL ROWNUM
---------- ----------
1 1
2 2
8 3
4 4
3 5
2 6
6 rows selected.
SQL>
Therefore, where clause you wrote doesn't make any sense. Perhaps you should explain what you really have, rules that should be applied to source data and result you'd like to get.
Based on text you put into the title:
compare before column in before row with next column in next row
maybe you'd be interested in lag and lead analytic functions which then let you compare values in adjacent rows (pay attention to NULL values; I didn't). For example:
SQL> with x as (
2 select 1 col from dual union all
3 select 2 col from dual union all
4 select 8 col from dual union all
5 select 4 col from dual union all
6 select 3 col from dual union all
7 select 2 col from dual
8 ),
9 temp as
10 (select col,
11 rownum as rn
12 from x
13 ),
14 temp2 as
15 (select
16 rn,
17 col as this_row,
18 lag(col) over (order by rn) as previous_row,
19 lead(col) over (order by rn) as next_row
20 from temp
21 )
22 select this_row,
23 previous_row,
24 next_row,
25 --
26 case when this_row < previous_row then 'This < previous'
27 when this_row < next_row then 'This < next'
28 else 'something else'
29 end as result
30 from temp2
31 order by rn;
Result:
THIS_ROW PREVIOUS_ROW NEXT_ROW RESULT
---------- ------------ ---------- ---------------
1 2 This < next
2 1 8 This < next
8 2 4 something else
4 8 3 This < previous
3 4 2 This < previous
2 3 This < previous
6 rows selected.
SQL>
Use lead or lag functions. But, please, do not use rownum for such purposes.
Rownum indicates simply the order in which a row was found in the database and cannot be used for other purposes except limiting the number of rows fetched, like where rownum<=1 to be certain you won't get a too_many_rows exception, for instance. Still, if in a query you do fetch the pseud-column rownum, give it an alias so that you may use that value later on.
Moreover, what is supposed to mean col2.ROWNUM or col1.ROWNUM? That is not clear. col1 and col2 are two columns, which do not have the attribute rownum.
Something that may help in the future for analytic queries:
https://oracle-base.com/articles/misc/lag-lead-analytic-functions
And, if you wish to get a working SQL, please explain clearly what you wish to achieve, for I haven't really understood what that code is intended to do.
A way you may use rownum without getting errors:
with x as (
select 1 col from dual union all
select 2 col from dual union all
select 8 col from dual union all
select 4 col from dual union all
select 3 col from dual union all
select 2 col from dual)
,x2 as (
select col col1 ,col col2, col col3 ,rownum rn
from x
)
select *
from x2
where rn between 2 and 3 --- rownum cannot be used in such a
condition!!!
;
Or, to be certain you get only the first row from a table satisfying a given condition:
select x_col1, x_col2 into v_col1, v_col2
from x_table
where ... --- logical conditions
and rownum<=1; --- rownum <= 1 avoids too_many_rows_exception if several rows satisfy the logical conditions given before
In Oracle, results sets have a non-deterministic order (i.e. they are unordered) unless you use an ORDER BY clause. Therefore, if you have a physical table, you need another column to provide the order (rather than relying on the ROWNUM pseudo-column, which may result in unexpected behaviour):
CREATE TABLE x (order_id, col) AS
SELECT 1, 1 FROM DUAL UNION ALL
SELECT 2, 2 FROM DUAL UNION ALL
SELECT 3, 8 FROM DUAL UNION ALL
SELECT 4, 4 FROM DUAL UNION ALL
SELECT 5, 3 FROM DUAL UNION ALL
SELECT 6, 2 FROM DUAL;
If you want to find the rows that go up in succession, then you can use MATCH_RECOGNIZE for row-by-row pattern matching:
SELECT *
FROM x
MATCH_RECOGNIZE(
ORDER BY order_id
MEASURES
any_row.col AS col1,
FIRST(up.col) AS col2,
LAST(up.col) AS col3,
FIRST(order_id) AS start_order_id
PATTERN ( any_row up{2} )
DEFINE up AS ( col > PREV(col) )
)
or the LEAD analytic function:
SELECT *
FROM (
SELECT col AS col1,
LEAD(col, 1) OVER (ORDER BY order_id) AS col2,
LEAD(col, 2) OVER (ORDER BY order_id) AS col3,
order_id
FROM x
)
WHERE col2 > col1
AND col3 > col2;
Which both output:
COL1
COL2
COL3
START_ORDER_ID
1
2
8
1
fiddle
It looks like you want to find the rows where the value of the column is bigger than it is in both - the previous and next row. If so, you could try this:
WITH
tbl (ID, COL) AS -- Sample data (ID column is just to preserve order of the rows)
(
Select 1, 1 From Dual Union All
Select 2, 2 From Dual Union All
Select 3, 8 From Dual Union All
Select 4, 4 From Dual Union All
Select 5, 3 From Dual Union All
Select 6, 2 From DUAL
)
Select ID, COL, CASE WHEN COL > LAG(COL, 1) OVER(Order By ID) And COL > LEAD(COL, 1) OVER(Order By ID) THEN 'YES' END "BIGGER_THAN_PREV_AND_NEXT"
From tbl
Order By ID
ID COL BIGGER_THAN_PREV_AND_NEXT
---------- ---------- -------------------------
1 1
2 2
3 8 YES
4 4
5 3
6 2
... with a bit different sample data this will find the other row(s) that satisfy the condition ...
WITH
tbl (ID, COL) AS -- Sample data (ID column is just to preserve order of the rows)
(
Select 1, 1 From Dual Union All
Select 2, 2 From Dual Union All
Select 3, 8 From Dual Union All
Select 4, 4 From Dual Union All
Select 5, 5 From Dual Union All -- value of COL changed from 3 to 5
Select 6, 2 From DUAL
)
Select ID, COL, CASE WHEN COL > LAG(COL, 1) OVER(Order By ID) And COL > LEAD(COL, 1) OVER(Order By ID) THEN 'YES' END "BIGGER_THAN_PREV_AND_NEXT"
From tbl
Order By ID
ID COL BIGGER_THAN_PREV_AND_NEXT
---------- ---------- -------------------------
1 1
2 2
3 8 YES
4 4
5 5 YES
6 2
OR without ID - using ROWNUM (as in your question), - not adviseable, though...
WITH
tbl (COL) AS -- Sample data (without ID column)
(
Select 1 From Dual Union All
Select 2 From Dual Union All
Select 8 From Dual Union All
Select 4 From Dual Union All
Select 5 From Dual Union All
Select 2 From DUAL
)
Select COL, CASE WHEN COL > LAG(COL, 1) OVER(Order By ROWNUM) And COL > LEAD(COL, 1) OVER(Order By ROWNUM) THEN 'YES' END "BIGGER_THAN_PREV_AND_NEXT"
From tbl
COL BIGGER_THAN_PREV_AND_NEXT
---------- -------------------------
1
2
8 YES
4
5 YES
2
Any Order By clause added to the query could change the ROWNUM values and the result...

Count occurences of values in table, when I treat one value as occurence of all other values

I have a table with one column (just to simplify the problem) with values 0-23 or *.
I want to count occurrences of each value 0-23, but treat * as occurrence of all other values
for example:
column_name
-------------
3
4
5
6
7
*
4
4
3
*
I want to get something like that:
column_name | count
--------------------
1 | 2
2 | 2
3 | 4
4 | 5
5 | 3
6 | 3
7 | 3
.....
I tried experimenting with different count and "group by" methods, but always getting very strange results. Basically the main problem here is to how count rows when I need to have one value in all other groups.
You could use analytic function that counts values where * is replaced by actual value between 0 and 23:
SELECT DISTINCT n.RN "COL_1", Count(REPLACE(t.COL_1, '*', n.RN)) OVER(Partition By n.RN) "CNT"
FROM tbl t
INNER JOIN ( Select To_Char(LEVEL - 1) "RN" From Dual Connect By LEVEL <=24 ) n ON(n.RN = REPLACE(t.COL_1, '*', n.RN))
WHERE n.RN IN(SELECT COL_1 FROM tbl)
ORDER BY To_Number(n.RN)
which with your sample data:
WITH
tbl (COL_1) AS
(
Select '3' From Dual Union All
Select '4' From Dual Union All
Select '5' From Dual Union All
Select '6' From Dual Union All
Select '7' From Dual Union All
Select '*' From Dual Union All
Select '4' From Dual Union All
Select '4' From Dual Union All
Select '3' From Dual Union All
Select '*' From Dual Union All
Select '3' From Dual
)
... results as:
COL_1 CNT
---------------------------------------- ----------
3 5
4 5
5 3
6 3
7 3
... and if you exclude the Where clause you will get all the rows (0 - 23) with number of occurances counted by REPLACE of * with any of the numbers
COL_1 CNT
---------------------------------------- ----------
0 2
1 2
2 2
3 5
4 5
5 3
6 3
7 3
8 2
9 2
10 2
11 2
12 2
13 2
14 2
15 2
16 2
17 2
18 2
19 2
20 2
21 2
22 2
23 2
You can do it using successive WITH's :
First one to calculate number of occurrence of *.
And the second is to calculate number of occurrence of each number.
with cte as (
select count(1) as c
from mytable
where column_name = '*'
),
cte2 as (
select column_name, count(1) as c
from mytable, cte
group by column_name
)
select column_name, cte.c + cte2.c
from cte2, cte;
You can with nested statements too,
SELECT ID,(count_ + (
SELECT COUNT(ID) FROM sql_test_a
WHERE ID = '*')) as count_
FROM (
SELECT ID,COUNT(ID) as count_
FROM sql_test_a WHERE ID != '*' GROUP BY ID);

how to return multiple records from single record in Oracle

I have source Tbl like
CID No_Of_Seats_Booked Seat_Numbers
-------------------------------------
1 3 01A01B01C
Tgt table O/P
CID Seat_id
------------
1 01A
1 01B
1 01C
Here's one option:
SQL> with test (cid, no_of_seats_booked, seat_numbers) as
2 -- sample data
3 (select 1, 3, '01A01B01C' from dual union all
4 select 2, 2, '02A02B' from dual)
5 -- query you need begins here
6 select cid,
7 substr(seat_numbers, 1 + (column_value - 1) * 3, 3) seat_id
8 from test cross join
9 table(cast(multiset(select level from dual
10 connect by level <= no_of_seats_booked
11 ) as sys.odcinumberlist))
12 order by cid, seat_id;
CID SEA
---------- ---
1 01A
1 01B
1 01C
2 02A
2 02B
SQL>
Another approach, just for variety:
with demo(cid, no_of_seats_booked, seat_numbers) as
( select 1, 3, '01A01B01C' from dual union all
select 2, 2, '02A02B' from dual
)
select d.cid
, regexp_substr(d.seat_numbers, '...', 1, r.rnum) seat_id
from demo d
cross apply (select rownum as rnum from dual connect by rownum <= d.no_of_seats_booked) r
order by d.cid, seat_id;

create sequence of numbers on grouped column in Oracle

Consider below table with column a,b,c.
a b c
3 4 5
3 4 5
6 4 1
1 1 8
1 1 8
1 1 0
1 1 0
I need a select statement to get below output. i.e. increment column 'rn' based on group of column a,b,c.
a b c rn
3 4 5 1
3 4 5 1
6 4 1 2
1 1 8 3
1 1 8 3
1 1 0 4
1 1 0 4
You can use the DENSE_RANK analytic function to get a unique ID for each combination of A, B, and C. Just note that if a new value is inserted into the table, the IDs of each combination of A, B, and C will shift and may not be the same.
Query
WITH
my_table (a, b, c)
AS
(SELECT 3, 4, 5 FROM DUAL
UNION ALL
SELECT 3, 4, 5 FROM DUAL
UNION ALL
SELECT 6, 4, 1 FROM DUAL
UNION ALL
SELECT 1, 1, 8 FROM DUAL
UNION ALL
SELECT 1, 1, 8 FROM DUAL
UNION ALL
SELECT 1, 1, 0 FROM DUAL
UNION ALL
SELECT 1, 1, 0 FROM DUAL)
SELECT t.*, DENSE_RANK () OVER (ORDER BY b desc, c desc, a) as rn
FROM my_table t;
Result
A B C RN
____ ____ ____ _____
3 4 5 1
3 4 5 1
6 4 1 2
1 1 8 3
1 1 8 3
1 1 0 4
1 1 0 4
As a starter: for your answer to make sense at all, you need a column that defines the ordering of the rows. Let me assume that you have such column, called id.
Then, you can use window functions:
select a, b, c,
sum(case when a = lag_a and b = lag_b and c = lag_c then 0 else 1 end) over(order by id) rn
from (
select t.*,
lag(a) over(order by id) lag_a,
lag(b) over(order by id) lag_b,
lag(c) over(order by id) lag_c
from mytable t
) t
Assuming you have some way of ordering your rows, then you can use MATCH_RECOGNIZE:
SELECT a, b, c, rn
FROM table_name
MATCH_RECOGNIZE (
ORDER BY id
MEASURES MATCH_NUMBER() AS rn
ALL ROWS PER MATCH
PATTERN ( FIRST_ROW EQUAL_ROWS* )
DEFINE EQUAL_ROWS AS (
EQUAL_ROWS.a = PREV( EQUAL_ROWS.a )
AND EQUAL_ROWS.b = PREV( EQUAL_ROWS.b )
AND EQUAL_ROWS.c = PREV( EQUAL_ROWS.c )
)
)
So, for your test data:
CREATE TABLE table_name ( id, a, b, c ) AS
SELECT 1, 3, 4, 5 FROM DUAL UNION ALL
SELECT 2, 3, 4, 5 FROM DUAL UNION ALL
SELECT 3, 6, 4, 1 FROM DUAL UNION ALL
SELECT 4, 1, 1, 8 FROM DUAL UNION ALL
SELECT 5, 1, 1, 8 FROM DUAL UNION ALL
SELECT 6, 1, 1, 0 FROM DUAL UNION ALL
SELECT 7, 1, 1, 0 FROM DUAL;
Outputs:
A | B | C | RN
-: | -: | -: | -:
3 | 4 | 5 | 1
3 | 4 | 5 | 1
6 | 4 | 1 | 2
1 | 1 | 8 | 3
1 | 1 | 8 | 3
1 | 1 | 0 | 4
1 | 1 | 0 | 4
db<>fiddle here
It can also be done without any ordering, by getting the distinct groups and numbering each group. Borrowing the first part from EJ Egjed:
WITH my_table (a, b, c) AS
(SELECT 3, 4, 5 FROM DUAL
UNION ALL
SELECT 3, 4, 5 FROM DUAL
UNION ALL
SELECT 6, 4, 1 FROM DUAL
UNION ALL
SELECT 1, 1, 8 FROM DUAL
UNION ALL
SELECT 1, 1, 8 FROM DUAL
UNION ALL
SELECT 1, 1, 0 FROM DUAL
UNION ALL
SELECT 1, 1, 0 FROM DUAL)
, groups as (select distinct a, b, c
from my_table)
, groupnums as (select rownum as num, a, b, c
from groups)
select a, b, c, num
from my_table join groupnums using(a,b,c);

Oracle - generate a running number by group

I need to generate a running number / group sequence inside a select statement for a group of data.
For example
Group Name Sequence
1 a 1
1 b 2
1 c 3
2 d 1
2 e 2
2 f 3
So for each group the sequence should be a running number starting with 1 depending on the order of column"Name".
I already pleayed around with Row_Number() and Level but I couldn't get a solution.
Any idea how to do it?
Analytic functions help.
SQL> with test (cgroup, name) as
2 (select 1, 'a' from dual union all
3 select 1, 'b' from dual union all
4 select 1, 'c' from dual union all
5 select 2, 'd' from dual union all
6 select 2, 'e' from dual union all
7 select 2, 'f' from dual
8 )
9 select cgroup,
10 name,
11 row_number() over (partition by cgroup order by name) sequence
12 from test
13 order by cgroup, name;
CGROUP N SEQUENCE
---------- - ----------
1 a 1
1 b 2
1 c 3
2 d 1
2 e 2
2 f 3
6 rows selected.
SQL>
Try this
SELECT
"Group",
Name,
DENSE_RANK() OVER (PARTITION BY "Group" ORDER BY Name) AS Sequence
FROM table;