SQL recursive CTE - sql

I have a column where I need to keep finding the last record value associated with the original record in that column.
select rec1, val1 from table1:
rec1 val1
a1 t1
t1 t2
t2 null
a2 t7
t7 null
There are essentially 2 original records in this table (a1, a2). I need to associate t2 with a1 in my sql query since the link is based on val1 column (a1 -> t1 -> t2) until val1 is null. The record a2 is linked to t7 only since there is no further linkage for t7 (a2 -> t7).
I hope there is a 'simple' way to accomplish this. I have tried but am unable to make much progress.
Thanks

Here is a recursive CTE formulation. This version assumes no loops and that you don't have more than 100 links in the chain:
with cte as (
select rec1, val1, 1 as lev
from table1 t1
where not exists (select 1 from table1 tt1 where tt1.val1 = t1.rec1)
union all
select cte.rec1, t.val1, cte.lev + 1 as lev
from cte join
table1 t1
on t1.val1 = cte.rec1
)
select *
from (select cte.*, max(lev) over (partition by rec1) as maxlev
from cte
) cte
where maxlev = lev;

Related

insert new sequential records into child table for each record of parent table

I have two tables (SQL-server):
t1 (parent)
===========
id
1
2
t2 (child)
=======================
parent_id record_number
1 1
2 1
2 2
Is it possible in one SQL statement to insert new records into t2 for each of parent id, so the result will be:
t2:
=======================
parent_id record_number
1 1
1 2
2 1
2 2
2 3
Thank you!
I think what the OP is after is:
INSERT INTO T2
SELECT T1.id
MAX(T2.ID) + 1
FROM T1
JOIN T2 ON T1.ID = T2.parent_id
GROUP BY T1.id;
Something like this should get what you want.
Note I added it with a LEFT JOIN so you could use ISNULL in case the parent record isn't in t2 yet.
INSERT INTO t2 (parent_id, record_number)
SELECT
A.id,
ISNULL(MAX(B.record_number), 0) + 1
FROM t1 A
LEFT JOIN t2 B
ON A.id = B.parent_id
GROUP BY
A.id
As an aside, generating a record number could be done effectively using the ROW_NUMBER() function. Here is an example. Perhaps you have specific reasons why it needs to be persisted to the table, but if not this could be useful as well to calculate it on the fly.
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY parent_id ORDER BY id)
FROM t2
ORDER BY parent_id, record_number
Note it assumes you have an "id" column in t2, or something else to order the records by
Is this what you want?
insert into t2 (parent_id, record_number)
select id, id
from t1;
If so, I prefer using NULL to represent that that something has no parent:
insert into t2 (parent_id, record_number)
select null, id
from t1;
This is a more accurate representation of the data. Something is not its own parent.

Can we use correlated sub-query in the group clause?

I have a table t2 with field Col & a,b,c,d,e as records. I'm trying to get an output like:
r1 0 1
1 b a
2 d c
4 e
when i use the below query i get an error: Syntax error in the expresion (((Select Count(b.Col)+1 from t2 as b where a.col>b.col)+1)\2
Transform
first(col) as col1
Select ((Select Count(b.Col)+1 from t2 as b where a.col>b.col)+1)\2 as r1
From t2 as a
Group by (((Select Count(b.Col)+1 from t2 as b where a.col>b.col)+1)\2)
Pivot
(Select Count(b.Col)+1 from t2 as b where a.col>b.col) MOD 2
I don't think so. Just use a subquery:
select r1
from (select a.*,
(Select Count(b.Col)+1 from t2 as b where a.col>b.col)+1)\2 as r1
from t2 as a
) as a1
group by r1;
Or, because you are only selecting distinct values, use select distinct rather than group by in the original query.

Filter values if even one raw contains any value from another table

