OracleSQL -> Sort View by three levels - sql

I created the following View in my Oracle SQL Developer:
The Results are not sorted In the way I need them.
I need to sort the results like this:
So If you would draw it as a graph with a depth of three
It would look like this:
The Maximum level depth is three. I tried different approaches with select connect_by_root, but it does not work. ORDER BY Also does no work because of the tree structure of the data.
Does anyone have a tip for me ?

You could use a left join:
select t.*
from t left join
t tp
on tp.id = t.parent_id
order by coalesce(tp.parent_id, t.parent_id, t.id),
coalesce(t.parent_id, t.id),
t.id
This assumes that the parent ids are smaller than the id, which seems to be true for your data. It is not a necessary assumption, but it simplifies the logic.

With a LEFT self join and conditional sorting:
select t1.*
from tablename t1
left join tablename t2 on t2.id = t1.parent_id
order by coalesce(t2.parent_id, t1.parent_id, t1.id),
case when t1.parent_id is null then 0 else 1 end,
case when t2.parent_id is not null then t1.parent_id else t1.id end,
case when t2.parent_id is null then 0 else 1 end,
t1.id
See the demo.
Results:
> ID | PARENT_ID | LEVEL
> -----: | --------: | :--------
> 29101 | null | LVL_ONE
> 153799 | 29101 | LVL_TWO
> 153800 | 153799 | LVL_THREE
> 153801 | 153799 | LVL_THREE
> 153803 | 29101 | LVL_TWO
> 153804 | 153803 | LVL_THREE
> 153802 | null | LVL_ONE
> 153805 | 153802 | LVL_TWO
> 153806 | 153805 | LVL_THREE

If the view you have used is a hierarchial query, then you have an option to "order siblings by", i would suggest you use that
Here is an example
https://oracle-base.com/articles/misc/hierarchical-queries

drop table a1;
CREATE TABLE a1
as
SELECT 153804 as ID, 153803 as PARENT_ID
FROM DUAL
UNION ALL
SELECT 153801, 153799
FROM DUAL
UNION ALL
SELECT 153803, 29101
FROM DUAL
UNION ALL
SELECT 29101, NULL
FROM DUAL
UNION ALL
SELECT 153802, NULL
FROM DUAL
UNION ALL
SELECT 153805, 153802
FROM DUAL
UNION ALL
SELECT 153806, 153805
FROM DUAL
UNION ALL
SELECT 153800, 153799
FROM DUAL
UNION ALL
SELECT 153799, 29101
FROM DUAL
;
select id, parent_id, level, 'LEVEL_' || level as level_desc
from a1
start with parent_id is null
connect by parent_id = prior(id)
order siblings by id
;

Related

How to select first x records from second table

I would like to get all records from first table and only x records from second table.
How many records from second table I have info in first table :
My tables are
table1 :
WITH table1(a,b) AS
(
SELECT 'aa',3 FROM dual UNION ALL
SELECT 'bb',2 FROM dual UNION ALL
SELECT 'cc',4 FROM dual
)
SELECT *
FROM table1;
a | b (number of records from table2 (x))
------
aa | 3
bb | 2
cc | 4
table2 :
WITH table2(a,b) AS
(
SELECT 'aa','1xx' FROM dual UNION ALL
SELECT 'aa','2yy' FROM dual UNION ALL
SELECT 'aa','3ww' FROM dual UNION ALL
SELECT 'aa','4zz' FROM dual UNION ALL
SELECT 'aa','5qq' FROM dual UNION ALL
SELECT 'bb','1aa' FROM dual UNION ALL
SELECT 'bb','2bb' FROM dual UNION ALL
SELECT 'bb','3cc' FROM dual UNION ALL
SELECT 'cc','1oo' FROM dual UNION ALL
SELECT 'cc','2uu' FROM dual UNION ALL
SELECT 'cc','3tt' FROM dual UNION ALL
SELECT 'cc','4zz' FROM dual UNION ALL
SELECT 'cc','5rr' FROM dual
)
SELECT *
FROM table2;
a | b
--------
aa | 1xx
aa | 2yy
aa | 3ww
aa | 4zz
aa | 5qq
bb | 1aa
bb | 2bb
bb | 3cc
bb | 4dd
bb | 5ee
cc | 1oo
cc | 2uu
cc | 3tt
cc | 4zz
cc | 5rr
Expected Result:
a | b
--------
aa | 1xx
aa | 2yy
aa | 3ww
bb | 1aa
bb | 2bb
cc | 1oo
cc | 2uu
cc | 3tt
cc | 4zz
You can use ROW_NUMBER() analytic function with LEFT/RIGHT OUTER JOIN among the tables :
WITH t2 AS
(
SELECT t2.a,t2.b, ROW_NUMBER() OVER (PARTITION BY t2.a ORDER BY t2.b) AS rn
FROM table2 t2
)
SELECT t2.a, t2.b
FROM t2
LEFT JOIN table1 t1
ON t1.a = t2.a
WHERE rn <= t1.b
Demo
You need to write something like:
SELECT a,
b
FROM Table2 T,
( SELECT LEVEL L FROM DUAL
CONNECT BY LEVEL <= (SELECT MAX(b) FROM Table1)
) A
WHERE T.b>= A.L
ORDER BY T.a;
Ideally you should have a ordering column in table2. When you say first X rows it does not make any sense unless you have something like an id or date field to order the records.
Anyway, assuming the number part of the column b in table 2 for ordering and assuming the number would be followed by 2 characters only such as xx,yy etc you can use the logic below
Select Tb1.a, Tb1.b
from
(Select t.*, row_number() over (partition by a order by substr(b,1,length(b)-2)) as seq
from Table2 t) Tb1
join Table1 Tb2
on Tb1.a = Tb2.a
Where Tb1.seq <= Tb2.b;
Demo - https://dbfiddle.uk/?rdbms=oracle_11.2&fiddle=3030b2372bcbb007606bbb6481af9884
Again it's just a job for lateral:
WITH prep AS
(
SELECT *
FROM tab1,
LATERAL
(
SELECT LEVEL AS lvl
FROM dual
CONNECT BY LEVEL <= b
)
)
SELECT p.a, t2.b
FROM prep p
JOIN tab2 t2
ON p.lvl = regexp_substr(t2.b,'^\d+')
AND p.a = t2.a
ORDER BY p.a, p.lvl

