Get root and top level from SQL tree - sql

I have a tree table. And, I am going to get root and top level on this tree.
Help with the solution you can use anything you want
declare #disc table (
id int,
parent int,
label varchar(50)
)
insert into #disc
select *
from (
values (1, null, 'q_1'),
(2, 1, 'a_1_1'),
(3, 2, 'a_1_1_1'),
(4, 1, 'a_1_2'),
(5, null, 'q_5'),
(6, 5, 'a_5_1'),
(7, 5, 'a_5_2')
) x (id, parent, label);
1. q_1
2. a_1_1
3. a_1_1_1
4. a_1_2
5. q_5
6. a_5_1
7. a_5_2
And, my result should be like this:
1: 1, null, q_1
2: 2, 1, a_1_1
3: 5, null, q_5
4: 6, 5, a_5_1
or
1: 1, null, q_1
2: 5, null, q_5
3: 2, 1, a_1_1
4: 6, 5, a_5_1
I only found one way, but I believe there is a better solution:
with rec as (
select id, parent, label,
row_number() over(order by id) rnk,
1 lvl
from #disc
where parent is null
union all
select d.id, d.parent, d.label,
row_number() over(order by d.id) rnk,
r.lvl + 1
from rec r
join #disc d on r.id = d.parent
)
select *
from rec
where parent is null or (rnk = 1 and lvl = 2)

If I understand this, the parent value will be null in the root nodes. The next level down will have a root node as parent. So ...
;with roots as
(
select id, parent, label
from #disc
where parent is null
)
select id, parent, label
from roots
union
select id, parent, label
from #disc
where parent in (select id from roots)

It doesn't look like you actually want to recurse here.
You can just do a self-join inside an apply.
select
row_number() over (order by isnull(c.parent, c.id), c.id),
c.id,
c.parent,
c.label
from #disc p
cross apply (
select p.id, p.parent, p.label
union all
select top 1 c.id, c.parent, c.label
from #disc c
where p.id = c.parent
order by c.id
) c
where p.parent is null;
db<>fiddle

Related

Where clause on Running total

I have this table which stores containers by region and the number of coffee pouches in each of the containers.
if object_id( 'dbo.Container' ) is not null
drop table dbo.Container
go
create table dbo.Container
(
Id int not null,
Region int not null,
NumberOfCoffeePouches int not null,
constraint pkc_Container__Id primary key clustered(Id asc)
)
go
insert into dbo.Container
( Id , Region , NumberOfCoffeePouches )
values
( 1, 1, 10 ),
( 2, 1, 30 ),
( 3, 1, 5),
( 4, 1, 7),
( 5, 1, 1),
( 6, 1, 3),
( 7, 2, 4),
( 8, 2, 4),
( 9, 2, 4)
I need to list out the container Ids that will be used to fulfill an order of, say 50, coffee pouches. Over supplying is OK.
Here is query I have come up with
declare #RequiredCoffeePouches int = 50
select
sq2.Id,
sq2.NumberOfCoffeePouches,
sq2.RunningTotal,
sq2.LagRunningTotal
from
(
select
sq1.Id,
sq1.NumberOfCoffeePouches,
sq1.RunningTotal,
lag(sq1.RunningTotal, 1, 0) over (order by sq1.Id asc)
as 'LagRunningTotal'
from
(
select
c.Id,
c.NumberOfCoffeePouches,
sum(c.NumberOfCoffeePouches)
over (order by c.Id asc) as 'RunningTotal'
from
dbo.Container as c
where
c.Region = 1
) as sq1
) as sq2
where
sq2.LagRunningTotal <= #RequiredCoffeePouches
It gives the expected result
Id NumberOfCoffeePouches RunningTotal LagRunningTotal
----------- --------------------- ------------ ---------------
1 10 10 0
2 30 40 10
3 5 45 40
4 7 52 45
Question:
Is there a better and more optimized way to achieve this?
Specially the Container table is very large table and I think the sub query sq1 will unnecessarily calculate the RunningTotals for all the containers in the region. I was wondering if there is anyway to have sq1 stop processing more rows once the RunnningTotal exceeds over the #RequiredCoffeePouches.
Two things:
Moving your WHERE clause inside of the relevant sub-select can greatly increase the speed of the query because it'll pull less data. Using your example:
SELECT
sq2.Id,
sq2.NumberOfCoffeePouches,
sq2.RunningTotal,
sq2.LagRunningTotal
FROM
(
SELECT
sq1.Id,
sq1.NumberOfCoffeePouches,
sq1.RunningTotal,
lag(sq1.RunningTotal, 1, 0) over (order by sq1.Id asc) AS 'LagRunningTotal'
FROM
(
SELECT
c.Id,
c.NumberOfCoffeePouches,
SUM(c.NumberOfCoffeePouches) OVER (order by c.Id asc) AS 'RunningTotal'
FROM dbo.Container AS c
WHERE c.Region = 1
) AS sq1
WHERE sq2.LagRunningTotal <= #RequiredCoffeePouches
) AS sq2
CTEs can also improve performance:
;WITH sql1CTE AS (
SELECT
c.Id,
c.NumberOfCoffeePouches,
SUM(c.NumberOfCoffeePouches) OVER (order by c.Id asc) AS 'RunningTotal'
FROM dbo.Container AS c
WHERE c.Region = 1
),
sql2CTE AS (
SELECT
Id,
NumberOfCoffeePouches,
RunningTotal,
lag(RunningTotal, 1, 0) over (order by Id asc) AS 'LagRunningTotal'
FROM sql1CTE
WHERE LagRunningTotal <= #RequiredCoffeePouches
)
SELECT
Id,
NumberOfCoffeePouches,
RunningTotal,
LagRunningTotal
FROM sql2CTE
SQL Server CTE Basics
If you're using SSMS, select "Include Client Statistics" and "Include Actual Execution Plan" to keep track of how your query performs while you're crafting it.

