CTE for all Child for Multiple parent - sql

I have a parent child relation table as shown below:
ContractID ContractIdRef
---------- -------------
1 null
2 1
3 1
4 2
5 4
10 null
11 10
12 11
15 null
16 12
I want result like below:
ContractID ContractIdRef rw
----------- -------------- ---
1 null 1
2 1 1
3 1 1
4 2 1
5 4 1
10 null 10
11 10 10
12 11 10
15 null 15
16 12 10
In above result I want to specify each rows parent.
Thanks

As you mentioned in the TAGS Comman Table Expression is the way to go
;WITH REC_CTE
AS (SELECT [contractid],
[ContractIdRef],
[contractid] AS rw
FROM Yourtable
WHERE [contractidref] IS NULL
UNION ALL
SELECT T.[contractid],
T.[contractidref],
c.rw
FROM Yourtable AS T
INNER JOIN REC_CTE C
ON T.[contractidref] = c.[contractid]
WHERE T.[contractid] <> T.[contractidref])
SELECT [contractid],
[contractidref],
rw
FROM REC_CTE
ORDER BY [contractid]
Demo
Schema Setup
If object_id('tempdb.dbo.#Yourtable') is not null
DROP table #Yourtable
CREATE TABLE #Yourtable
([ContractID] INT, [ContractIdRef] INT);
Sample data
INSERT INTO #Yourtable
([ContractID], [ContractIdRef])
VALUES
('1', NULL),
('2', '1'),
('3', '1'),
('4', '2'),
('5', '4'),
('10', NULL),
('11', '10'),
('12', '11'),
('15', NULL),
('16', '12');
Query
;WITH REC_CTE
AS (SELECT [ContractID],
[ContractIdRef] as [ContractIdRef],
[ContractID] AS rw
FROM #Yourtable where [ContractIdRef] is null
UNION ALL
SELECT T.[ContractID],
T.[ContractIdRef],
c.rw
FROM #Yourtable AS T
INNER JOIN REC_CTE c
ON T.[ContractIdRef] = c.[ContractID]
WHERE T.[ContractID] <> T.[ContractIdRef])
SELECT [ContractID],
[ContractIdRef],
rw
FROM REC_CTE
ORDER BY [ContractID]
Result
+-----------+-------------+----+
|ContractID |ContractIdRef| rw |
+-----------+-------------+----+
|1 |NULL | 1 |
|2 |1 | 1 |
|3 |1 | 1 |
|4 |2 | 1 |
|5 |4 | 1 |
|10 |NULL | 10 |
|11 |10 | 10 |
|12 |11 | 10 |
|15 |NULL | 15 |
|16 |12 | 10 |
+-----------+-------------+----+

with Q as(
select ContractID, ContractIdRef, ContractID as root
from childs
where ContractIdRef is null
union all
select C.ContractID, C.ContractIdRef, Q.root
from Q, childs C
where C.ContractIdRef=Q.ContractID
)
select * from Q
order by ContractID
Tested on MS SQL 2014.
For Postgresql need add word 'recursive' after 'with'. Test on sqlfiddle.com
For Oracle first line writed as with Q(ContractID,ContractIdRef,root).

Related

tsql select with all variations of joins

