Oracle SELECT query to find the smallest distinct delimited string - sql

I have below table
C1 | Path
--------|-----------
T1 | T0/T1
T2 | T0/T1/T2
T3 | T0/T1/T2/T3
X1 | T0/X0/X1
T2 | T0/X0/X1/T2
T3 | T0/X0/X1/T2/T3
T3 | T0/Y0/Y1/Y2/T3
Y3 | T0/X0/X2/Y3
Y4 | T0/X0/X2/Y3/Y4
I need to write q query or code which should return smallest distinct delimited string as shown below
C1 | Path
--------|-----------
T1 | T0/T1
X1 | T0/X0/X1
T3 | T0/Y0/Y1/Y2/T3
Y3 | T0/X0/X2/Y3
Ex: T0/T1 is a substring of T0/T1/T2 and T0/T1/T2/T3.
T0/Y0/Y1/Y2/T3 is not substring of any other string. It is distinct.
Can you please help me to write this query in oracle

Try:
SELECT t1.*
FROM Table1 t1
LEFT JOIN Table1 t2
ON t1.Path <> t2.Path
AND t1.path LIKE '%'||t2.Path||'%'
WHERE t2.c1 IS NULL
Demo: http://sqlfiddle.com/#!4/a3f87/3
| C1 | PATH |
|----|----------------|
| T1 | T0/T1 |
| X1 | T0/X0/X1 |
| T3 | T0/Y0/Y1/Y2/T3 |
| Y3 | T0/X0/X2/Y3 |

This query works http://sqlfiddle.com/#!4/bf32e/19
With X as
(select 'T0/T1' c1 from dual
union
select 'T0/T1/T2' c1 from dual
union
select 'T0/T1/T2/T3' c1 from dual
union
select 'T0/X0/X1' c1 from dual
union
select 'T0/X0/X1/T2' c1 from dual
union
select 'T0/X0/X1/T2/T3' c1 from dual
union
select 'T0/Y0/Y1/Y2/T3' c1 from dual
union
select 'T0/X0/X2/Y3' c1 from dual
union
select 'T0/X0/X2/Y3/Y4' c1 from dual),
Y as (
select distinct X1.C1 YC1 from X X1, X X2
where X1.C1 like '%'||X2.C1||'%' and X1.C1<>X2.C1)
select * from X where not exists
(select 1 from Y where YC1=X.C1)

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

Recursive sql query in oracle

Table: ID1 and ID2 are name of the column
| ID1 | ID2 |
| 4 | 3 |
| 3 | 2 |
| 2 | 1 |
| 7 | 6 |
| 6 | 5 |
| 9 | 8 |
Desired Result
| ID1 | ID2 |
| 4 | 1 |
| 7 | 5 |
| 9 | 8 |
I need to build a recursive sql query for oracle using connect by or recursive cte. Unable to figure out solution.
No need to use CTE in this case since you do not do any cumulative calculations while traversing the tree.
SQL> with t(id1, id2) as
2 (select 4,3 from dual
3 union all select 3,2 from dual
4 union all select 2,1 from dual
5 union all select 7,6 from dual
6 union all select 6,5 from dual
7 union all select 9,8 from dual)
8 select connect_by_root id1 id1, id2
9 from t
10 where connect_by_isleaf = 1
11 start with not exists (select null from t t0 where t0.id2 = t.id1)
12 connect by prior id2 = id1;
ID1 ID2
---------- ----------
4 1
7 5
9 8
This is just a supplement answer without using Hierarchical queries which removes common elements from id1 and id2.
WITH t(id1, id2)
AS (SELECT 4,
3
FROM dual
UNION ALL
SELECT 3,
2
FROM dual
UNION ALL
SELECT 2,
1
FROM dual
UNION ALL
SELECT 7,
6
FROM dual
UNION ALL
SELECT 6,
5
FROM dual
UNION ALL
SELECT 9,
8
FROM dual),
t1
AS (SELECT id1 id1,
id1 id2
FROM t),
t2
AS (SELECT id2 id1,
id2 id2
FROM t),
t3
AS (SELECT ROWNUM row_num1,
id1
FROM (SELECT ( t.id1 ) id1
FROM t
WHERE NOT EXISTS (SELECT NULL
FROM t1,
t2
WHERE ( t1.id1 = t2.id1
AND t1.id2 = t2.id2 )
AND ( t.id1 = t1.id1 ))
ORDER BY t.id1 ASC)),
t4
AS (SELECT ROWNUM row_num1,
id2
FROM (SELECT ( t.id2 ) id2
FROM t
WHERE NOT EXISTS (SELECT NULL
FROM t1,
t2
WHERE ( t1.id1 = t2.id1
AND t1.id2 = t2.id2 )
AND ( t.id2 = t2.id2 ))
ORDER BY t.id2 ASC))
SELECT a.id1,
b.id2
FROM t3 a,
t4 b
WHERE a.row_num1 = b.row_num1
ORDER BY id1;