Select Parent having null and not null child

Given 3 tables like:
[Table_Main] ----> [Table_Sub] ----> [Table_Prop]
1-N 0-N
I want to select item in [Table_Main] that :
- Have multiple [Table_Sub].
- with [Table_Sub] lines that have both [Table_Prop] and haven't.
To select those value I use :
SELECT Table_Main.Field_ID
FROM Table_Main
INNER JOIN Table_Sub on Table_Main.Field_ID = Table_Sub.Table_Main_Field_ID
LEFT JOIN Table_Prop on Table_Sub.Field_ID = Table_Prop.Table_Sub_Field_ID
If we rename table Family, Child and Pet. I need family where some childs has pet(s) but some child doesn't.
Family: Id, Name
1, Foo -- Family with 2 childs, one of them has a pet
2, Bar -- Family with 2 childs, 0 pet
3, Abc -- Family with 2 childs, both have pet
Child: Id, Family_Id, Name
1, 1, John -- Child of Foo
2, 1, Joe -- Child of Foo
3, 2, Jane
4, 2, Jessica
5, 3, XXX
6, 3, YYY
Pet: Id, Child_Id, Name
1, 2, FooBar -- Joe's pet
2, 5, Huey
3, 6, Dewey
Expected Result:
1, Foo
Family with less than 2 childs is exclude from the exemple has they can satisfy both constraint:
- Has a child with a pet
- Has a child with no pet.
Table Creation :
CREATE TABLE Family(
1 INTEGER NOT NULL PRIMARY KEY
,Foo VARCHAR(20) NOT NULL
);
INSERT INTO Family(1,Foo) VALUES (1,'Foo');
INSERT INTO Family(1,Foo) VALUES (2,'Bar');
INSERT INTO Family(1,Foo) VALUES (3,'Abc');
CREATE TABLE Child(
Id INTEGER NOT NULL PRIMARY KEY
,Family_Id INTEGER NOT NULL
,Name VARCHAR(20) NOT NULL
);
INSERT INTO Child(Id,Family_Id,Name) VALUES (1,1,'John');
INSERT INTO Child(Id,Family_Id,Name) VALUES (2,1,'Joe');
INSERT INTO Child(Id,Family_Id,Name) VALUES (3,2,'Jane');
INSERT INTO Child(Id,Family_Id,Name) VALUES (4,2,'Jessica');
INSERT INTO Child(Id,Family_Id,Name) VALUES (5,3,'XXX');
INSERT INTO Child(Id,Family_Id,Name) VALUES (6,3,'YYY');
CREATE TABLE Pet(
Id INTEGER NOT NULL PRIMARY KEY
,Family_I INTEGER NOT NULL
,Name VARCHAR(20) NOT NULL
);
INSERT INTO Pet(Id,Family_Id,Name) VALUES (1,2,'FooBar');
INSERT INTO Pet(Id,Family_Id,Name) VALUES (2,5,'Huey');
INSERT INTO Pet(Id,Family_Id,Name) VALUES (3,6,'Dewey');
This will give you desired result.
;with family as
(
select 1 FamilyID, 'Foo' Family union select 2, 'Bar' union select 3, 'ABC'
), child as
(
select 1 ChildID, 1 FamilyID ,'John' ChildName union
select 2, 1, 'Joe' union
select 3, 2, 'Jane' union
select 4, 2, 'Jessica' union
select 5, 3, 'XXX'union
select 6, 3, 'YYY'
), pets as
(
select 1 petid , 2 childid, 'FooBar' pet union
select 2, 5, 'Huey' union
select 3, 6, 'Dewey'
)
SELECT T.FamilyID, Max(Family) Family, MIN(CNT) [Min] , MAX(CNT) [Max] FROM
(
SELECT f.FamilyID, C.ChildID, SUM(case when petid is null then 0 else 1 end) CNT FROM Family F
JOIN Child C ON F.FamilyID = C.FamilyID
LEFT JOIN Pets P ON C.ChildID = P.ChildID
GROUP BY F.FamilyID, C.ChildID
) T JOIN Family F on T.FamilyID = F.FamilyID GROUP BY T.FamilyID
HAVING MIN(CNT) = 0 AND MAX(CNT) > 0
Query
select family.ID, family.name
from family
left join child on family.ID = child.family_id
left join pet on pet.child_ID = child.Id
group by family.name,family.ID
having count(child.id) > 1 and count( pet.id) <>0 and count(child.id) > count( pet.id)
Output
looks like you are close but if I understand right:
With parent as (
select 'Charlie' name from dual union all
select 'Ben' name from dual union all
select 'Bob' name from dual union all
select 'Harry' name from dual
)
,child as (
select 'Ben' parentname, 'Bebbie' name from dual union all
select 'Ben' parentname, 'Tilda' name from dual union all
select 'Bob' parentname, 'Shara' name from dual union all
select 'Bob' parentname, 'Sandra' name from dual
)
,pet as (
select 'Tilda' childname, 'Dog' pet from dual union all
select 'Tilda' childname, 'Cat' pet from dual union all
select 'Shara' childname, 'Bird' pet from dual union all
select 'Shara' childname, 'Snake' pet from dual
)
select pa.name,ch.name,count(pe.pet)
from parent pa
inner join child ch on ch.parentname = pa.name
left join pet pe on pe.childname = ch.name
group by pa.name,ch.name