Return default value for some IN condition not match

I think this shouldn't be really hard. I am writing an Oracle-SQL code to extract data from SQL:
select ID, Qty from TableOne where ID in ('A', 'B', 'C')
I want the database to show the result of the query if there is match for some items in the IN condition, and return a default value if there is no match for those items in the IN condition.
For example, I want the result to be:
+----+-----------+
| ID | Qty |
+----+-----------+
| A | 3 |
| A | 5 |
| B | 4 |
| C | Not Found |
+----+-----------+
Where there is no ID = C in the table TableOne.
Is there any easy way to code the result?
Thank you very much!
Use COALESCE, NVL or CASE with a LEFT OUTER JOIN and specify the ids in a sub-query factoring clause:
WITH ids_to_match( id ) AS (
SELECT 'A' FROM DUAL UNION ALL
SELECT 'B' FROM DUAL UNION ALL
SELECT 'C' FROM DUAL
)
select i.ID,
COALESCE( TO_CHAR(Qty), 'Not Found' ) AS Qty
from ids_to_match i
LEFT OUTER JOIN TableOne t
ON ( t.id = i.id )
or use a collection and a table collection expression:
select i.COLUMN_VALUE AS ID,
COALESCE( TO_CHAR(Qty), 'Not Found' ) AS Qty
from TABLE( SYS.ODCIVARCHAR2LIST( 'A', 'B', 'C' ) ) i
LEFT OUTER JOIN
TableOne t
ON ( t.id = i.COLUMN_VALUE )
You can use LEFT JOIN with UNION ALL :
WITH ALL_ID AS (
SELECT 'A' AS ID FROM DUAL UNION ALL
SELECT 'B' AS ID FROM DUAL UNION ALL
SELECT 'C' AS ID FROM DUAL
)
SELECT A.ID, t.Qty -- REPLACE NULL WITH NOT FOUND
FROM ALL_ID A ID LEFT JOIN
Table t
ON t.ID = A.ID;
if this works for you:
select t2.ID, case when t2.QTY is NULL then TO_CHAR('Not found') else t2.QTY end "QTY" from TableOnet1 t1 right join Tabletwo t2
on t1.ID = t2.ID where t2.ID in ('A', 'B', 'C')

Join on a selected number of digits

