SQL select parent-child recursively based on a reference table - sql

I saw many questions related to a recursive query but couldn't find any that shows how to use it based on a reference table.
I have a MasterTable where Id, ParentId columns are establishing the parent/child relation.
I have a SubTable where I have a bunch of Ids which could be a parent Id or child Id.
I would like to retrieve all related records (parent or child, recursively) from the MasterTable based on given SubTable
Current output:
id parentId
----------- -----------
1 NULL
2 1
3 1
4 NULL
5 4
6 5
7 6
Expected output
id parentId
----------- -----------
1 NULL
2 1
3 1
4 NULL
5 4
6 5
7 6
8 9
9 NULL
10 NULL
11 10
13 11
14 10
15 16
16 NULL
Comparison of actual vs expected:
Code:
DECLARE #MasterTable TABLE
(
id INT NOT NULL,
parentId INT NULL
);
DECLARE #SubTable TABLE
(
id INT NOT NULL
);
INSERT INTO #MasterTable (id, parentId)
VALUES (1, NULL), (2, 1), (3, 1), (4, NULL), (5, 4), (6, 5),
(7, 6), (8, 9), (9, NULL), (10, NULL), (11, 10), (12, NULL),
(13, 11), (13, 11), (14, 10), (15, 16), (16, NULL);
INSERT INTO #SubTable (id)
VALUES (1), (2), (3), (4), (6), (5), (7),
(8), -- it does not show
(13), -- it does not show
(15); -- it does not show
/* beside 8,13,15 it should add 9,11,14 and 10,16 */
;WITH cte AS
(
SELECT
mt1.id,
mt1.parentId
FROM
#MasterTable AS mt1
WHERE
mt1.parentId IS NULL
AND EXISTS (SELECT NULL AS empty
FROM #SubTable AS st
WHERE st.Id = mt1.id)
UNION ALL
SELECT
mt2.id,
mt2.parentId
FROM
#MasterTable AS mt2
INNER JOIN
cte AS c1 ON c1.id = mt2.parentId
)
SELECT DISTINCT
c2.id,
c2.parentId
FROM
cte AS c2
ORDER BY
id;

Is the following query suitable for the issue in question?
with
r as(
select
m.*, iif(m.parentid is null, 1, 0) p_flag
from #MasterTable m
join #SubTable s
on s.id = m.id
union all
select
m.*, iif(m.parentid is null, 1, r.p_flag)
from r
join #MasterTable m
on (r.p_flag = 1 and m.parentid = r.id) or
(r.p_flag = 0 and r.parentid = m.id)
)
select distinct
id, parentid
from r
order by id;
Output:
| id | parentid |
+----+----------+
| 1 | NULL |
| 2 | 1 |
| 3 | 1 |
| 4 | NULL |
| 5 | 4 |
| 6 | 5 |
| 7 | 6 |
| 8 | 9 |
| 9 | NULL |
| 10 | NULL |
| 11 | 10 |
| 13 | 11 |
| 14 | 10 |
| 15 | 16 |
| 16 | NULL |
Test it online with rextester.com.

;WITH cte
AS (
SELECT mt1.id,
mt1.parentId
FROM #MasterTable AS mt1
WHERE mt1.parentId IS NULL
UNION ALL
SELECT mt2.id,
mt2.parentId
FROM #MasterTable AS mt2
INNER JOIN cte AS c1
ON c1.id = mt2.parentId
)
SELECT DISTINCT c2.id,
c2.parentId
FROM cte AS c2
where
EXISTS (
SELECT 1 AS empty FROM #SubTable AS st
WHERE ( st.Id = c2.id or st.Id = c2.parentId)
)
or
EXISTS (
SELECT 1 AS empty FROM #MasterTable AS mt
WHERE ( c2.Id = mt.parentId or c2.parentId = mt.parentId)
)
ORDER BY id;

You may try this....
; with cte as(
select distinct mas.id, mas.parentId, iif(mas.parentid is null, 1, 0) PId
from #MasterTable mas inner join #SubTable sub
on sub.id in(mas.id, mas.parentid) ----- create top node having parentid is null
union all
select mas.id, mas.parentId, ct.PId
from cte ct inner join #MasterTable mas
on (ct.PId = 1 and mas.parentid = ct.id) or
(ct.PId = 0 and ct.parentid = mas.id) ----- create child node for correspoding parentid created above
)
select distinct id, parentid from cte order by id
option (MAXRECURSION 100); ---- Add Maxrecursion to prevent the infinite loop
You can find this link for more info on recursive query in SQL link. In this link see Example E or above.

