Repeat rows dynamically in Oracle based on a condition - sql

Heres what i have
Query
with temp as (
select 11 as x from dual
union
select 222 as x from dual
)
select x from temp t,(SELECT 1
FROM DUAL
CONNECT BY ROWNUM <= 3)
Output
X
--
11
222
11
222
11
222
Output i desire
X
--
11
11
222
222
222
basically i would like the row to repeat itself based on the length of the column 'X' value.
so if value is 'abc'
then that row should repeat 3 times

This works:
with temp as (
select '11' as x from dual
union
select '222' as x from dual
)
SELECT x, LEVEL from temp t
CONNECT BY LEVEL <= LENGTH(t.x)
and prior x = x
and prior sys_guid() is not null;
Last line does the trick. Same can be achieved with other reference to PRIOR:
with temp as (
select '11' as x from dual
union
select '222' as x from dual
)
SELECT x, LEVEL from temp t
CONNECT BY LEVEL <= LENGTH(t.x)
and PRIOR x = x
and PRIOR DBMS_RANDOM.VALUE () IS NOT NULL;

Since you're on 11g you can use recursive subquery factoring to achieve this:
with t as (
select 11 as x from dual
union all
select 222 as x from dual
),
r (x, idx, len) as (
select x, 1, length(x)
from t
union all
select r.x, r.idx + 1, r.len
from r
where r.idx < r.len
)
select x from r
order by x;
X
-----
11
11
222
222
222
The anchor member gets the original rows and the length of the value.The recursive member adds one to idx until it reaches the length.
SQL Fiddle.
You can do it with a hierarchical query too:
with t as (
select 11 as x from dual
union all
select 222 as x from dual
)
select x
from t
connect by level <= length(x)
and prior x = x
and prior sys_guid() is not null;
The combination of the two prior clauses - one restricitng duplicates, the other involing a non-deterministic function to prevent cycling when you do that - gives you the desired rows:
X
-----
11
11
222
222
222
SQL Fiddle.

with temp as (
select 11 as x from dual
union
select 222 as x from dual
)
select x from temp t,(
SELECT 1
FROM DUAL
CONNECT BY ROWNUM <= 3)
order by 1

Related

Oracle SQL to Mimic Nested Window Function

My actual problem involves a larger row source and more involved math, but this is a small example that still exhibits the challenge faced. Using Oracle 19c.
Suppose we have a table X with four rows of data as follows.
x
-
1
2
3
4
Further, suppose we want to derive two columns, a and b, from X such that
a = x + sum(previous rows of b ordered by x)
b = a - 1.
Where if there are no previous rows, then the sum is 0.
Therefore, the new table would have rows like the following.
x a b
- - -
1 1 0
2 2 1
3 4 3
4 8 7
The following is invalid SQL, but provides an example of what is being attempted.
with
X AS
(
select 1 x from dual
union all select 2 from dual
union all select 3 from dual
union all select 4 from dual
)
, A AS
(
select
x
, x + sum(b) over (order by x range between unbounded preceding and 1 preceding) AS a
, a - 1 AS b
from x
)
select * from A
;
Perhaps a hierarchical query may help, but not sure on what it is to connect by.
Any ideas would be appreciated. Thanks in advance.
You can do this using a recursive CTE:
with X AS (
select 1 x from dual
union all select 2 from dual
union all select 3 from dual
union all select 4 from dual
),
cte(x, a, b, b_sum) as (
select x, x as a, x - 1 as b, x - 1 as b_sum
from x
where x = 1
union all
select x.x, x.x + cte.b_sum, x.x + cte.b_sum - 1, cte.b_sum + (x.x + cte.b_sum - 1)
from cte join
x
on x.x = cte.x + 1
)
select *
from cte;
Here is a db<>fiddle.

Gradually aggregating a string column in Oracle SQL

I would like to gradually aggregate a string column in Oracle sql.
From this table:
col_1 | col_2
-------------
1 | A
1 | B
1 | C
2 | C
2 | D
to:
col_1 | col_2
-------------
1 | A
1 | A,B
1 | A,B,C
2 | C
2 | C,D
I tried LISTAGG but it won't return all rows due to group by. I have about 2 million rows in the table.
Oracle doesn't support accumulating string concatenation with a single listagg() expression. However, you can use a subquery.
Just one note: SQL tables represent unordered sets. You seem to have an ordering in mind. The following code adds an ordering column:
with t as (
select 1 as id, 1 as x, 'A' as y from dual union all
select 2, 1 as x, 'B' as y from dual union all
select 3, 1 as x, 'C' as y from dual union all
select 4, 2 as x, 'C' as y from dual union all
select 5, 2 as x, 'D' as y from dual
)
select t.*,
(select listagg(t2.y, ',') within group (order by t2.id)
from t t2
where t2.x = t.x and t2.id <= t.id
)
from t;
A hierarchical query option looks like this:
SQL> with t as (
2 select 1 as id, 1 as x, 'A' as y from dual union all
3 select 2, 1 as x, 'B' as y from dual union all
4 select 3, 1 as x, 'C' as y from dual union all
5 select 4, 2 as x, 'C' as y from dual union all
6 select 5, 2 as x, 'D' as y from dual
7 )
8 select x,
9 ltrim(sys_connect_by_path(y, ','), ',') result
10 from (select x,
11 y,
12 row_number() over (partition by x order by y) rn
13 from t
14 )
15 start with rn = 1
16 connect by prior rn = rn - 1 and prior x = x;
X RESULT
---------- --------------------
1 A
1 A,B
1 A,B,C
2 C
2 C,D
SQL>

Oracle SQL intersection of 2 comma separated string

