Recursive sql query in oracle - sql

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;

Related

Query different number of records for each category in SQL

I have a table that looks like the following:
col1 | col2 | col3 | col4
A | 1 | 2 | 4
A | 2 | 5 | 3
A | 5 | 1 | 6
B | 3 | 1 | 2
B | 4 | 4 | 4
I have another table where the records are unique and looks like the following:
col1 | col2
A | 2
B | 1
I want to query Table 1 in such a way that I filter out only n number of records for each category in Table 1 based on the value the categories have in Table 2.
Based on Table 2 I need to extract 2 records for A and 1 record for B. I need the resulting queried table to look like the following:
col1 | col2 | col3 | col4
A | 2 | 5 | 3
A | 1 | 2 | 4
B | 3 | 1 | 2
The choice of the records are made based on col4 sorted in ascending order. I am currently tring to do this on BigQuery.
You can use row_number() and join:
select t1.col1, t1.col2, t1.col3, t1.col4
from (select t1.*, row_number() over (partition by col1 order by col4) as seqnum
from table1 t1
) t1 join
table2 t2
on t2.col1 = t1.col1 and t1.seqnum <= t2.col2
order by t1.col1, t1.col4;
Below is for BigQuery Standard SQL
#standardSQL
SELECT t.*
FROM (
SELECT ARRAY_AGG(t1 ORDER BY t1.col4) arr, MIN(t2.col2) cnt
FROM table1 t1 JOIN table2 t2 ON t1.col1 = t2.col1
GROUP BY t1.col1
), UNNEST(arr) t WITH OFFSET num
WHERE num < cnt
you can test / play with it using dummy data from your question as below
#standardSQL
WITH `table1` AS (
SELECT 'A' col1, 1 col2, 2 col3, 4 col4 UNION ALL
SELECT 'A', 2, 5, 3 UNION ALL
SELECT 'A', 5, 1, 6 UNION ALL
SELECT 'B', 3, 1, 2 UNION ALL
SELECT 'B', 4, 4, 4
), `table2` AS (
SELECT 'A' col1, 2 col2 UNION ALL
SELECT 'B', 1
)
SELECT t.*
FROM (
SELECT ARRAY_AGG(t1 ORDER BY t1.col4) arr, MIN(t2.col2) cnt
FROM table1 t1 JOIN table2 t2 ON t1.col1 = t2.col1
GROUP BY t1.col1
), UNNEST(arr) t WITH OFFSET num
WHERE num < cnt
with output as
Row col1 col2 col3 col4
1 A 2 5 3
2 A 1 2 4
3 B 3 1 2

Append string to existing record with matching id in SQL Server?

I've a table where I want to append name column if id1 and id2 column have same values.
http://sqlfiddle.com/#!3/8be48/1
Input table
id1 id2 name
1 1 a
1 1 b
2 2 c
2 2 c
3 3 d
3 3 e
3 3 f
Expected Output
id1 id2 name
1 1 a | b
2 2 c | c
3 3 d | e | f
I tried groping the id1 and id2 together and concatenating name column but it didn't work out.
select id1, id2, name + ' | ' + name
from testdata
group by id1, id2;
Use STUFF String function :
CREATE TABLE #table(id1 INT, id2 INT , name VARCHAR(10))
INSERT INTO #table(id1 , id2 ,name )
SELECT 1,1,'a' UNION ALL
SELECT 1,1,'b' UNION ALL
SELECT 2,2,'c' UNION ALL
SELECT 2,2,'c' UNION ALL
SELECT 3,3,'d' UNION ALL
SELECT 3,3,'e' UNION ALL
SELECT 3,3,'f'
SELECT T1.id1 id1, T1.id2 id2 , STUFF( ( SELECT ' | ' + name FROM #table T2
WHERE T1.id1 = T2.id1 AND T1.id2 = T2.id2 FOR XML PATH('') ) ,1,2,'')
FROM #table T1
GROUP BY id1 , id2