Related

Join on existing aggregate query to pivot result without id

On a Sql-Server instance, I have three tables:
ActionItem
Id
Name
1
Fish
2
Gravy
3
Pants
ActionData
Id
ActionId
Group
Field
Value
1
1
1
1
100
2
1
1
2
200
3
1
1
3
300
4
1
1
4
NULL
5
1
1
5
NULL
6
1
2
6
"Some Text"
7
2
1
1
50
8
2
1
2
60
9
2
1
3
70
Costing
Id
ActionId
Break
Cost
1
1
Normal
11.3
2
1
Sub
54
3
1
Premium
0.4
4
3
Normal
22
5
3
Premium
0.67
I have a query that sums the cost for each ActionItem:
select
ai.Id,
ai.Name,
sum(c.Cost)
from ActionItem ai
left join Costing c on ai.Id = c.ActionId
group by
ai.Id,
ai.Name
Nice and straight-forward:
Id
Name
(No column name)
1
Fish
65.7
2
Gravy
NULL
3
Pants
22.67
I created a pivot too:
select * from
(select [ActionId], [Group], [Field], [Value] from ActionData) src
pivot (max([Value]) for [ActionId] in ([1],[2],[3],[4])) ppp
Which gets me data in the right format:
Group
Field
1
2
3
4
1
1
100
50
NULL
NULL
1
2
200
60
NULL
NULL
1
3
300
70
NULL
NULL
1
4
NULL
NULL
NULL
NULL
1
5
NULL
NULL
NULL
NULL
2
6
"Some Text"
NULL
NULL
NULL
But I cannot join these two queries together because that PIVOT doesn't contain the ActionId ... even though I use Select * from - how can I get the ActionId col to show on my pivoted data, so I can join it to the rest of my original query?
I could not get sqlfiddle.com to work for MS SQL SERVER today but here are create and inserts if anyone's interested:
CREATE TABLE ActionItem
([Id] int, [Name] varchar(5));
INSERT INTO ActionItem
([Id], [Name])
VALUES
(1, 'Fish'),
(2, 'Gravy'),
(3, 'Pants');
CREATE TABLE ActionData
([Id] int, [ActionId] int, [Group] int, [Field] int, [Value] varchar(11));
INSERT INTO ActionData
([Id], [ActionId], [Group], [Field], [Value])
VALUES
(1, 1, 1, 1, '100'),
(2, 1, 1, 2, '200'),
(3, 1, 1, 3, '300'),
(4, 1, 1, 4, NULL),
(5, 1, 1, 5, NULL),
(6, 1, 2, 6, '"Some Text"'),
(7, 2, 1, 1, '50'),
(8, 2, 1, 2, '60'),
(9, 2, 1, 3, '70')
;
CREATE TABLE Costing (
[Id] int,
[ActionId] int,
[Break] VARCHAR(9),
[Cost] FLOAT);
INSERT INTO Costing
([Id], [ActionId], [Break], [Cost])
VALUES
('1', '1', 'Normal', '11.3'),
('2', '1', 'Sub', '54'),
('3', '1', 'Premium', '0.4'),
('4', '3', 'Normal', '22'),
('5', '3', 'Premium', '0.67');
Not sure what output you expect.
But here's an attempt to join the two queries in 1 pivot.
select pvt.*
from
(
select d.ActionId, ai.Name
--, d.[Group]
, cast(d.[Field] as varchar(30)) as [Col]
, try_cast(d.[Value] as float) as [Value]
from ActionData d
left join ActionItem ai on ai.Id = d.ActionId
where isnumeric(d.[Value]) = 1
union all
select c.ActionId, ai.Name
--, 1 as [Group]
, c.[Break] as [Col]
, sum(c.Cost) as TotalCost
from Costing c
left join ActionItem ai
on ai.Id = c.ActionId
group by c.ActionId, ai.Name, c.[Break]
) src
pivot (
max([Value])
for [Col] in ([1],[2],[3],[4],[Normal],[Premium],[Sub])
) pvt
GO
ActionId | Name | 1 | 2 | 3 | 4 | Normal | Premium | Sub
-------: | :---- | ---: | ---: | ---: | ---: | -----: | ------: | ---:
1 | Fish | 100 | 200 | 300 | null | 11.3 | 0.4 | 54
2 | Gravy | 50 | 60 | 70 | null | null | null | null
3 | Pants | null | null | null | null | 22 | 0.67 | null
db<>fiddle here