What can I apply as function?
Query:
Select x, f(y) from table where y like '%ab%cd%ef';
sample table(y is sorted alphabatically)
x. y
1 ab
2 ab,cd
3 cd,ef
4 ab,ef,gh,yu
5 de,ef,rt
Expected Output:
Output:
x y
1 ab
2 ab,cd
3 cd,ef
4 ab,ef
5 ef
Use regexp_substr function with connect by level expressions as
with tab(x,y) as
(
select 1,'ab' from dual union all
select 2,'ab,cd' from dual union all
select 3,'cd,ef' from dual union all
select 4,'ab,ef,gh,yu' from dual union all
select 5,'de,ef,rt' from dual
), tab2 as
(
Select x, regexp_substr(y,'[^,]+',1,level) as y
from tab
connect by level <= regexp_count(y,',') + 1
and prior x = x
and prior sys_guid() is not null
), tab3 as
(
select x, y
from tab2
where y like '%ab%'
or y like '%cd%'
or y like '%ef%'
)
select x, listagg(y,',') within group (order by y) as y
from tab3
group by x;
X Y
1 ab
2 ab,cd
3 cd,ef
4 ab,ef
5 ef
Demo
Follow comments written within the code.
SQL> with test (x, y) as
2 -- your sample table
3 (select 1, 'ab' from dual union all
4 select 2, 'ab,cd' from dual union all
5 select 3, 'cd,ef' from dual union all
6 select 4, 'ab,ef,gh,yu' from dual union all
7 select 5, 'de,ef,rt' from dual
8 ),
9 srch (val) as
10 -- a search string, which is to be compared to the sample table's Y column values
11 (select 'ab,cd,ef' from dual),
12 --
13 srch_rows as
14 -- split search string into rows
15 (select regexp_substr(val, '[^,]+', 1, level) val
16 from srch
17 connect by level <= regexp_count(val, ',') + 1
18 ),
19 test_rows as
20 -- split sample values into rows
21 (select x,
22 regexp_substr(y, '[^,]+', 1, column_value) y
23 from test,
24 table(cast(multiset(select level from dual
25 connect by level <= regexp_count(y, ',') + 1
26 ) as sys.odcinumberlist))
27 )
28 -- the final result
29 select t.x, listagg(t.y, ',') within group (order by t.y) result
30 from test_rows t join srch_rows s on s.val = t.y
31 group by t.x
32 order by t.x;
X RESULT
---------- --------------------
1 ab
2 ab,cd
3 cd,ef
4 ab,ef
5 ef
SQL>

How can I find unoccupied id numbers in a table?

In my table I want to see a list of unoccupied id numbers in a certain range.
For example there are 10 records in my table with id's: "2,3,4,5,10,12,16,18,21,22" and say that I want to see available ones between 1 and 25. So I want to see a list like:
1,6,7,89,11,13,14,15,17,19,20,23,24,25
How should I write my sql query?
Select the numbers form 1 to 25 and show only those that are not in your table
select n from
( select rownum n from dual connect by level <= 25)
where n not in (select id from table);
Let's say you a #numbers table with three numbers -
CREATE TABLE #numbers (num INT)
INSERT INTO #numbers (num)
SELECT 1
UNION
SELECT 3
UNION
SELECT 6
Now, you can use CTE to generate numbers recursively from 1-25 and deselect those which are in your #numbers table in the WHERE clause -
;WITH n(n) AS
(
SELECT 1
UNION ALL
SELECT n+1 FROM n WHERE n < 25
)
SELECT n FROM n
WHERE n NOT IN (select num from #numbers)
ORDER BY n
OPTION (MAXRECURSION 25);
You can try using the "NOT IN" clause:
select
u1.user_id + 1 as start
from users as u1
left outer join users as u2 on u1.user_id + 1 = u2.id
where
u2.id is null
see also SQL query to find Missing sequence numbers
You need LISTAGG to get the output in a single row.
SQL> WITH DATA1 AS(
2 SELECT LEVEL rn FROM dual CONNECT BY LEVEL <=25
3 ),
4 data2 AS(
5 SELECT 2 num FROM dual UNION ALL
6 SELECT 3 FROM dual UNION ALL
7 SELECT 4 from dual union all
8 SELECT 5 FROM dual UNION ALL
9 SELECT 10 FROM dual UNION ALL
10 SELECT 12 from dual union all
11 SELECT 16 from dual union all
12 SELECT 18 FROM dual UNION ALL
13 SELECT 21 FROM dual UNION ALL
14 SELECT 22 FROM dual)
15 SELECT listagg(rn, ',')
16 WITHIN GROUP (ORDER BY rn) num_list FROM data1
17 WHERE rn NOT IN(SELECT num FROM data2)
18 /
NUM_LIST
----------------------------------------------------
1,6,7,8,9,11,13,14,15,17,19,20,23,24,25
SQL>

get the nearest highest value from a list oracle sql

I have a column in the database in the following format: yymmddhh24miss
Sample Data:
140203101241
140202101141
140102101240
143001101244
142801101245
142701131347
142601121542
142101131744
...
I need to get the nearest high value from the list. Ex: If I pass 142701131333, then it should return 142701131347 from the above list.
Any help appreciated!
SELECT data
FROM
(
SELECT data
FROM tbl
WHERE data > '142701131333'
ORDER BY data
) a
WHERE rownum = 1
SQL> with t (x) as (
2 select 140203101241 from dual union all
3 select 140202101141 from dual union all
4 select 140102101240 from dual union all
5 select 143001101244 from dual union all
6 select 142801101245 from dual union all
7 select 142701131347 from dual union all
8 select 142601121542 from dual union all
9 select 142101131744 from dual
10 )
11 select min(x) minx from t where x > 142701131333
12 /
MINX
-----------------
142701131347
SELECT MIN(sample_data)
FROM tableName
WHERE sample_data > 142701131333