For an export I have to select all stats for all years and groups, even if the combination has no amount to export.
Here an example and expected result:
DECLARE #years TABLE (yr INT)
INSERT #years VALUES (2020),(2021),(2022)
DECLARE #grps TABLE (grp INT)
INSERT #grps VALUES (1),(2),(3)
DECLARE #stats TABLE (item INT, yr INT, grp INT, amount INT)
INSERT #stats VALUES
(1,2021,1,344)
,(1,2021,2,34)
,(1,2021,3,44)
,(1,2020,1,249)
,(1,2020,3,70)
,(2,2021,1,850)
,(2,2021,2,1260)
,(2,2020,1,799)
/* EXPECTED RESULT */
/*
|item|year|grp |amout|
|1 |2020|1 | 249 |
|1 |2020|2 | 0 |
|1 |2020|3 | 70 |
|1 |2021|1 | 344 |
|1 |2021|2 | 34 |
|1 |2021|3 | 44 |
|1 |2022|1 | 0 |
|1 |2022|2 | 0 |
|1 |2022|3 | 0 |
|2 |2020|1 | 799 |
|2 |2020|2 | 0 |
|2 |2020|3 | 0 |
|2 |2021|1 | 850 |
|2 |2021|2 |1260 |
|2 |2021|3 | 0 |
|2 |2022|1 | 0 |
|2 |2022|2 | 0 |
|2 |2022|3 | 0 |
*/
I get id done with this query:
SELECT item.item
,yr.yr
,grp.grp
,ISNULL(st.amount,0) [amount]
FROM (SELECT DISTINCT item FROM #stats) item
CROSS APPLY (SELECT yr FROM #years) yr
CROSS APPLY (SELECT grp FROM #grps) grp
LEFT JOIN #stats st
ON st.item = item.item
AND st.yr = yr.yr
AND st.grp = grp.grp
ORDER BY 1,2,3
Is there no simpler solution to this?
It's debatable if it's a simpler solution.
But you can cross join in a CTE to get all expected combos.
Then left join the CTE to the existing data.
DECLARE #years TABLE (yr INT);
INSERT #years VALUES (2020),(2021),(2022);
DECLARE #grps TABLE (grp INT);
INSERT #grps VALUES (1),(2),(3);
DECLARE #stats TABLE (item INT, yr INT, grp INT, amount INT)
INSERT #stats VALUES
(1,2021,1,344)
,(1,2021,2,34)
,(1,2021,3,44)
,(1,2020,1,249)
,(1,2020,3,70)
,(2,2021,1,850)
,(2,2021,2,1260)
,(2,2020,1,799);
;WITH CTE_YEARS AS (
SELECT DISTINCT yr FROM #years
)
, CTE_GROUPS AS (
SELECT DISTINCT grp FROM #grps
)
, CTE_ITEMS AS (
SELECT DISTINCT item FROM #stats
)
, CTE_ALL_YEAR_GROUP_ITEMS AS (
SELECT y.yr, g.grp, i.item
FROM CTE_YEARS y
CROSS JOIN CTE_GROUPS g
CROSS JOIN CTE_ITEMS i
)
SELECT
cte.item
, cte.yr
, cte.grp
, ISNULL(stat.amount, 0) AS amount
FROM CTE_ALL_YEAR_GROUP_ITEMS cte
LEFT JOIN #stats stat
ON stat.item = cte.item
AND stat.yr = cte.yr
AND stat.grp = cte.grp
ORDER BY 1,2,3
item
yr
grp
amount
1
2020
1
249
1
2020
2
0
1
2020
3
70
1
2021
1
344
1
2021
2
34
1
2021
3
44
1
2022
1
0
1
2022
2
0
1
2022
3
0
2
2020
1
799
2
2020
2
0
2
2020
3
0
2
2021
1
850
2
2021
2
1260
2
2021
3
0
2
2022
1
0
2
2022
2
0
2
2022
3
0
Test on db<>fiddle here

SQL query to split and keep only the top N values

I have the following table data:
| name |items |
--------------------
| Bob |1, 2, 3 |
| Rick |5, 3, 8, 4|
| Bill |2, 4 |
I need to create a table with a split items column, but with the limitation to have at most N items per name. E.g. for N = 3 the table should look like this:
|name |item|
-----------
|Bob |1 |
|Bob |2 |
|Bob |3 |
|Rick |5 |
|Rick |3 |
|Rick |8 |
|Bill |2 |
|Bill |4 |

I have the following query that splits items correctly, but doesn't account for the maximum number N. What should I modify in the query (standard SQL, BigQuery) to account for N?
WITH data_split AS (
SELECT name, SPLIT(items,',') AS item
FROM (
SELECT name, items
-- A lot of additional logic here
FROM data
)
)
SELECT name, item
FROM data_split
CROSS JOIN UNNEST(data_split.item) AS item
You can try a more semi-standard way - works practically everywhere:
WITH
-- your input ...
indata(id,nam,items) AS ( -- need a sorting column "id" to keep the sort order
SELECT 1, 'Bob' ,'1,2,3' -- blanks after comma can irritate
UNION ALL SELECT 2, 'Rick','5,3,8,4' -- the splitting function below ...
UNION ALL SELECT 3, 'Bill','2,4'
)
-- real query starts here, replace comma below with "WITH" ...
,
-- exactly 3 integers
i(i) AS (
SELECT 1 -- need to add FROM DUAL , in Oracle, for example ...
UNION ALL SELECT 2
UNION ALL SELECT 3
)
SELECT
id
, nam
, SPLIT(items,',',i) AS item -- SPLIT_PART in other DBMS-s
FROM indata CROSS JOIN i
WHERE SPLIT_PART(items,',',i) <> ''
ORDER BY 1, 3
;
-- out id | nam | item
-- out ----+------+------
-- out 1 | Bob | 1
-- out 1 | Bob | 2
-- out 1 | Bob | 3
-- out 2 | Rick | 3
-- out 2 | Rick | 5
-- out 2 | Rick | 8
-- out 3 | Bill | 2
-- out 3 | Bill | 4
Consider below approach (BigQuery)
select name, trim(item) item
from your_table, unnest(split(items)) item with offset
where offset < 3
if applied to sample data in your question - output is

How to make MS SQL select on this

I have MS SQL database table like this
TableA
+----+-----------+--------+
|ID | Table2_FK | Value |
+----+-----------+--------+
|1 | 7 | X |
|2 | 7 | Y |
|3 | 8 | X |
|4 | 8 | Z |
|5 | 9 | W |
|6 | 9 | M |
|5 | 10 | X |
|6 | 10 | Z |
+----+-----------+--------+
I want to make query to get list of Table2_FKs if I pass X and Z in query for Values. In this example 8 and 10 is the result
It can be more than 2 values
You can do this with group by and having:
select table2_fk
from t
where value in ('X', 'Z')
group by table2_fk
having count(*) = 2;
If the values can be duplicated for a key value, then use count(distinct value) = 2. The "2" is the number of values in the IN list.
Try this:
select distinct Table2_FK
from TableA
where value in ('X','Z');
You can use query as below:
Select distinct table2_fk from (
Select *, Ct = count(id) over (partition by table2_fk) from yourtable
) a
Where a.[Value] in ('X','Z') and a.Ct >= 2
you can use a query like below
select
distinct Table2_FK
from TableA a
where exists (
select 1 1 from TableA b where b.value ='X' and a.Table2_FK =b.Table2_FK
)
and exists (
select 1 1 from TableA c where c.value ='Z' and a.Table2_FK =c.Table2_FK
)

Insert new colum with previous rows information

I have this table:
Id |Name |ParentId
1 |John |Null
2 |Oscar |1
3 |Peter |2
4 |Abbey |3
5 |Adrian |4
6 |Barbara |5
and i want to make a select that will give me a new column that gets the previous Name with by the parentId to make a listName (Order by ParentID).
the final result in this example would be this:
Id |Name |ParentId | List
1 |John |Null | John
2 |Oscar |1 | John-Oscar
3 |Peter |2 | John-Oscar-Peter
4 |Abbey |3 | John-Oscar-Peter-Abbey
5 |Adrian |4 | John-Oscar-Peter-Abbey-Adrian
6 |Barbara |5 | John-Oscar-Peter-Abbey-Adrian-Barbara
Thnks for all the help!
You can use a recursive CTE to produce the desired result:
declare #t table (Id int, Name varchar(20), ParentId int)
insert #t values
( 1 ,'John' ,Null ),
( 2 ,'Oscar' ,1 ),
( 3 ,'Peter' ,2 ),
( 4 ,'Abbey' ,3 ),
( 5 ,'Adrian' ,4 ),
( 6 ,'Barbara' ,5 )
;with x as (
select *, cast(name as varchar(1000)) as list from #t where parentid is null
union all
select t.id, t.name, t.parentid, cast(x.list+'-'+t.name as varchar(1000)) from #t t join x on t.parentid = x.id
)
select * from x
This also works for multiple roots, of course.
This is same as concatenate columns to rows
select id,name,pid,
stuff((select '-'+name from yourtable n2 where n2.id<=n1.id for xml path('')),1,1,'') b
from yourtable n1

Recursive SQL - count number of descendants in hierarchical structure

Consider a database table with the following columns:
mathematician_id
name
advisor1
advisor2
The database represents data from the Math Genealogy Project, where each mathematician usually has one single advisor, but there are situations when there are two advisors.
Visual aid to make things clearer:
How do I count the number of descendants for each of the mathematicians?
I should probably use Common Table Expressions (WITH RECURSIVE), but I am pretty much stuck at the moment. All the similar examples I found deal with hierarchies having only one parent, not two.
Update:
I adapted the solution for SQL Server provided by Vladimir Baranov to also work in PostgreSQL:
WITH RECURSIVE cte AS (
SELECT m.id as start_id,
m.id,
m.name,
m.advisor1,
m.advisor2,
1 AS level
FROM public.mathematicians AS m
UNION ALL
SELECT cte.start_id,
m.id,
m.name,
m.advisor1,
m.advisor2,
cte.level + 1 AS level
FROM public.mathematicians AS m
INNER JOIN cte ON cte.id = m.advisor1
OR cte.id = m.advisor2
),
cte_distinct AS (
SELECT DISTINCT start_id, id
FROM cte
)
SELECT cte_distinct.start_id,
m.name,
COUNT(*)-1 AS descendants_count
FROM cte_distinct
INNER JOIN public.mathematicians AS m ON m.id = cte_distinct.start_id
GROUP BY cte_distinct.start_id, m.name
ORDER BY cte_distinct.start_id
You didn't say what DBMS you use. I'll use SQL Server for this example, but it will work in other databases that support recursive queries as well.
Sample data
I entered only the right part of your tree, starting from Euler.
The most interesting part is the multiple paths between Lagrange and Dirichlet.
DECLARE #T TABLE (ID int, name nvarchar(50), Advisor1ID int, Advisor2ID int);
INSERT INTO #T (ID, name, Advisor1ID, Advisor2ID) VALUES
(1, 'Euler', NULL, NULL),
(2, 'Lagrange', 1, NULL),
(3, 'Laplace', NULL, NULL),
(4, 'Fourier', 2, NULL),
(5, 'Poisson', 2, 3),
(6, 'Dirichlet', 4, 5),
(7, 'Lipschitz', 6, NULL),
(8, 'Klein', NULL, 7),
(9, 'Lindemann', 8, NULL),
(10, 'Furtwangler', 8, NULL),
(11, 'Hilbert', 9, NULL),
(12, 'Taussky-Todd', 10, NULL);
This is how it looks like:
SELECT * FROM #T;
+----+--------------+------------+------------+
| ID | name | Advisor1ID | Advisor2ID |
+----+--------------+------------+------------+
| 1 | Euler | NULL | NULL |
| 2 | Lagrange | 1 | NULL |
| 3 | Laplace | NULL | NULL |
| 4 | Fourier | 2 | NULL |
| 5 | Poisson | 2 | 3 |
| 6 | Dirichlet | 4 | 5 |
| 7 | Lipschitz | 6 | NULL |
| 8 | Klein | NULL | 7 |
| 9 | Lindemann | 8 | NULL |
| 10 | Furtwangler | 8 | NULL |
| 11 | Hilbert | 9 | NULL |
| 12 | Taussky-Todd | 10 | NULL |
+----+--------------+------------+------------+
Query
It is a classic recursive query with two interesting points.
1) The recursive part of the CTE joins to the anchor part using both Advisor1ID and Advisor2ID:
INNER JOIN CTE
ON CTE.ID = T.Advisor1ID
OR CTE.ID = T.Advisor2ID
2) Since it is possible to have multiple paths to the descendant, recursive query may output the node several times. To eliminate these duplicates I used DISTINCT in CTE_Distinct. It may be possible to solve it more efficiently.
To understand better how the query works run each CTE separately and examine intermediate results.
WITH
CTE
AS
(
SELECT
T.ID AS StartID
,T.ID
,T.name
,T.Advisor1ID
,T.Advisor2ID
,1 AS Lvl
FROM #T AS T
UNION ALL
SELECT
CTE.StartID
,T.ID
,T.name
,T.Advisor1ID
,T.Advisor2ID
,CTE.Lvl + 1 AS Lvl
FROM
#T AS T
INNER JOIN CTE
ON CTE.ID = T.Advisor1ID
OR CTE.ID = T.Advisor2ID
)
,CTE_Distinct
AS
(
SELECT DISTINCT
StartID
,ID
FROM CTE
)
SELECT
CTE_Distinct.StartID
,T.name
,COUNT(*) AS DescendantCount
FROM
CTE_Distinct
INNER JOIN #T AS T ON T.ID = CTE_Distinct.StartID
GROUP BY
CTE_Distinct.StartID
,T.name
ORDER BY CTE_Distinct.StartID;
Result
+---------+--------------+-----------------+
| StartID | name | DescendantCount |
+---------+--------------+-----------------+
| 1 | Euler | 11 |
| 2 | Lagrange | 10 |
| 3 | Laplace | 9 |
| 4 | Fourier | 8 |
| 5 | Poisson | 8 |
| 6 | Dirichlet | 7 |
| 7 | Lipschitz | 6 |
| 8 | Klein | 5 |
| 9 | Lindemann | 2 |
| 10 | Furtwangler | 2 |
| 11 | Hilbert | 1 |
| 12 | Taussky-Todd | 1 |
+---------+--------------+-----------------+
Here DescendantCount counts the node itself as a descendant. You can subtract 1 from this result if you want to see 0 instead of 1 for the leaf nodes.
Here is SQL Fiddle.