Query Count Rows Join Oracle

I've been struggling trying to do this query, this is what I have/need:
Two tables, Table A and Table B, for each row in table A there might be N rows in table B. But I need to find the rows from A that have exactly two rows in B where one of them the type( column in B) starts with 'PYT' and the other one has a column null, also I need the amount from table B to be from the latest row( DATEP in table B) I've been trying to do it, but I've found some issues, this is what I have so far:
SELECT
A.TYPE, A.NMRAD,A.ID, B.AMOUNT
FROM
TABLE_A A
JOIN TABLE_B B ON A.ID = B.ID_A AND A.NMRAD = B.NMRAD AND A.TYPE = B.TYPE
WHERE
(SELECT COUNT(1) FROM (SELECT 1 FROM TABLE_B WHERE ID_A = A.ID AND TYPE LIKE 'PYT%'
UNION
SELECT 1 FROM TABLE_B WHERE ID_A = A.ID AND B.TYPEPROCESS IS NOT NULL))=2
WHERE A.TYPE=?
For example:
Table A
ID | NMRAD | TYPE
1 | 2 | PYT1
2 | 14 | PYT2
5 | 11 | PYY2
TABLE B
ID_A | NMRAD | TYPE | TYPEPROCESS | AMOUNT | DATEP
1 | 2 | PTY1 | NULL | 50 | 18/10/2018
1 | 2 | PYY2 | 123 | 35 | 19/10/2018
2 | 14 | PTY2 | NULL | 50 | 18/09/2018
2 | 14 | PTY2 | NULL | 35 | 17/10/2018
2 | 14 | PTY3 | NULL | 77 | 11/07/2018
EXPECTED RESULT
TYPE | NMRAD | ID | AMOUNT
PTY1 | 2 | 1 | 35
I could not match your "exactly two rows" criteria, but here is SQL that matches your expected results:
WITH
aset AS
(SELECT a.*
FROM tablea a
WHERE EXISTS
(SELECT NULL
FROM tableb b
WHERE a.id = b.id
AND a.nmrad = b.nmrad
AND a.TYPE = b.TYPE
AND b.TYPE LIKE 'PTY%')
AND EXISTS
(SELECT NULL
FROM tableb b
WHERE a.id = b.id
AND a.nmrad = b.nmrad
AND a.TYPE = b.TYPE
AND b.typeprocess IS NULL))
SELECT a.*
, (SELECT amount
FROM tableb b
WHERE a.id = b.id
AND a.nmrad = b.nmrad
AND datep = (SELECT MAX( datep )
FROM tableb bb
WHERE b.id = bb.id AND b.nmrad = bb.nmrad)) amount
FROM aset a;
This resulted in:
ID NMRAD TYPE AMOUNT
1 2 PTY1 35
Make it easy for us to help you, next time include a setup:
CREATE TABLE tablea
(
id NUMBER( 3 )
, nmrad NUMBER( 2 )
, TYPE VARCHAR2( 4 )
);
CREATE TABLE tableb
(
id NUMBER( 3 )
, nmrad NUMBER( 2 )
, TYPE VARCHAR2( 4 )
, typeprocess NUMBER( 3 )
, amount NUMBER( 3 )
, datep DATE
);
begin
insert into tablea values (1, 2, 'PTY1');
insert into tablea values (2, 14, 'PTY1');
insert into tablea values (5, 11, 'PTY1');
insert into tableb values (1, 2, 'PTY1', NULL, 50, to_date('18/10/2018', 'DD/MM/YYYY'));
insert into tableb values (1, 2, 'PYY2', 123, 35, to_date('19/10/2018', 'DD/MM/YYYY'));
insert into tableb values (2, 14, 'PTY2', NULL, 50, to_date('18/09/2018', 'DD/MM/YYYY'));
insert into tableb values (2, 14, 'PTY2', NULL, 35, to_date('17/10/2018', 'DD/MM/YYYY'));
insert into tableb values (2, 14, 'PTY3', NULL, 77, to_date('11/07/2018', 'DD/MM/YYYY'));
commit;
end;

