Connect by in cross apply - sql

I have the below code:
SELECT
m, l
FROM
(
SELECT
2 * level + 3 l
FROM
dual
CONNECT BY
2 * level + 3 <= 1000
) CROSS APPLY (
SELECT
2 * level + 1 m
FROM
dual
CONNECT BY
power(2 * level + 1, 2) <= l
) order by m, l
However, the first results shown are the below. Why is the 2nd power of m connecting with l in that case if power(3, 2) is not <= 5 (or 7)?
m l
3 5
3 7

Because CONNECT BY checks whether the query will recurse after the first row is generated so you will always get at least one row (where LEVEL is 1); if you want to limit the number of rows then you need a START WITH or WHERE clause.
You can see the difference if you also include either clause and OUTER APPLY to return the l rows when m then has no generated rows (and would be NULL):
SELECT m, l
FROM ( SELECT 2 * level + 3 AS l
FROM dual
CONNECT BY 2 * level + 3 <= 25 )
OUTER APPLY
( SELECT 2 * level + 1 AS m
FROM dual
START WITH 9 <= l
CONNECT BY power(2 * level + 1, 2) <= l )
order by l, m
or
SELECT m, l
FROM ( SELECT 2 * level + 3 AS l
FROM dual
CONNECT BY 2 * level + 3 <= 25 )
OUTER APPLY
( SELECT 2 * level + 1 AS m
FROM dual
WHERE power(2 * level + 1, 2) <= l
CONNECT BY power(2 * level + 1, 2) <= l )
order by l, m
or
Outputs:
M
L
5
7
3
9
3
11
3
13
3
15
3
17
3
19
3
21
3
23
3
25
5
25
db<>fiddle here

Related

Oracle SQL display distinct none "standard alphanumeric caracters