The column ddm_zip has only 5 digit zipcodes. The column tt_pad_zip contains values anywhere between 2-5. I would like to perform a join selecting values that appear on both sides of the table.
However, tt_pad_zip has various levels of granularity.
For e.g. when we see value of 55 in tt_pad_zip, it is considering all zipcodes that start with 55, for e.g. 551...553...55636 etc. Hence I want the join to select this.
For e.g. when we see value of 558 in tt_pad_zip, it is considering all zipcodes that start with 558, for e.g. 5581...5585...558743 etc. Hence I want the join to select this.
However if there is a value in tt_pad_zip that has the exact zipcode as ddm_zip e.g. 58636 as shown below, I would like the output to pick this .
column: ddm_zip column: tt_pad_zip
55636 55
57254 5734
58636 57254
58636
Join on the SUBSTRing of ddm_zip that has equal length to the tt_pad_zip value you are comparing it to. Either:
SUBSTR( ddm_zip, 1, LENGTH( tt_pad_zip ) ) = tt_pad_zip
or:
ddm_zip LIKE tt_pad_zip || '%'
Oracle Setup:
CREATE TABLE table1 ( ddm_zip ) AS
SELECT '55636' FROM DUAL UNION ALL
SELECT '57254' FROM DUAL UNION ALL
SELECT '55824' FROM DUAL;
CREATE TABLE table2 ( tt_pad_zip ) AS
SELECT '55' FROM DUAL UNION ALL
SELECT '5734' FROM DUAL UNION ALL
SELECT '57254' FROM DUAL UNION ALL
SELECT '55636' FROM DUAL;
Query:
SELECT *
FROM table1 t1
INNER JOIN
table2 t2
ON t1.ddm_zip LIKE t2.tt_pad_zip || '%'
Output:
DDM_ZIP | TT_PAD_ZIP
:------ | :---------
55636 | 55
55636 | 55636
57254 | 57254
55824 | 55
Query 2: If you want the most granular match.
SELECT DDM_ZIP,
MAX( TT_PAD_ZIP ) AS TT_PAD_ZIP
FROM table1 t1
INNER JOIN
table2 t2
ON t1.ddm_zip LIKE t2.tt_pad_zip || '%'
GROUP BY DDM_ZIP
Output:
DDM_ZIP | TT_PAD_ZIP
:------ | :---------
55636 | 55636
57254 | 57254
55824 | 55
db<>fiddle here
I think you are looking for something like the following:
-- Sample Data:
WITH dat (ddm_zip, tt_pad_zip) AS
(SELECT 0,55 FROM dual
UNION ALL
SELECT 1,5734 FROM dual
UNION ALL
SELECT 2,57254 FROM dual
UNION ALL
SELECT 3,55636 FROM dual
UNION ALL
SELECT 55636, 99 FROM dual
UNION ALL
SELECT 57254, 88 FROM dual
UNION ALL
SELECT 57341, 77 FROM dual)
-- Query:
SELECT d2.ddm_zip, d1.tt_pad_zip
FROM dat d1
JOIN dat d2
-- Join the data via like:
ON d2.ddm_zip LIKE d1.tt_pad_zip || '%'
-- Exclude the data where a "better" match exists:
AND NOT EXISTS (SELECT 1 FROM dat d3
WHERE d2.ddm_zip LIKE d3.tt_pad_zip || '%'
AND LENGTH(d1.tt_pad_zip) < LENGTH(d3.tt_pad_zip))
You can use conditional join with a CASE statement in the ON clause:
select t1.ddm_zip, t2.tt_pad_zip
from table1 t1 inner join table2 t2
on t1.ddm_zip like case
when exists(select 1 from table2 where tt_pad_zip = t1.ddm_zip) then t2.tt_pad_zip
else '%' || t2.tt_pad_zip || '%'
end
See the demo.
Results:
> DDM_ZIP | TT_PAD_ZIP
> :------ | :---------
> 55636 | 55
> 57254 | 57254
> 58636 | 58636
You could try using the SUBSTR operation to just compare the first two numbers of each column. I don't know which type of join you want, your table names, or example data, but here is a good syntax.
SELECT
*
FROM
table1 INNER JOIN table2 ON SUBSTR(ddm_zip, 1, 2) = SUBSTR(tt_pad_zip, 1,2)

oracle SQL select/join with many-to-one and nulls