sql data where not exists in a table and not duplicate

its a bit tricky please focus on my requirments, I have 2 tables , I want to get data from first table where not exists in the second AND the data in first column are not duplicate for sub id and child id.
example: I have this table
tab1
id subid childid
1 11 77
2 22 55
3 33 66
4 11 77
7 22 55
8 33 60
9 99 98
10 33 60
11 97 98
tab2
id
1
4
7
10
the first thing I want is the id in tab1 doesnt exists in tab2 which will be
2,3,8,9,11 however some of those id have duplicate subid and chilid so i have to exclude them therefore I should have id 3, 9, 11
I tried this query but it returne me also 3 ,9 ,11, 8 , I dont want 8 how to fix the query ?
select *
from tab1 a
where not exists (select 1 from tab2 b where a.id = b.id)
and a.sub_id in (select c.sub_id
from tab1 c
group by c.sub_id,c.evt_id
having count(1) = 1)
For multiple database vendors I think the easiest solution would be a couple of not exists queries:
select *
from tab1 a
where not exists (
select 1
from tab2 b
where a.id = b.id
)
and not exists (
select 1
from tab1 c
where c.sub_id = a.sub_id
and c.evt_id = a.evt_id
and c.id <> a.id
)
i think below query will work
select a.*
from tab1 a
where not exists (select 1 from tab2 b where a.id = b.id)
and not exists (select 1 from tab1 c
where c.sub_id = a.sub_id
and c.childid = a.childid
group by c.sub_id,childid
having count(*)> = 2
)
Just to add an approach using a CTE, you can first determine the unique childid,subid pairs and then join that table with your main table:
DB Fiddle
Schema (PostgreSQL v9.6)
create table tab1 (
id integer primary key unique not null
, subid integer not null
, childid integer not null
);
insert into tab1 (id,subid,childid) values (1, 11, 77);
insert into tab1 (id,subid,childid) values (2, 22, 55);
insert into tab1 (id,subid,childid) values (3, 33, 66);
insert into tab1 (id,subid,childid) values (4, 11, 77);
insert into tab1 (id,subid,childid) values (7, 22, 55);
insert into tab1 (id,subid,childid) values (8, 33, 60);
insert into tab1 (id,subid,childid) values (9, 99, 98);
insert into tab1 (id,subid,childid) values (10, 33,60);
insert into tab1 (id,subid,childid) values (11, 97 ,98);
create table tab2 (
id integer primary key unique not null
);
insert into tab2 (id) values (1);
insert into tab2 (id) values (4);
insert into tab2 (id) values (7);
insert into tab2 (id) values (10);
Query #1
with tab1_unique as (
select subid, childid, count(*) as duplicate
from tab1
group by subid, childid
having count(*) = 1
)
select *
from tab1 a
join tab1_unique u on a.subid = u.subid and a.childid = u.childid
where not exists (select 1 from tab2 b where a.id = b.id);
| id | subid | childid | subid | childid | duplicate |
| --- | ----- | ------- | ----- | ------- | --------- |
| 11 | 97 | 98 | 97 | 98 | 1 |
| 9 | 99 | 98 | 99 | 98 | 1 |
| 3 | 33 | 66 | 33 | 66 | 1 |
not exists should do this:
select t1.*
from (select t1.*, count(*) over (partition by subid, childid) as cnt
from tab1 t1
) t1
where not exists (select 1 from tab2 t2 where t2.id = t1.id) and
cnt = 1;
You can use not exists as well for the subid/childid with the assumption that the rows are unique in the first table. Without this assumption, window functions are best solution -- and possibly the best solution anyway.

Eliminating duplicate rows except one column with condition