How do I need to change my sql to get what I want in this case?

I have a table like following:
id value date
1 5 2015-01-10
2 5 2015-06-13
3 5 2015-09-05
4 11 2015-02-11
5 11 2015-01-10
6 11 2015-01-25
As can be seen, every value appears 3 times with different date. I want to write a query that returns the unique values that has the maximum date, which would be the following for the above table:
id value date
3 5 2015-09-05
4 11 2015-02-11
How could I do it?
This is the updated question:
The real question I am encountering is a little bit more complicated than the simplified version above. I thought I can move a step further once I know the answer to the simplified version, but I guest I was wrong. So, I am updating the question herein.
I have 2 tables like following:
Table 1
id id2 date
1 2 2015-01-10
2 5 2015-06-13
3 9 2015-09-05
4 10 2015-02-11
5 26 2015-01-10
6 65 2015-01-25
Table 2
id id2 data
1 2 A
2 5 A
3 9 A
4 10 B
5 26 B
6 65 B
Here, Table 1 and Table 2 are joined by id2
What I want to get is two records as follows:
id2 date data
9 2015-01-10 A
10 2015-02-11 B
You can use row_number to select the rows with the greatest date per value
select * from (
select t2.id2, t1.date, t2.data,
row_number() over (partition by t2.data order by t1.date desc) rn
from table1 t1
join table2 t2 on t1.id = t2.id2
) t where rn = 1
select a.id, a.value, a.date
from mytable a,
( select id, max(date) maxdate
from mytable b
group by id) b
where a.id = b.id
and a.date = b.maxdate;
Oracle Setup:
CREATE TABLE Table1 ( id, id2, "date" ) AS
SELECT 1, 2, DATE '2015-01-10' FROM DUAL UNION ALL
SELECT 2, 5, DATE '2015-06-13' FROM DUAL UNION ALL
SELECT 3, 9, DATE '2015-09-05' FROM DUAL UNION ALL
SELECT 4, 10, DATE '2015-02-11' FROM DUAL UNION ALL
SELECT 5, 26, DATE '2015-01-10' FROM DUAL UNION ALL
SELECT 6, 65, DATE '2015-01-25' FROM DUAL;
CREATE TABLE Table2 ( id, id2, data ) AS
SELECT 1, 2, 'A' FROM DUAL UNION ALL
SELECT 2, 5, 'A' FROM DUAL UNION ALL
SELECT 3, 9, 'A' FROM DUAL UNION ALL
SELECT 4, 10, 'B' FROM DUAL UNION ALL
SELECT 5, 26, 'B' FROM DUAL UNION ALL
SELECT 6, 65, 'B' FROM DUAL;
Query:
SELECT MAX( t1.id ) KEEP ( DENSE_RANK LAST ORDER BY t1."date" ) AS id,
MAX( t1.id2 ) KEEP ( DENSE_RANK LAST ORDER BY t1."date" ) AS id2,
MAX( t1."date" ) AS "date",
t2.data
FROM Table1 t1
INNER JOIN
Table2 t2
ON ( t1.id = t2.id AND t1.id2 = t2.id2 )
GROUP BY t2.data
Output:
ID ID2 date DATA
---------- ---------- ------------------- ----
3 9 2015-09-05 00:00:00 A
4 10 2015-02-11 00:00:00 B
Query 2:
SELECT id,
id2,
"date",
data
FROM (
SELECT t1.*,
t2.data,
ROW_NUMBER() OVER ( PARTITION BY t2.data ORDER BY t1."date" DESC ) AS rn
FROM Table1 t1
INNER JOIN
Table2 t2
ON ( t1.id = t2.id AND t1.id2 = t2.id2 )
)
WHERE rn = 1;
Output:
ID ID2 date DATA
---------- ---------- ------------------- ----
3 9 2015-09-05 00:00:00 A
4 10 2015-02-11 00:00:00 B

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;

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.