Build AST with recursive CTE in Postgres

Given following table:
create table tree
(
id int,
parent_id int REFERENCES tree(id),
operator varchar,
primary key(id)
);
insert into tree values
(1, null, 'AND'),
(2, 1, 'NOT'),
(3, 1, 'OR'),
(4, 2, 'AND'),
(5, 3, 'Y'),
(6, 3, 'Z'),
(7, 4, 'K'),
(8, 4, 'OR'),
(9, 8, 'X'),
(10, 8, 'A'),
(11, 8, 'B')
;
how could I compute final AST: AND(NOT(AND(K, OR(X, A, B))), OR(Y, Z))
I tried different approaches with recursive CTE, but my problem is that CTE neither allows aggregations in recursive part of CTE nor subqueries where CTE is used.
Latest thing I tried was this:
with RECURSIVE tree1(id, parent_id, operator, n, cc) as (
select t.*, 0, cc.cc
from tree t, lateral(select count(*) as cc from tree tt where tt.parent_id = t.id) cc
where t.parent_id is null
union ALL
select t.*, n + 1, cc.cc
FROM tree t INNER JOIN tree1 a on t.parent_id = a.id, lateral(select count(*) as cc from tree tt where tt.parent_id = t.id) cc
), tree2(id, parent_id, operator, n, cc) AS (
select t.id, t.parent_id, t.operator, t.n, t.cc
from tree1 t
where t.n = (select max(n) from tree1)
union (
select p.id, p.parent_id, p.operator || '(' || string_agg(c.operator, ', ') || ')' as operator, p.n, p.cc
from tree1 p, tree2 c
where p.id = c.parent_id
group by p.id, p.parent_id, p.operator, p.n, p.cc
union
select p.id, p.parent_id, p.operator, p.n, p.cc
from tree1 p, tree2 c
where p.cc = 0 and p.n + 1 = c.n
)
)
select * from tree2
but it didn't work due to limitations of CTE.
The docs says CTE is Turing complete yet I can't find way to compute desired result. Am I missing something or is my understanding of Turing completeness wrong? :)
(I have Postgres 9.6)