I need to find the way to list all the characters used in the column in order to narrow down the "Approved" values within the insert template we are creating...
the idea is to allow all letters (only standard) without any dialect / country specific ones.
trying something like this... but need to have a list of the characters left over... like "$%()* etc.
SELECT * FROM mytable WHERE REGEXP_LIKE(column_1,^[a-zA-Z0-9-]+$)
To find the other characters, you could remove the ones you do expect and then see what is left:
SELECT REGEXP_REPLACE( column1, '[a-zA-Z0-9-]' ) AS other_characters
FROM mytable
WHERE REGEXP_REPLACE( column1, '[a-zA-Z0-9-]' ) IS NOT NULL
If you want to concatenate and remove duplicate characters:
WITH replace_expected ( str ) AS (
SELECT REGEXP_REPLACE( column1, '[a-zA-Z0-9-]' )
FROM mytable
WHERE REGEXP_REPLACE( column1, '[a-zA-Z0-9-]' ) IS NOT NULL
),
split_strings ( str, pos, ch ) AS (
SELECT str, 1, SUBSTR(str, 1, 1)
FROM replace_expected
UNION ALL
SELECT str, pos + 1, SUBSTR(str, pos + 1, 1)
FROM split_strings
WHERE pos < LENGTH(str)
)
SELECT LISTAGG(DISTINCT ch) WITHIN GROUP (ORDER BY ch) AS other_characters
FROM split_strings;
fiddle
Two steps:
Extract the special characters from all strings in the column and concatenate them into one long string (with possibly many characters appearing multifold in the string).
Use a recursive query to loop through the characters and return a string with distinct characters.
The query:
with one_row (str) as
(
select listagg(regexp_replace(column_1, '[a-zA-Z0-9\-]'))
from mytable
where regexp_like(column_1, '[^a-zA-Z0-9\-]')
)
select listagg(distinct substr(str, level, 1)) as c
from one_row
connect by level <= length(str);
create table dummy_data as
select rownum as id, dbms_random.string(opt, len) str
from (
select case round(dbms_random.value(0, 3))
when 0 then 'a'
when 1 then 'x'
else 'p'
end opt,
round(dbms_random.value(5, 60)) len
from dual connect by level <= 30 );
SELECT * FROM DUMMY_DATA;
ID STR
1 UMUUJ0R5VM1T3X10TDCNIWC3MQ5ELOB041YMNEJSLT
2 _t8 }LeZhjiMB"8/a'/~a
3 BLSE6XX6SL3M7W0DG3HH28SCHPSAT11ZH2E5DOSKEV3KW9
4 1]Mh58(l<Wa}{
5 :_QiWUkwp}V$}O
6 NC911A4SRN35CNXT2EU5H2GZ67IQQLKH
7 e"8,z$=Yvy5egvEH2KUkNoVjkitd9IMm0ZktsB i(bk4uU]c3;E
8 MgbpIsLZpWEcAghOUKOISA
9 7H02ASKO3CZRN4D5FUNPEU6YUZD
10 KbJ+QrI\l.th%>^f!Io%wshsVA%
11 PO9A47VU7AXI17XYD5VMSWW8E
12 1ILWL4V
13 FgubwibYBytNvmJHxUfG
14 ?[ngH?0!k.onN>mF(nrkO
15 86G0HP3
16 WXDBV3OBMVSDKQ59YT73G0II3U94
17 GP375CFIQPPN6216I5A7L54O
18 i\L<K,"d'ye 6s~_MB0O1 aC$q;T"EaqpZ^s\gIiYu&:%OnhVj]<a]CmOgqM
19 WxUEtr\II(97i7PQ-Z]yqd#&`#CQB0M"c0;{.by9qo#HT
20 IF5OP7KS9AXW91
21 HNcKwxXozXjTVwKeFZDLdmNOzFKKKq
22 4D8CINXIVT244RDDRZ5TSDQ4CRF4
23 3)oxevW-(~=+#cP[^g)##|1.TL-_N9O-Zdgj"cwJC'*NR; FtK)K
24 AndzeLIEPklDuTWWEBrKrdNKdXwMGeLauJkRzKpKHEGAsxlEXliwBTHdK
25 dlEX1tGFuU5\5+{5`R
26 /W0.{B&)ax&lWEE#OSw
27 CBKOVLKDFKC3EVR
28 :V#Lc.Z"8[O-)cAWUpMjc?j\Kj?xV#%`Yp [VkEV1
29 P9P047
30 W)S<fB`F;N_brMP
with
h (ok_chars) as (
select '0123456789' || 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' ||
'abcdefghijklmnopqrstuvwxyz'
from dual
)
select c, count(*) as cnt
from dummy_data cross join lateral (
select substr(str, level, 1) as c
from h
where instr(ok_chars, substr(str, level, 1)) = 0
connect by level <= length(str)
)
group by c;
C CNT
} 4
- 5
' 3
< 4
, 2
= 2
_ 5
( 5
^ 3
[ 4
" 7
/ 3
: 3
. 6
% 5
! 2
? 4
` 4
8
; 5
+ 3
> 2
) 6
| 1
* 1
~ 3
\ 6
& 4
# 5
] 5
{ 4
# 4
$ 3

Generate series of integers with random start and end SQL Oracle

I am trying to generate a series of integers that go from a random start-point until a random (greater) end-point. Furthermore, I would like to do this as a window function, so I could use this in a OVER (PARTITION BY ) statement.
Basically I'm trying to select random hours (from 1-24), but in a random way and also consecutively, and do this for each client (which is why I suggest an OVER (PARTITION BY client) statement, but I'm open for other ideas.
I am trying to use:
SELECT
T1.HOURS
FROM (
SELECT
LEVEL HOURS
FROM DUAL
CONNECT BY
LEVEL <= 24
) T1,
(
SELECT
INIT,
LEAST(INIT + LENGTH, 24) FIN
FROM (
SELECT
ROUND(DBMS_RANDOM.VALUE(1, 24)) INIT,
ROUND(DBMS_RANDOM.VALUE(1, 24)) LENGTH
FROM DUAL
) T0
) T2
WHERE
T1.HOURS >= T2.INIT AND
T1.HOURS <= T2.FIN;
But the result is unfortunately non-consecutive orders.
|hours|
|-----|
|17|
|18|
|20|
|24|
The code is simply nor working because the table T0 (which is the one that filters the initial INIT and final FIN value) is being replicated for each row.
This is a desired result:
client
hours
1
4
1
5
1
6
1
7
2
14
2
15
3
13
3
14
3
15
3
16
3
17
3
18
3
19
3
20
3
21
In the desired result a consecutive list is selected for each client, with a random start and end point.
In Oracle, you can use a correlated LATERAL join:
SELECT c.id,
h.hours
FROM ( SELECT id,
FLOOR(DBMS_RANDOM.VALUE(1, 25)) AS bound1,
FLOOR(DBMS_RANDOM.VALUE(1, 25)) AS bound2
FROM clients
) c
CROSS JOIN LATERAL (
SELECT LEAST(bound1, bound2) + LEVEL - 1 AS hours
FROM DUAL
CONNECT BY LEVEL <= ABS(bound1 - bound2) + 1
) h;
Then, for the sample data:
CREATE TABLE clients (id) AS
SELECT LEVEL FROM DUAL CONNECT BY LEVEL <= 3;
May (randomly) output:
ID
HOURS
1
7
1
8
1
9
1
10
2
19
2
20
2
21
2
22
2
23
2
24
3
6
3
7
3
8
3
9
3
10
3
11
3
12
3
13
3
14
3
15
3
16
fiddle

Struggle to understand SQL query

I'm struggling to understand what the below 2 chunks of code do. Some comments would be really appreciated.
Chunk 1:
SELECT
l
FROM
(
SELECT
level l
FROM
dual
CONNECT BY
level <= 1000
),
(
SELECT
level m
FROM
dual
CONNECT BY
level <= 1000
)
Chunk 2:
HAVING
COUNT(
CASE l / m
WHEN trunc(l / m) THEN
'Y'
END
) = 2
PD: I will post below the entire SQL query to provide more background. The query just gets the prime numbers 1 to 1000 and listaggs them with ampersands.
SELECT
LISTAGG(l, '&') WITHIN GROUP(
ORDER BY
l
)
FROM
(
SELECT
l
FROM
(
SELECT
level l
FROM
dual
CONNECT BY
level <= 1000
),
(
SELECT
level m
FROM
dual
CONNECT BY
level <= 1000
)
WHERE
m <= l
GROUP BY
l
HAVING
COUNT(
CASE l / m
WHEN trunc(l / m) THEN
'Y'
END
) = 2
ORDER BY
l
);
[TL;DR] It is a prime number generator.
SELECT level l FROM dual CONNECT BY level <= 1000
Generates 1000 rows containing incrementally increasing numbers from 1 to 1000 and aliases the column to l.
SELECT level m FROM dual CONNECT BY level <= 1000
Does exactly the same thing but aliases the column to m.
SELECT l
FROM ( SELECT level l FROM dual CONNECT BY level <= 1000 ),
( SELECT level m FROM dual CONNECT BY level <= 1000 )
Applies a CROSS JOIN (using the legacy comma-join syntax) between the two tables and returns only the l column; so you will have 1000 copies of each of the numbers from 1 to 1000 (for a total of 1000000 rows)... or would do if there wasn't the filter condition:
WHERE m <= l
Which filters those rows so you don't get 1000 rows of each number but you will get 1 row with the l value of 1, 2 rows with the l value of 2 (with m values of 1 and 2), 3 rows with the value of 3 (with m values of 1, 2, and 3), ... etc. up to 1000 rows with the value of 1000 (with m values of 1, 2, 3, ..., 999, 1000).
GROUP BY l
HAVING COUNT( CASE l / m WHEN trunc(l / m) THEN 'Y' END ) = 2
Counts the number of factors of l (where the value m divides exactly into l) and filters to only have those rows where there are exactly two factors. Those factors must be the values when m equals 1 and l; which means that there are no other factors of l between the values of 1 and l and so l is a prime number.
ORDER BY l
Then orders the values in ascending order.
A more efficient generator would be:
SELECT 2 AS l FROM DUAL UNION ALL
SELECT 3 AS l FROM DUAL UNION ALL
SELECT l
FROM ( SELECT 2 * level + 3 l FROM dual CONNECT BY 2 * level + 3 <= 1000 )
CROSS APPLY
( SELECT 2 * level + 1 m FROM dual CONNECT BY POWER(2 * level + 1, 2) <= l )
GROUP BY l
HAVING COUNT( CASE l / m WHEN trunc(l / m) THEN 1 END ) = 0
ORDER BY l
Which correlates the row generators to only generate odd numbers and so that the second generator stops generating at the square root of the value of the first generator.

Oracle - theoretical sql query for create intervals

Is it possible to solve this situation by sql query in ORACLE?
I have a table like this:
TYPE UNIT
A 230
B 225
C 60
D 45
E 5
F 2
I need to separate units to the three(variable) 'same'(equally sized) intervals and foreach figure out the count? It means something like this:
0 - 77 -> 4
78 - 154 -> 0
155 - 230 -> 2
You can use the maximum value and a connect-by query to generate the upper and lower values for each range:
select ceil((level - 1) * int) as int_from,
floor(level * int) - 1 as int_to
from (select round(max(unit) / 3) as int from t42)
connect by level <= 3;
INT_FROM INT_TO
---------- ----------
0 76
77 153
154 230
And then do a left outer join to your original table to do the count for each range, so you get the zero value for the middle range:
with intervals as (
select ceil((level - 1) * int) as int_from,
floor(level * int) - 1 as int_to
from (select round(max(unit) / 3) as int from t42)
connect by level <= 3
)
select i.int_from || '-' || i.int_to as range,
count(t.unit)
from intervals i
left join t42 t
on t.unit between i.int_from and i.int_to
group by i.int_from, i.int_to
order by i.int_from;
RANGE COUNT(T.UNIT)
---------- -------------
0-76 4
77-153 0
154-230 2
Yes, this can be done in Oracle. The hard part is the definition of the bounds. You can use the maximum value and some arithmetic on a sequence with values of 1, 2, and 3.
After that, the rest is just a cross join and aggregation:
with bounds as (
select (case when n = 1 then 0
when n = 2 then trunc(maxu / 3)
else trunc(2 * maxu / 3)
end) as lowerbound,
(case when n = 1 then trunc(maxu / 3)
when n = 2 then trunc(2*maxu / 3)
else maxu
end) as upperbound
from (select 1 as n from dual union all select 2 from dual union all select 3 from dual
) n cross join
(select max(unit) as maxu from atable t)
)
select b.lowerbound || '-' || b.upperbound,
sum(case when units between b.lowerbound and b.upperbound then 1 else 0 end)
from atable t cross join
bounds b
group by b.lowerbound || '-' || b.upperbound;

Oracle Regular Expression To replace A with Z, B with Y, C with X and So on

I want a regular Expression which replaces a string with its respective character in descending order including number:
i.e.
replace a alphanumeric string with descending order string
if special characters are found in sting keep the as it is
I means :
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
Z Y X W V U T S R Q P O N M L K J I H G F E D C B A
0 1 2 3 4 5 6 7 8 9
9 8 7 6 5 4 3 2 1 0
Few examples:
1) ABC*1230 -> ZYX*8739
2) Hello World! -> Svool Dliow!
3) Good & Bad -> Tllw & Yzw
I'd use the Oracle TRANSLATE function for this:
TRANSLATE(myValue,
'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
'ZYXWVUTSRQPONMLKJIHGFEDCBA9876543210')
Here it is done with REGEXP:
SELECT LISTAGG(DECODE(o_str_level,NULL,srch_l.rs,o_str_level)) WITHIN GROUP (ORDER BY srch_l.ri) AS the_result
FROM
(
SELECT REGEXP_SUBSTR(t_str, '.', 1, LEVEL) rs
, REGEXP_INSTR (t_str, '.', 1, LEVEL) ri
FROM
(
SELECT 'ABCDEF TUVWXYZ' AS t_str FROM DUAL
) test_str
CONNECT BY LEVEL <= LENGTH(t_str)
) srch_l
LEFT JOIN
(
SELECT REGEXP_SUBSTR(r_str, '\w', 1, LEVEL) AS r_str_level
, REGEXP_SUBSTR(o_str, '\w', 1, LEVEL) AS o_str_level
FROM
(
SELECT 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' r_str, 'ZYXWVUTSRQPONMLKJIHGFEDCBA' o_str FROM DUAL
)
CONNECT BY LEVEL <= 26
) apt
ON srch_l.rs = apt.r_str_level;
-- Result:
-- ZYXWVU GFEDCBA
Is it simpler than TRANSLATE? Almost...