I'm having trouble crafting an Oracle SQL query that returns the results I seek. It's possible that what I'm trying to do isn't possible.
For a given code in table one, if the code exists in table two and ANY of the flags are "1", then the status should be "1" in the query results. Otherwise, the status should be "0". If the code doesn't exist at all in table 2, then the status should be null.
tab1
------------
id,code
------------
1,ABC
2,DEF
3,GHI
4,JKL
5,MNO
6,PQR
7,STU
tab2
------------
id,code,flag
------------
1,ABC,0
2,ABC,0
3,DEF,1
4,DEF,1
5,GHI,0
6,GHI,1
7,JKL,1
8,JKL,0
9,MNO,0
10,PQR,1
(query?)
result
------------
id,code,status
------------
1,ABC,0
2,DEF,1
3,GHI,1
4,JKL,1
5,MNO,0
6,PQR,1
7,STU,null
So far, the only query I've been able to come up with is this, which doesn't give the right results in the status column...
select tab1.*, (select * from (
select flag from tab2 where tab2.code = code order by flag desc)
where rownum <=1) as status from tab1;
... status is always "1", which is incorrect.
I'm thinking that instead of using order by and selecting the first result, it might be possible to instead count the number of "1" flags for each code, but I'm not sure if that would work.
My first inclination is to use a subselect:
select t1.*,
(select max(t2.flag)
from table2 t2
where t2.code = t1.code
) as t2flag
from table1 t1;
You can also phrase this as a left join with an aggregation:
select t1.*, t2.flag
(select max(t2.flag)
from table2 t2
where t2.code = t1.code
) as t2flag
from table1 t1 left join
(select t2.code, max(t2.flag) as flag
from table2 t2
group by t2.code
) t2
on t2.code = t1.code;
Both these methods are assuming that flag is either 0 or 1, as in your question.
I'd use a LEFT JOIN:
select t1.id, max(t1.code) code, max(t2.flag) status
from table1 t1
left join table2 t2 on t1.code=t2.code
group by t1.id
Assuming that 0 and 1 are the only two flags, what about something like:
with tab1 as (select 1 id, 'ABC' code from dual union all
select 2 id, 'DEF' code from dual union all
select 3 id, 'GHI' code from dual union all
select 4 id, 'JKL' code from dual union all
select 5 id, 'MNO' code from dual union all
select 6 id, 'PQR' code from dual union all
select 7 id, 'STU' code from dual),
tab2 as (select 1 id, 'ABC' code, 0 flag from dual union all
select 2 id, 'ABC' code, 0 flag from dual union all
select 3 id, 'DEF' code, 1 flag from dual union all
select 4 id, 'DEF' code, 1 flag from dual union all
select 5 id, 'GHI' code, 0 flag from dual union all
select 6 id, 'GHI' code, 1 flag from dual union all
select 7 id, 'JKL' code, 1 flag from dual union all
select 8 id, 'JKL' code, 0 flag from dual union all
select 9 id, 'MNO' code, 0 flag from dual union all
select 10 id, 'PQR' code, 1 flag from dual)
select t1.id,
t1.code,
t2.flag status
from tab1 t1
left join (select code,
max(flag) flag
from tab2
group by code) t2
on (t1.code = t2.code);
ID CODE STATUS
---------- ---- ----------
5 MNO 0
6 PQR 1
2 DEF 1
1 ABC 0
3 GHI 1
4 JKL 1
7 STU
Maybe I'm missing something, but it seems to be as simple as this:
select tab1.id, tab1.code, max(tab2.flag) status
from tab1
left join tab2 on tab1.code = tab2.code
group by tab1.id, tab1.code
order by tab1.id;
A sample SQL Fiddle gives the desired result:
| id | code | status |
|----|------|--------|
| 1 | ABC | 0 |
| 2 | DEF | 1 |
| 3 | GHI | 1 |
| 4 | JKL | 1 |
| 5 | MNO | 0 |
| 6 | PQR | 1 |
| 7 | STU | (null) |
SELECT ID,
CODE,
MAX( STATUS)
FROM
(SELECT DISTINCT T1.ID,
T1.CODE,
FLAG AS STATUS
FROM T1
LEFT JOIN T2
ON T1.CODE = T2.CODE
)
GROUP BY ID,
CODE;
This gives result as you asked:
SELECT tab1.id,tab1.code,max(tab2.flag) as Status
FROM tab1 LEFT JOIN tab2
ON tab1.code=tab2.code
GROUP BY tab2.code
ORDER BY tab1.id;

How to duplicate each row in sql query?

Suppose we have a query
SELECT * FROM my_table ORDER BY id
which results in
id | title
-----------
1 | 'ABC'
2 | 'DEF'
3 | 'GHI'
How could I modify given select statement to have each row duplicated in the result set like this:
id | title
-----------
1 | 'ABC'
1 | 'ABC'
2 | 'DEF'
2 | 'DEF'
3 | 'GHI'
3 | 'GHI'
Try this...
SELECT * FROM my_table
UNION ALL
SELECT * FROM my_table
ORDER BY id
You can use union all, but I like using cross join:
select *
from MyTable cross join
(select 1 from dual union all select 2 from dual) n
order by id;
The reason I like the cross join is in the case where MyTable is really some complicated subquery. Although the query optimizer might evaluate it only once, you can't really depend on that fact. So the performance should be better in this case.
You could cross join to a row generator, the numeric value indicates how many duplicates per original you want.
select *
from my_table
cross join
(select null
from dual
connect by level <= 2)
order by id