I have table1
c1 c2
1 a
1 b
1 c
2 a
3 b
and table2
c3
a
h
y
I need to filter all c1 if even 1 one of c2 contains any ofc3 from table2
result should be
c1
3
So far I tried
with cte as(
select c1, collect_set(c2) as c2
from table1
)
but I can't join it with table2 in such a way that will allow me to filter raws I don't need. For example, with
select c1
from cte
cross join table2
I could filter raws like
1 (a, b, c) a
but not
1 (a, b, c) x
and in the ennd I would even get
2 (a) x
which I don't need at all.
I also thought about concatinating
select c1, concat_ws(',', c2)
and using like '%c3%', but c3 is a column with many values and not some string.
NOT EXISTS wouldn't work either
Is there a way to do it?
I think you want something like this:
select t1.c1
from table1 t1 left join
table2 t2
on t1.c2 = t2.c3
group by t1.c1
having count(t2.c3) = 0;
Please check out Gordon's answer for a more appropriate and better solution.
SELECT [c1]
FROM #table1
WHERE [c1] NOT IN ( SELECT [c1]
FROM #table1
INNER JOIN #table2 ON c2 = c3 );

SQL : Filtering with multiple columns in a subquery

I want to select all the rows from a table, those are not present in ID column of another table.
For example my Table1 has below structure :
C1 C2 C3
-- -- --
1 A Z
2 B Y
3 C X
My Table2 Looks like :
D1 D2
-- --
1 A
2 Y
3 X
My working query looks something like :
slect * from Table1
where (C2 NOT IN (Select D2 from Table2);
This works fine, but if I want to filter on basis of combination of both the columns (i.e. D1 & D2, then I cant write the query as :
slect * from Table1
where ((C1,C2) NOT IN (Select (D1,D2) from Table2);
Can anyone help me rectify the above query?
Use NOT EXISTS:
SELECT t.* from Table1 t
WHERE NOT EXISTS
(
SELECT 1 FROM Table2 t2
WHERE t.C1 = t2.D1
AND t.C2 = t2.D2
)
Result:
C1 C2 C3
2 B Y
3 C X
Here's a Demo: http://sqlfiddle.com/#!3/81fdd/4/0
NOT EXISTS has lesss isues than NOT IN anyway:
Should I use NOT IN, OUTER APPLY, LEFT OUTER JOIN, EXCEPT, or NOT EXISTS?
SELECT T1.*
FROM Table1 AS T1
LEFT JOIN Table2 AS T2
ON T2.D1 = T1.C1
AND T2.D2 = T1.C2
WHERE T2.D1 IS NULL

retrieving rows with max date

I have two tables like this:
Table1 (Number column is unique)
Number | date
1234 2008-10-06 17:11:00
5678 2005-10-19 16:20:00
9023 2005-12-09 16:20:00
4243 2009-01-06 17:11:00
5234 2009-01-14 17:11:00
Table 2
Number | code
1234 A1
1234 B1
5678 A1
9023 A1
4243 C1
5234 C1
I am trying to retrieve data from these two tables so that I get only one row for each code in Table 2 that is the most recent (from table 1).
Based on this example, my result would be:
1234 A1 (because thats the one with latest date)
1234 B1 (because thats the one with latest date)
5234 C1 (because thats the one with latest date)
Select Distinct T1.number, T2.code
From Table2 T2
Join Table1 T1
On T1.Number = T2.Number
And T1.date =
(Select Max(Date) From Table1
Where Number = T2.Number)
EDIT: to fix issue noted in comment:
Select Z.Number, Z.Code
From (Select A.number, A.code, B.date
From Table2 A Join Table1 B
On B.Number = A.Number) Z
Where Z.Date =
(Select Max(Date)
From Table2 A Join Table1 B
On B.Number = A.Number
Where code = Z.Code)
Analytic function solution. This is for Oracle; if you're using another RDBMS it may not work. If there are multiple rows with the same date for a given code, this will arbitrarily select one.
SELECT number, code FROM (
SELECT t1.number,
t1.code,
row_number() OVER ( PARTITION BY t1.code ORDER BY t2.date DESC ) date_sort_key
FROM t1, t2
WHERE t2.number = t1.number
)
WHERE date_sort_key = 1
Substituting rank() for row_number() would make it report multiple entries where there is a duplicate date.
Here is a version of Dave's answer that works on SQL Server
SELECT number, code FROM (
SELECT Table2.number,
Table2.code,
row_number() OVER ( PARTITION BY table2.code ORDER BY table1.date DESC ) date_sort_key
FROM table1, Table2
WHERE Table2.number = table1.number
) a
WHERE date_sort_key = 1
This works for SQL SERVER
CREATE table Table1 (number int, date datetime)
INSERT Table1 VALUES (1234, '2008-10-06 17:11:00')
,(5678, '2005-10-19 16:20:00')
,(9023, '2005-12-09 16:20:00')
,(4243, '2009-01-06 17:11:00')
,(5234, '2009-01-14 17:11:00')
CREATE table Table2 (number int, code varchar(2))
INSERT Table2 VALUES (1234, 'A1 ')
,(1234, 'B1')
,(5678, 'A1')
,(9023, 'A1')
,(4243, 'C1')
,(5234, 'C1')
SELECT DISTINCT
a.number
,a.code
FROM Table2 a
INNER JOIN Table1 b ON a.number = b.number
INNER JOIN (
SELECT
t2.code
,MAX(t1.date) as date
FROM Table2 t2
INNER JOIN Table1 t1
ON t1.Number = t2.Number
GROUP BY t2.code
) c ON b.date = c.date