Max size in a connected by prior Oracle - oracle

I've got some help turning my table of the sort:
Col
23
25
15
53
...
into something like 23,25,15,53...
The query that does it is
SELECT max(ltrim(sys_connect_by_path(flow_run_id, ','), ','))
FROM
(select flow_run_id, rownum rn
from table
where CREATED_DATE < sysdate - 32
and flow_id = 3
order by 1 desc)
START WITH rn = 1
CONNECT BY PRIOR rn = rn - 1
(this beaulty was given by Michael in here)
My current problem is that the result is too long (ORA-01489 over the 4k chars from varchar2). I'm still learning about these sys_connected_by_path so I'd need some help sorting this. How could I make this query return me multiple rows instead of one super long line? i.e.:
Instead of
419,1,2,3,411,418,4,415,887,413,414,201,888,890,401,417,610,412,416,5,6,922,1080,1422,1423,1411,1412,1413,1414,1415,1416,1417,1418,1419,1964,2217,1636,2037,1988,1970,2038,1989,2000,2040,1993,2043,1994,2001,2044,1658,1995,2045,2224,1996,2019,1678,1997,2022,2201,1680,2219,2024,2207,1677,2209,2220,1959,2211,1961,2026,2212,1962,2028,2215,1675,1676,2035,2216,1986,1963,2017,1983,1935,2002,2018,1985,1936,2003,2020,2032,1937,2004,2021,2033,1938,1943,2023,2034,1939,1944,2025,2225,1941,1950,2027,2036,1942,1955,2029,2041,1945,1956,2030,2227,1946,1957,2031,2039,1947,2005,1974,2042,1948,2006,1976,2228,1949,2007,1978,1951,2009,1979,1929,1952,2012,1980,1931,1953,2013,1981,1933,1954,2015,2334,2350,2311,2239,2240,2241,2242,2245,2246,2249,2250,2336,2312,2008,2010,2011,2014,2251,2253,2016,2243,2244,2247,2351,2248,(...)
get
419,1,2,3,411,418,4,415,887,413,414,201,888,890,401,417,610,412,416,5,6,922,1080
1423,1411,1412,1413,1414,1415,1416,1417,1418,1419,1964,2217,1636,2037,1988,1970,2038
2000,2040,1993,2043,1994,2001,2044,1658,1995,2045,2224,1996,2019,1678,1997,2022,2201
(...)
Any tips?
Thanks!
f.

the following query will cut your big string in parts:
SQL> SELECT root_rn, MAX(concat)
2 FROM (SELECT connect_by_root(rn) root_rn,
3 ltrim(sys_connect_by_path(flow_run_id, ','), ',') concat
4 FROM (SELECT flow_run_id, rownum rn
5 FROM (SELECT round(dbms_random.VALUE(1, 10000))
6 AS flow_run_id
7 FROM dual
8 CONNECT BY ROWNUM <= 2000)
9 ORDER BY 1 DESC)
10 START WITH MOD(rn, 10) = 1
11 CONNECT BY PRIOR rn = rn - 1
12 AND MOD(rn, 10) != 1)
13 GROUP BY root_rn
14 ORDER BY root_rn;
ROOT_RN MAX(CONCAT)
---------- -------------------------------------------------------------------
1 654,6710,5297,5481,5085,2793,7646,9170,1051,2387
11 1882,8285,5430,4928,267,3779,3843,1151,3085,1446
21 4721,6087,6755,9904,805,2776,4633,2772,7785,5818
31 5189,5307,6481,2099,3832,9788,5970,8068,6605,3904
41 53,7013,1314,7717,9320,7069,907,5367,5013,7637
51 3903,2318,2611,7954,5751,5598,6148,6555,9724,984
[...]
You can replace "10" with a bigger number if you want more elements on each row.

Some little modifications to keep order
SELECT 10*frn+1 root,ltrim(sys_connect_by_path(flow_run_id,','),',') FROM
(SELECT flow_run_id,mod(rn,10) mrn,floor(rn/10) frn,count(*)over(partition by floor(rn/10))-1 crn FROM
(SELECT flow_run_id, row_number()over(order by flow_run_id)-1 rn FROM
(SELECT round(dbms_random.VALUE(1, 10000)) AS flow_run_id FROM dual CONNECT BY ROWNUM <= 2000
)
)
)
WHERE crn = mrn
START WITH mrn = 0
CONNECT BY PRIOR mrn = mrn-1 AND PRIOR frn = frn

Related

Oracle Subquery while using count and max with join