Access path between parent and child table in Oracle

I have parent and child tables as shown below.
Child_table | Reference_colums | Parent_table | Referenced_columns
------------|------------------|--------------|-------------------
T1 | Clan_Id | T0 | Clan_Id
X1 | Clan_Id | T0 | Clan_Id
X2 | Clan_Id | T0 | Clan_Id
T2 | Clan_Id | T1 | Clan_Id
Y1 | Clan_Id | T1 | Clan_Id
Y2 | Clan_Id | X1 | Clan_Id
T3 | C31 | T2 | C2
T4 | C4 | T3 | C32
If I give input (Parent table = T0 an Child table as T4)
I should get output as
Child_table | Reference_colums | Parent_table | Referenced_columns
------------|------------------|--------------|-------------------
T1 | Clan_Id | T0 | Clan_Id
T2 | Clan_Id | T1 | Clan_Id
T3 | C31 | T2 | C2
T4 | C4 | T3 | C32
As access path between T4 and T0 is
T0 ->T1->T2->T3->T4
Can you please help me to write the SELECT query in Oracle
As mentioned by #DavidAldrige you need to use the Oracle's CONNECT BY that handles hierarchical queries.
SELECT *
FROM tab
START WITH child = 'T4'
CONNECT BY prior parent = child and child != 'T0';
sqlfiddle demo
There is absolutely nothing wrong with using Oracle's connect by clause. I have been trying to use Common Table Expressions (CTE) instead because they are not proprietary to Oracle. The skill translates to other databases, which in my case is SQL server. The following query will give you the results you desire, but using a CTE instead of connect by.
The DATASET clause is just setting up the sample data, FINDSET is where the work is performed:
WITH
dataset
AS
(SELECT 'T1' AS child_table, 'T0' AS parent_table
FROM DUAL
UNION ALL
SELECT 'X1' AS child_table, 'T0' AS parent_table
FROM DUAL
UNION ALL
SELECT 'X2' AS child_table, 'T0' AS parent_table
FROM DUAL
UNION ALL
SELECT 'T2' AS child_table, 'T1' AS parent_table
FROM DUAL
UNION ALL
SELECT 'Y1' AS child_table, 'T1' AS parent_table
FROM DUAL
UNION ALL
SELECT 'Y2' AS child_table, 'X1' AS parent_table
FROM DUAL
UNION ALL
SELECT 'T3' AS child_table, 'T2' AS parent_table
FROM DUAL
UNION ALL
SELECT 'T4' AS child_table, 'T3' AS parent_table
FROM DUAL),
findset (parent_table, child_table)
AS
(SELECT parent_table, child_table
FROM dataset
WHERE child_table = 'T4'
UNION ALL
SELECT dataset.parent_table, dataset.child_table
FROM findset INNER JOIN dataset ON dataset.child_table =
findset.parent_table)
SELECT *
FROM findset;
This gives the following result:
PARENT_TABLE CHILD_TABLE
T3 T4
T2 T3
T1 T2
T0 T1

SQL - Getting specific rows by complicated condition

I have the following table:
+-----------+-----------+-------+
| ItemCode1 | ItemCode2 | Value |
+-----------+-----------+-------+
| X1 | Y1 | 1 |
| X2 | Y1 | 50 |
| X3 | Y3 | 1 |
| X4 | Y4 | 20 |
| X5 | Y4 | 1 |
+-----------+-----------+-------+
And I'd like to select 1 ItemCode1 for each distinct ItemCode2, based on the highest value. I.E, the output table should look like:
+-----------+-----------+-------+
| ItemCode1 | ItemCode2 | Value |
+-----------+-----------+-------+
| X2 | Y1 | 50 |
| X3 | Y3 | 1 |
| X4 | Y4 | 20 |
+-----------+-----------+-------+
I know it should be quite easy but for some reason, I can't get this one...
Help would be truly appreciated!
You can use ROW_NUMBER() to get the record with the highest value, then select just this one record in an outer query:
SELECT ItemCode1, ItemCode2, Value
FROM (
SELECT ItemCode1, ItemCode, Value,
ROW_NUMBER() OVER (PARTITION BY ItemCode2 ORDER BY Value DESC) rn
FROM MyTable ) t
WHERE t.rn = 1
If there are more than one records having the same max value and you want all of these records returned then you have to replace ROW_NUMBER() with RANK().
select t1.itemcode1, t1.itemcode2, t1.value
from your_table t1
join
(
select max(value) mvalue, itemcode2
from your_table
group by itemcode2
) t2 on t1.value = t2.mvalue
and t1.itemcode2 = t2.itemcode2
Select all rows where value = max(value) for the ItemCode2.
select ItemCode1, ItemCode2, Value
from tablename t1
where Value = (select max(Value)
from tablename t2
where t1.ItemCode2 = t2.ItemCode2)
Note that if several rows have same max value, they will all be returned.
Here's my submission. According to a comparison of the execution plans, this should execute a good half again faster than performing a subquery. The hash join is removed.
with
List( ItemCode1, ItemCode2, Value )as(
select 'X1', 'Y1', 20 union all
select 'X2', 'Y1', 50 union all
select 'X3', 'Y3', 1 union all
select 'X4', 'Y4', 20 union all
select 'X5', 'Y4', 20 union all
select 'X6', 'Y4', 50 union all
select 'X7', 'Y4', 75
),
Partial( ItemCode1, ItemCode2, Value, rnk )as(
select l.ItemCode1, l.ItemCode2, l.Value,
ROW_NUMBER() over( partition by l.ItemCode2 order by l.Value desc ) rnk
from list l
)
select ItemCode1, ItemCode2, Value
from Partial
where rnk = 1;
As you see, I've added some extra data rows. Can't have too much test data!