recursive query Sort

I have a Standard table, which sotres parent, child category relationship...like this.
id, parent, catName, sort
And I use the following query to create a recursive tree
;WITH cte AS (
SELECT 0 AS lvl, id, catName, parent,levels,sort,
CAST(id AS VARCHAR(128)) AS path
FROM CategoriesMap WHERE parent =0
UNION ALL
SELECT p.lvl + 1, c.id, c.catName, c.parent,c.levels,c.sort,
CAST(p.path + '_' + CAST(c.id AS VARCHAR) AS VARCHAR(128))
FROM CategoriesMap c
INNER JOIN cte p ON p.id = c.parent
)
SELECT
id,
catName AS catName,
lvl,
levels,
path,
parent,
sort
FROM cte
ORDER BY path
And the output is like this Image:
Look for the Row with value ASP.NET & CLASSIC ASP, these are the last leaf(children) for the technology > Software (parents), I want to sort the LAST CHILDREN of any given parent (last parent). I can have multiple parents for a given node (last child) & all I care about is sort the LAST Children (leaf) using the "Sort" column.
so basicly "Classic Asp" shoud be before "Asp.Net" (Last Column is SORT column in my image).
My query is fine, it returns the results as expected...only challenege is i want to SORT the last NODE using the SORT column in table, last node can have 3 or 4 children which I want to sort, all nodes above the last node are its parents (which are in correct order already).
I want output like this.... Internet > ISP's > CableVision (1) :
Verizon (2) as you can see CableVision & Verizon have Sort Value of 1
& then 2, Now lets say we have Shopping > Coupons > Macys(0) : Sears
(2), same thing....I want Macys & Sears to be sorted...and its pretty
obvious their parents are Shopping > Coupons.
#Richard aka cyberkiwi, after applying your code, my sorting for Categories table is very random. output is below
Delaying the calculation of the path for one level, so the final result set has the parent path (ppath) available:
;WITH cte AS (
SELECT 0 AS lvl, id, catName, parent,levels,sort,
CAST('' AS VARCHAR(128)) AS ppath
FROM CategoriesMap WHERE parent =0
UNION ALL
SELECT p.lvl + 1, c.id, c.catName, c.parent,c.levels,c.sort,
CAST(p.ppath + '_' + CAST(p.id AS VARCHAR) AS VARCHAR(128))
FROM CategoriesMap c
INNER JOIN cte p ON p.id = c.parent
)
SELECT
id,
catName,
lvl,
levels,
CAST(ppath + '_' + CAST(id AS VARCHAR) AS VARCHAR(128)) AS path,
parent,
sort
FROM cte
ORDER BY
CASE WHEN sort IS NULL
THEN path
ELSE ppath
END
, sort ;
Not really sure why the above gives error. This will not:
ORDER BY
CASE WHEN sort IS NULL
THEN CAST(ppath + '_' + CAST(id AS VARCHAR) AS VARCHAR(128))
ELSE ppath
END
, sort ;
This SQL Fiddle should give you what you need.
The trick really is when you mix leaves with branches. In my solution, leaves ALWAYS appear before branches, and within the leaves (even when inter-mixed with branches), they are sorted by the sort column of course.
DDL
create table CategoriesMap(
id int, parent int, catname varchar(20), sort int);
insert CategoriesMap select
1, 0, 'Activities', null union all select
2, 0, 'Property', null union all select
3, 2, 'For rent', null union all select
4, 2, 'For sale', null union all select
12, 0, 'Technology', 3 union all select
15, 12, 'Hardware', null union all select
21, 12, 'Phones', null union all select
22, 15, 'Computers', null union all select
18, 12, 'Software', null union all select
19, 18, 'Asp.net', 2 union all select
20, 18, 'SQL', 3 union all select
23, 18, 'Php', 4 union all select
24, 18, 'Classic ASP', 1;
Query
;WITH leaves AS (
SELECT A.id
FROM CategoriesMap A
LEFT JOIN CategoriesMap B ON A.id=B.parent
WHERE B.id is null
)
,cte AS (
SELECT 0 AS lvl, id, catName, parent,sort,
CAST(id AS VARCHAR(MAX)) AS path,
'/'+CAST(id AS VARCHAR(MAX))+'/' AS hier
FROM CategoriesMap
WHERE parent =0
UNION ALL
SELECT p.lvl + 1, c.id, c.catName, c.parent,c.sort,
p.path + '_' + CAST(c.id AS VARCHAR(MAX)),
p.hier + CAST(c.id AS VARCHAR(MAX)) + '/'
FROM CategoriesMap c
JOIN cte p ON p.id = c.parent
)
SELECT c.id,
c.catName,
c.lvl,
--levels,
c.path,
--c.hier,
c.parent,
c.sort
FROM cte c
LEFT JOIN leaves l on l.id=c.id
ORDER BY CASE WHEN l.id is null
then cast(hier as hierarchyid)
else cast(hier as hierarchyid).GetAncestor(1)
END,
CASE WHEN l.id is null then 0 else 1 end,
sort