I am having trouble trying to find an appropriate query(SQL-SERVER) for selecting records with condition however, the table I will be using has more than 100,000 rows and more than 20 columns.
So I need a code that satisfies the following condition:
1.)If [policy] and [plan] column is unique between rows then I will select that record
2.)If [policy] and [plan] return 2 or more rows then I will select the record which 'code' column isn't 999
3.)In some cases the unwanted rows may not have '999' in [code] column but may be other specifics
In other words, I would like to get row number 1,2,4,5,7.
Here is an example of what the table looks like
row #|policy|plan|code
-----------------------
1 | a | aa |111
-----------------------
2 | b | bb |112
-----------------------
3 | b | bb |999
-----------------------
4 | c | cc |111
-----------------------
5 | c | cc |112
-----------------------
6 | c | cc |999
-----------------------
7 | d | dd |999
-----------------------
I'm expecting to see something like
row #|policy|plan|code
-----------------------
1 | a | aa |111
-----------------------
2 | b | bb |112
-----------------------
4 | c | cc |111
-----------------------
5 | c | cc |112
-----------------------
7 | d | dd |999
-----------------------
Thank you in advance
This sounds like a prioritization query. You an use row_number():
select t.*
from (select t.*,
row_number() over (partition by policy, plan
order by code
) as seqnum
from t
) t
where seqnum = 1;
The expected output makes this a bit clearer:
select t.*
from (select t.*,
rank() over (partition by policy, plan
order by (case when code = 999 then 1 else 2 end) desc
) as seqnum
from t
) t
where seqnum = 1;
The OP wants all codes that are not 999 unless the only codes are 999. So, another approach is:
select t.*
from t
where t.code <> 999
union all
select t.*
from t
where t.code = 999 and
not exists (select 1
from t t2
where t2.policy = t.policy and t2.plan = t.plan and
t2.code <> 999
);
May be you want this (eliminate the last row if more than one)?
select t.*
from (select t.*
, row_number() over (partition by policy, plan
order by code desc
) AS RN
, COUNT(*) over (partition by policy, plan) AS RC
from t
) t
where RN > 1 OR RN=RC;
Output:
row policy plan code RN RC
1 1 a aa 111 1 1
2 2 b bb 112 2 2
3 5 c cc 112 2 3
4 4 c cc 111 3 3
5 7 d dd 999 1 1
CREATE TABLE #Table2
([row] int, [policy] varchar(1), [plan] varchar(2), [code] int)
;
INSERT INTO #Table2
([row], [policy], [plan], [code])
VALUES
(1, 'a', 'aa', 111),
(2, 'b', 'bb', 112),
(3, 'b', 'bb', 999),
(4, 'c', 'cc', 111),
(5, 'c', 'cc', 112),
(6, 'c', 'cc', 999),
(7, 'd', 'dd', 999)
;
with cte
as
(
select *,
row_number() over (partition by policy, [plan]
order by code
) as seqnum
from #Table2
)
select [row], [policy], [plan], [code] from cte where seqnum=1

SQL : how to find leaf rows?

i have a self related table myTable like :
ID | RefID
----------
1 | NULL
2 | 1
3 | 2
4 | NULL
5 | 2
6 | 5
7 | 5
8 | NULL
9 | 7
i need to get leaf rows on any depth
based on the table above, the result must be :
ID | RefID
----------
3 | 2
4 | NULL
6 | 5
8 | NULL
9 | 7
thank you
PS: the depth may vary , here is very small example
Try:
SELECT id,
refid
FROM mytable t
WHERE NOT EXISTS (SELECT 1
FROM mytable
WHERE refid = t.id)
DECLARE #t TABLE (id int NOT NULL, RefID int NULL);
INSERT #t VALUES (1, NULL), (2, 1), (3, 2), (5, NULL),
(6, 5), (4, NULL), (7, 5), (8, NULL), (9, 8), (10, 7);
WITH CTE AS
(
-- top level
SELECT id, RefID, id AS RootId, 0 AS CTELevel FROM #t WHERE REfID IS NULL
UNION ALL
SELECT T.id, T.RefID, RootId, CTELevel + 1 FROM #t T JOIN CTE ON T.RefID = CTE.id
), Leafs AS
(
SELECT
id, RefID, DENSE_RANK() OVER (PARTITION BY CTE.RootId ORDER BY CTELevel DESC) AS Rn
FROM CTE
)
SELECT
id, RefID
FROM
Leafs
WHERE
rn = 1
select ID, RefId
from myTable t1 left join myTable t2 on t1.ID = t2.RefID
where t2.RefID is null
try this:
SELECT *
FROM
my_table
WHERE
id NOT IN
(
SELECT DISTINCT
refId
FROM
my_table
WHERE
refId IS NOT NULL
)