Oracle Join tables with range of dates in first table and dates in second table

I have two tables in an Oracle database:
The first table has a date range and I need help in writing a SQL query to find all the records from second table as in the result table below. The first four digits in the date is year and last two are session (10-Fall; 20-Spring; 30-Summer).
1) Table1
seqnum | min_date| max_date |c_id
1 | 201210 | 201210 | 100
1 | 201220 | 201330 | 150
1 | 201410 | 201410 | 200
2) Table2
seqnum | b_date
1 | 201210
1 | 201220
1 | 201230
1 | 201310
1 | 201320
1 | 201330
1 | 201410
1 | 201420
1 | 201430
3) Result table
seqnum | b_date | c_id
1 | 201210 | 100
1 | 201220 | 150
1 | 201230 | 150
1 | 201310 | 150
1 | 201320 | 150
1 | 201330 | 150
1 | 201410 | 200
1 | 201420 | 200
1 | 201430 | 200
If Table1 have only the first record then all the dates in Table2 must be associated with c_id 100 only.
To do this as simply as possible:
select t2.seqnum, t2.b_date, coalesce(t1.c_id, t3.max_id) as c_id
from table2 t2
left outer join table1 t1
on t2.b_date between t1.min_date and t1.max_date
cross join (select max(c_id) as max_id from table1) t3
order by t1.c_id, t2.b_date
SQLFiddle here
Share and enjoy.
Fiddle: http://sqlfiddle.com/#!4/45c72/10/0
select t2.seqnum,
t2.b_date,
case when t2.b_date < min_rg then x.c_id
when t2.b_date > max_rg then y.c_id
else t1.c_id
end as c_id
from (select min(min_date) as min_rg, max(max_date) as max_rg from table1) z
join table1 x
on x.min_date = z.min_rg
join table1 y
on y.max_date = z.max_rg
cross join table2 t2
left join table1 t1
on t2.b_date between t1.min_date and t1.max_date
order by b_date
When B_DATE on table2 is lower than the first MIN_DATE on table1 it will show C_ID from table1 of the lowest MIN_DATE (100 in your case, right now).
When B_DATE on table2 is higher than the last MAX_DATE on table1 it will show C_ID from table1 of the highest MAX_DATE (200 in your case, right now).
with table1 as (
select 1 seqnum, 201210 min_date, 201210 max_date, 100 c_id from dual
union all select 1, 201220, 201330, 150 from dual
union all select 1, 201410, 201410, 200 from dual
),
table2 as (
select 1 seqnum, 201210 b_date from dual
union all select 1, 201220 from dual
union all select 1, 201230 from dual
union all select 1, 201310 from dual
union all select 1, 201320 from dual
union all select 1, 201330 from dual
union all select 1, 201410 from dual
union all select 1, 201420 from dual
union all select 1, 201430 from dual
),
semi as (
select t2.seqnum, t2.b_date, t1.c_id,
-- since Oracle 11g
--lag(c_id IGNORE NULLS) over(partition by t2.seqnum order by t2.b_date) prev_c_id
last_value(c_id IGNORE NULLS) over(partition by t2.seqnum
order by t2.b_date
ROWS BETWEEN UNBOUNDED PRECEDING
AND 1 PRECEDING) prev_c_id
from table2 t2 left join table1 t1
on t2.seqnum = t1.seqnum and t2.b_date between t1.min_date and t1.max_date
)
select seqnum, b_date, nvl(c_id, prev_c_id) c_id
from semi;
This can be done with analytic functions.
Left join Table2 with Table1
Calculate previous (rows are ordered by b_date) not null value of c_id with (LAG or LAST_VALUE + windowing) for each seqnum.
If c_id is NULL then show the first not null previous value.