Table COMPUTER:
Table SUPPLIER:
how to display the building location that has the most computers?
i Have been trying several ways include subquery, joins, max, count but all not working and error keeps happending
The result i pursueing is
SUPPID SNAME SADDRESS MAKE COUNT(*)
125 Apple Sdn.Bhd 18 Jalan Duta Apple 3
For example (where sample data is in lines #1 - 12; query you might be interested in begins at line #13):
SQL> with
2 -- sample data
3 computer (compid, make, suppid, locid) as
4 (select 13323, 'IBM' , 124, 333 from dual union all
5 select 13324, 'Apple', 125, 444 from dual union all
6 select 13325, 'Apple', 125, 444 from dual union all
7 select 13326, 'Apple', 125, 444 from dual
8 ),
9 supplier (suppid, sname, saddress) as
10 (select 124, 'IBM Sdn.Bhd' , '15 Jalan Duta' from dual union all
11 select 125, 'Apple Sdn.Bhd', '18 Jalan Duta' from dual
12 ),
13 comp_loc as
14 -- number of computers per location; RNK = 1 shows location with most computers
15 (select locid,
16 rank() over (order by count(*) desc) rnk,
17 count(*) cnt
18 from computer
19 group by locid
20 )
21 select distinct s.suppid, s.sname, s.saddress, c.make, l.cnt
22 from supplier s join computer c on c.suppid = s.suppid
23 join comp_loc l on l.locid = c.locid
24 where l.rnk = 1;
SUPPID SNAME SADDRESS MAKE CNT
---------- ------------- ------------- ----- ----------
125 Apple Sdn.Bhd 18 Jalan Duta Apple 3
SQL>
On Oracle 12 and newer
select s.suppid, s.sname, s.saddress, c.make, count(1)
from COMPUTER c
join SUPPLIER s
on c.suppid = s.suppid
group by s.suppid, s.sname, s.saddress, c.make
order by count(1) desc
fetch first 1 row only <-- this line will fetch you the top 1 line only
You might also use "fetch first 1 row with ties" to output all the top manufacturers if there are many of them having same "count". E.g If IBM and Appl were having same amount of lines
On Oracle version before 12 do the following:
select *
from (select s.suppid, s.sname, s.saddress, c.make, count(1)
from comps c
join suppls s
on c.suppid = s.suppid
group by s.suppid, s.sname, s.saddress, c.make
order by count(1) desc)
where rownum = 1; <-- this line will get you the top 1 manufacturer only
PS. version of the oracle database can be obtained for example using:
select version from v$instance;

count returns <<air>> when there's nothing to group by

Here's my SQL:
select
count(*)
from
sysdba.dw_cmap_arf_tmp
left join SYSDBA.TABLE1 rrc
on rrc.part_work_order = pwo
left join sysdba.TABLE2 R
on rrc.run_number = R.RUN_NUMBER
where
upper(run_type) like '%FEE%'
group by
pwo;
When there's nothing to group by, count returns --air--. It's null nor is it blank. I have modified the above SQL as the following to prove.
select
'>>' || count(*) || '<<' as blah
from
sysdba.dw_cmap_arf_tmp
left join SYSDBA.TABLE1 rrc
on rrc.part_work_order = pwo
left join sysdba.TABLE2 R
on rrc.run_number = R.RUN_NUMBER
where
upper(run_type) like '%ANNEAL%'
group by
pwo;
When I, however, write a statement above to perform an update, I got null. So tried coalesce, but got the same thing.
Does anyone know what I can do to replace --air-- with null or 0? Thanks!
P.S. I have did something research, but couldn't find anything...apologize in advance, if there's already a similar question out there.
Thanks!
This is what you currently have (based on Scott's EMP table): as there's no department 50, you get no rows selected (which is the air you're talking about, I presume).
SQL> with your_current_query as
2 (select count(*) cnt
3 from emp
4 where deptno = &deptno
5 group by job
6 )
7 select cnt
8 from your_current_query;
Enter value for deptno: 50
no rows selected
Just to show that it actually returns something if there are some data there:
SQL> /
Enter value for deptno: 30
CNT
----------
4
1
1
SQL>
OK; now, to do something with the situation where there are no rows selected, use union with a "dummy" row selected from the DUAL table:
SQL> with your_current_query as
2 (select count(*) cnt
3 from emp
4 where deptno = &deptno
5 group by job
6 )
7 select cnt
8 from your_current_query
9 -- add this: if YOUR_CURRENT_QUERY doesn't return anything, union it with
10 -- a select from dual
11 union all
12 select 0
13 from dual
14 where 0 = (select count(*) from your_current_query);
Enter value for deptno: 50
CNT
----------
0
SQL>
So: even though there are no employees in department 50, you got 0 as the result.
Again, to show what happens when there are some rows:
SQL> /
Enter value for deptno: 30
CNT
----------
4
1
1
SQL>
Finally, your query - rewritten - would look like this:
with your_current_query as
(select
count(*) cnt
from
sysdba.dw_cmap_arf_tmp
left join SYSDBA.TABLE1 rrc
on rrc.part_work_order = pwo
left join sysdba.TABLE2 R
on rrc.run_number = R.RUN_NUMBER
where
upper(run_type) like '%FEE%'
group by
pwo
)
select cnt from your_current_query
union all
select 0
from dual
where 0 = (select count(*) from your_Current_query);
See if it helps.

get particular day between a data range from giving table

I read many articles which introduce the using of connect by, but all of them get the particular days from a giving parameter(almost the begin date and end date)
What I want to know is how could I get split the rows from a certain table?
Example
Table T1
StartDate EndDate T_ID
2017-06-01 2017-06-15 01
2017-06-05 2017-06-06 02
The result I want is
TargetDate T_ID
2017-06-01 01
2017-06-02 01
2017-06-03 01
2017-06-04 01
2017-06-05 01
.
.
.
.
2017-06-15 01
2017-06-06 01
2017-06-06 02
I tried
SELECT T_ID, T1.StartDate+ LEVEL - 1 DD, LEVEL
FROM T1
WHERE T1.T_ID in = '01'
CONNECT BY LEVEL <= (TO_DATE(TRUNC(T1.EndDate)) - T1.StartDate + 1 ) ;
Waiting for your solution. Thanks.
Test Data:
CREATE TABLE t1 ( t_id, startdate, enddate ) AS
SELECT 1, DATE '2017-06-01', DATE '2017-06-15' FROM DUAL UNION ALL
SELECT 2, DATE '2017-06-05', DATE '2017-06-06' FROM DUAL;
Query:
SELECT T_ID,
COLUMN_VALUE AS dt,
ROW_NUMBER() OVER ( PARTITION BY t1.ROWID
ORDER BY Column_value ) AS lvl
FROM T1
CROSS JOIN
TABLE(
CAST(
MUTLTSET(
SELECT t1.Startdate + LEVEL - 1
FROM DUAL
CONNECT BY t1.Startdate + LEVEL - 1 <= t1.EndDate
) AS SYS.ODCIDATELIST
)
);
Output:
T_ID DT LVL
---- ---------- ---
1 2017-06-01 1
1 2017-06-02 2
1 2017-06-03 3
1 2017-06-04 4
1 2017-06-05 5
1 2017-06-06 6
1 2017-06-07 7
1 2017-06-08 8
1 2017-06-09 9
1 2017-06-10 10
1 2017-06-11 11
1 2017-06-12 12
1 2017-06-13 13
1 2017-06-14 14
1 2017-06-15 15
2 2017-06-05 1
2 2017-06-06 2
Here is the query in standard SQL (with a recursive cte) which also works in Oracle:
with all_dates(targetdate, t_id, enddate) as
(
select startdate as targetdate, t_id, enddate from t1
union all
select targetdate + 1, t_id, enddate from all_dates where targetdate < enddate
)
select targetdate, t_id
from all_dates
order by t_id, targetdate;
SELECT DISTINCT T_ID
, T1.StartDate+ LEVEL - 1 DD
, LEVEL
FROM T1
WHERE T1.T_ID IN( 1,2)
CONNECT BY LEVEL <= T1.EndDate - T1.StartDate + 1
But I'm not sure about performances (At moment I didn't find a way to limit without DISTINCT but using CONNECT BY clauses).
As an alternative you can use a CTE like this (you can remove RN column, I left it as a check):
with all_dates(targetdate, t_id, enddate, RN) as
(
select startdate as targetdate, t_id, enddate, 1 AS RN from t1
union all
select T1.startdate + all_dates.RN, T1.t_id, T1.enddate, all_dates.RN+1 AS RN
from t1
inner JOIN all_dates ON T1.startdate+all_dates.RN<=all_dates.enddate
AND T1.T_ID = all_dates.T_ID
)
select targetdate, t_id , RN
from all_dates
order by t_id, targetdate;
Sample data:
CREATE TABLE T1 (StartDate DATE, EndDate DATE, T_ID NUMBER(10,0));
INSERT INTO T1 VALUES ('20170601','20170615', 1);
INSERT INTO T1 VALUES ('20170605','20170606', 2);
INSERT INTO T1 VALUES ('20170701','20170703', 3);
Output:
20170601 1 1
20170602 1 2
20170603 1 3
20170604 1 4
20170605 1 5
20170606 1 6
20170607 1 7
20170608 1 8
20170609 1 9
20170610 1 10
20170611 1 11
20170612 1 12
20170613 1 13
20170614 1 14
20170615 1 15
20170605 2 1
20170606 2 2
20170701 3 1
20170702 3 2
20170703 3 3
If you're wanting to use connect by to achieve this, you will need to add a couple of additional clauses in order to get it to work with multiple rows:
WITH t1 AS (SELECT to_date('01/06/2017', 'dd/mm/yyyy') startdate, to_date('15/06/2017', 'dd/mm/yyyy') enddate, 1 t_id FROM dual UNION ALL
SELECT to_date('05/06/2017', 'dd/mm/yyyy') startdate, to_date('06/06/2017', 'dd/mm/yyyy') enddate, 2 t_id FROM dual)
SELECT t_id,
startdate + LEVEL -1 dd
FROM t1
CONNECT BY LEVEL <= enddate - startdate + 1
AND PRIOR t_id = t_id
AND PRIOR sys_guid() IS NOT NULL
ORDER BY t_id, dd;
T_ID DD
---------- -----------
1 01/06/2017
1 02/06/2017
1 03/06/2017
1 04/06/2017
1 05/06/2017
1 06/06/2017
1 07/06/2017
1 08/06/2017
1 09/06/2017
1 10/06/2017
1 11/06/2017
1 12/06/2017
1 13/06/2017
1 14/06/2017
1 15/06/2017
2 05/06/2017
2 06/06/2017

How to combine 2 select statements in oracle

i have table test2.it contains
ID
1
4
5
10
now i found missing numbers in this sequence.with this query
SELECT min_ID - 1 + level mn FROM
( SELECT MIN(ID) min_ID , MAX(ID) max_ID FROM test2 )
CONNECT BY level <= max_ID - min_ID + 1 minus SELECT ID FROM test2
output is:
MN
---
2
3
6
7
8
9
now i want to combine these 2 columns.I am unable to do this please help me.
i want output like
1 2
4 3
7 5
10 6
8
9
Oracle Setup:
CREATE TABLE test2 (id) AS
SELECT 1 FROM DUAL UNION ALL
SELECT 4 FROM DUAL UNION ALL
SELECT 5 FROM DUAL UNION ALL
SELECT 10 FROM DUAL;
Query:
WITH bounds ( mn, mx ) AS (
SELECT MIN( id ), MAX( id ) FROM test2
),
missing (id, rn) AS (
SELECT id, ROWNUM
FROM (
SELECT mn + LEVEL AS id
FROM bounds
CONNECT BY LEVEL < MX - MN
MINUS
SELECT id
FROM test2
)
),
existing ( id, rn ) AS (
SELECT id, ROWNUM
FROM test2
)
SELECT e.id, m.id
FROM existing e
FULL OUTER JOIN
missing m
ON ( e.rn = m.rn );
Output
ID ID
---------- ----------
1 2
4 3
5 6
10 7
9
8

3rd highest salary in oracle

I had been looking for the query to find the 3rd highest salary from the database (using Oracle database). I found the below query -
SELECT *
FROM
( SELECT e.*, row_number() over (order by sal DESC) rn FROM emp e
)
WHERE rn = 3;
I do not have oracle installed in my system, so I'm not try it out. But I want to know if the below query will work or not. If not, then why ?
WITH Sal_sort AS
(SELECT DISTINCT sal FROM salary ORDER BY sal DESC
)
SELECT * FROM Salary S, Sal_sort SS WHERE S.Sal = SS.Sal AND SS.rownum = 3;
Input Data
emp_no emp_fname emp_lname salary
1 aa bb 30
2 ee yy 31
3 rr uu 32
4 tt ii 33
5 tt ii 33
6 tt ii 33
7 tt ii 33
8 tt ii 30
9 tt ii 31
Example:
select * from ee;
select emp_no,salary ,dense_rank() over (order by salary ) dr
from ee
Output
emp_no salary dr
1 30 1
8 30 1
9 31 2
2 31 2
3 32 3
4 33 4
5 33 4
6 33 4
7 33 4
So much easier in version 12 of the database and higher now.
SELECT *
FROM employees
ORDER BY salary DESC OFFSET 2 ROWS FETCH NEXT 1 ROWS ONLY
Tim talks about this feature here.
And if you take a look at the plan, you can see it's not magic, the optimizer is using analytic functions to derive the results.
JUST ONE LINE
select * from (
select salary,dense_rank() over (order by salary desc) rank from employees) where rank=3;
or
select * from (
select a.*,dense_rank() over (order by a.salary desc) rank from employees a) where rank=3;
Without Dense_rank()
SELECT salary FROM employees
ORDER BY salary DESC
OFFSET 2
FETCH 1 NEXT ONE ROWS ONLY;
With Dense_rank()
SELECT salary
FROM
(
SELECT salary, DENSE_RANK() OVER (ORDER BY salary DESC) as rank from employees
)
WHERE rank = 3;

Resources