Recursive query to check that all parents are enabled

I have a CMS system which has a sitemap table with a parent-child relationship and a content table. Sometimes I don't want to include content in queries if it's corresponding sitemap entry or any of its parents is disabled.
The basic table structure is:
tb_Sitemap: id, parent_id, enabled
tb_Content: id, sitemap_id
So I want to be able to add something to my queries like this:
SELECT * FROM tb_Content WHERE {tb_Sitemap.enabled and any or all parents are also enabled}
I know I need to use a CTE but I am unsure about how to add these to a WHERE clause or how to go about it.
I am guessing I need do something like, but not sure how to add to a WHERE clause:
;WITH cte (enabled)
AS
(
SELECT enabled FROM tb_Content WHERE id = tb_Content.sitemap_id
UNION ALL
SELECT CASE WHEN b.enabled != 1 THEN 0 ELSE a.enabled FROM tb_Sitemap a
INNER JOIN cte b ON a.parent_id = b.id
)
SELECT enabled FROM cte
Sample data:
tb_Sitemap
id: 1, parent_id: null, enabled: 1
id: 2, parent_id: 1, enabled: 1
id: 3, parent_id: 2, enabled: 1
id: 4, parent_id: 1, enabled: 0
id: 5, parent_id: 4, enabled: 1
id: 6, parent_id: 5, enabled: 1
tbl_Content
sitemap_id: 3 (this would appear because sitemap_id:3 is enabled as is all of its parents)
sitemap_id: 6 (this will not appear because although sitemap_id:6 is enabled, one of its parents is not)
-- A little test data.
declare #tb_Sitemap as table ( id int, parent_id int null, enabled bit )
insert into #tb_Sitemap ( id, parent_id, enabled ) values
( 1, NULL, 1 ), ( 2, 1, 1 ), ( 3, 2, 1 ),
( 4, 1, 0 ), ( 5, 4, 1 ), ( 6, 5, 1 )
declare #tb_Content as table ( sitemap_id int )
insert into #tb_Content ( sitemap_id ) values ( 3 ), ( 6 )
-- Query the little beggars.
; with CTE as (
-- Start at the root(s).
select id, parent_id, enabled, enabled as summary_enabled
from #tb_Sitemap
where parent_id is NULL
union all
-- Add one generation at a time.
select S.id, s.parent_id, s.enabled, cast( case when s.enabled = 1 and CTE.summary_enabled = 1 then 1 else 0 end as bit )
from CTE inner join
#tb_Sitemap as S on S.parent_id = CTE.id
)
select *, case when summary_enabled = 1 and sitemap_id is not NULL then '< winner!' else '' end as include
from CTE left outer join
#tb_Content as C on C.sitemap_id = CTE.id