How to write this recursive CTE for SQL Server? - sql

For simplicity my schema:
Folders table (FolderId, ParentFolderId)
DeviceFolderProperties table (FolderId, LogDataRetentionDaysEvent)
Not every folder has a retention day. However this is an inherited value. How can you write something in SQL to return every folder and its retention day and if that is null its inherited value.
There are multiple levels to inheritance, so it will need to walk the tree.
This is what I have tried:
;
WITH [cte]
AS
(
SELECT f.FolderId, f.ParentFolderId, dfp.LogDataRetentionDaysEvent
FROM [Folders] f
LEFT JOIN DeviceFolderProperties dfp
ON f.FolderId = dfp.FolderId
),
[cte_collapse]
AS --recurse where r days is null
(
SELECT c.FolderId, c.ParentFolderId, c.LogDataRetentionDaysEvent
FROM [cte] c
WHERE c.LogDataRetentionDaysEvent IS NULL
UNION ALL
SELECT c.FolderId, c.ParentFolderId, ISNULL(c.LogDataRetentionDaysEvent, cc.LogDataRetentionDaysEvent)
FROM [cte] c
JOIN [cte_collapse] cc ON cc.FolderId = c.ParentFolderId
)
SELECT
*
FROM
[cte_collapse]

You could write this as:
with
data as (
select f.FolderID, f.ParentFolderId, dfp.LogDataRetentionDaysEvent
from Folders f
left join DeviceFolderProperties dfp on dfp.FolderID = f.FolderID
),
cte as (
select d.*, FolderID OriginalFolderId
from data d
union all
select d.*, c.OriginalFolderId
from cte c
inner join data d on d.FolderID = c.ParentFolderId
where c.LogDataRetentionDaysEvent is null
)
select OriginalFolderId, max(LogDataRetentionDaysEvent) LogDataRetentionDaysEvent
from cte
group by OriginalFolderId
We start by generating a derived table that contains information from both tables. Then, for each record, the recursive query climbs up the hierarchy, searching for a non-null the retention at each level. The trick is to stop as soon as a match is met.

Related

Use Exists with a Column of Query Result?

I have 2 tables.
One is bom_master:
CHILD
PARENT
1-111
66-6666
2-222
77-7777
2-222
88-8888
3-333
99-9999
Another one is library:
FileName
Location
66-6666_A.step
S:\ABC
77-7777_C~K1.step
S:\DEF
And I want to find out if the child's parents have related files in the library.
Expected Result:
CHILD
PARENT
FileName
1-111
66-6666
66-6666_A.step
2-222
77-7777
77-7777_C~K1.step
Tried below lines but return no results. Any comments? Thank you.
WITH temp_parent_PN(parentPN)
AS
(
SELECT
[PARENT]
FROM [bom_master]
where [bom_master].[CHILD] in ('1-111','2-222')
)
SELECT s.[filename]
FROM [library] s
WHERE EXISTS
(
SELECT
*
FROM temp_parent_PN b
where s.[filename] LIKE '%'+b.[parentPN]+'%'
)
If you have just one level of dependencies use the join solution proposed by dimis164.
If you have deeper levels you could use recursive queries allowed by WITH clause (
ref. WITH common_table_expression (Transact-SQL)).
This is a sample with one more level of relation in bom_master (you could then join the result of the recursive query with library as you need).
DECLARE #bom_master TABLE (Child NVARCHAR(MAX), Parent NVARCHAR(MAX));
INSERT INTO #bom_master VALUES
('1-111', '66-666'),
('2-222', '77-777'),
('3-333', '88-888'),
('4-444', '99-999'),
('A-AAA', '1-111');
WITH
leaf AS ( -- Get the leaf elements (elements without a child)
SELECT Child FROM #bom_master b1
WHERE NOT EXISTS (SELECT * FROM #bom_master b2 WHERE b2.Parent = b1.Child) ),
rec(Child, Parent, Level) AS (
SELECT b.Child, b.Parent, Level = 1
FROM #bom_master b
JOIN leaf l ON l.Child = b.Child
UNION ALL
SELECT rec.Child, b.Parent, Level = rec.Level + 1
FROM rec
JOIN #bom_master b
ON b.Child = rec.Parent )
SELECT * FROM rec
I think you don't have to use exists. The problem is that you need to substring to match the join.
Have a look at this:
SELECT b.CHILD, b.PARENT, l.[FileName]
FROM [bom_master] b
INNER JOIN [library] l ON b.PARENT = SUBSTRING(l.FileName,1,7)

Why Row_Number in a view gives a nullable column

I have a view using a CTE and I want use a row number to simulate a key for my edmx in Visual Studio
ALTER VIEW [dbo].[ViewLstTypesArticle]
AS
WITH cte (IdTypeArticle, IdTypeArticleParent, Logo, Libelle, FullLibelle, Racine) AS
(
SELECT
f.Id AS IdTypeArticle, NULL AS IdParent,
f.Logo, f.Libelle,
CAST(f.Libelle AS varchar(MAX)) AS Expr1,
f.Id AS Racine
FROM
dbo.ArticleType AS f
LEFT OUTER JOIN
dbo.ArticleTypeParent AS p ON p.IdTypeArticle = f.Id
WHERE
(p.IdTypeArticleParent IS NULL)
AND (f.Affichable = 1)
UNION ALL
SELECT
f.Id AS IdTypeArticle, p.IdTypeArticleParent,
f.Logo, f.Libelle,
CAST(parent.Libelle + ' / ' + f.Libelle AS varchar(MAX)) AS Expr1,
parent.Racine
FROM
dbo.ArticleTypeParent AS p
INNER JOIN
cte AS parent ON p.IdTypeArticleParent = parent.IdTypeArticle
INNER JOIN
dbo.ArticleType AS f ON f.Id = p.IdTypeArticle
)
SELECT
*,
ROW_NUMBER() OVER (ORDER BY FullLibelle) AS Id
FROM
(SELECT
IdTypeArticle, IdTypeArticleParent, Logo, Libelle,
FullLibelle, Racine
FROM cte) AS CTE1
When I look in properties of column I see Id bigint ... NULL
And my edmx exclude this view cause don't find a column can be used to key
When I execute my view ID have no null. I've all my row number.
If someone encounter this problem and resolved it ... Thanks
SQL Server generally thinks that columns are NULL-able in views (and when using SELECT INTO).
You can convince SQL Server that this is not the case by using ISNULL():
select *,
ISNULL(ROW_NUMBER() over(ORDER BY FullLibelle), 0) as Id
from . . .
Note: This works with ISNULL() but not with COALESCE() which otherwise has very similar functionality.

How to convert list of comma separated Ids into their name?

I have a table that contains:
id task_ids
1 10,15
2 NULL
3 17
I have the table that has the names of this tasks:
id task_name
10 a
15 b
17 c
I want to generate the following output
id task_ids task_names
1 10,15 a,b
2 null null
3 17 c
I know this structure isn't ideal but this is legacy table which I will not change now.
Is there easy way to get the output ?
I'm using Presto but I think this can be solved with native sql
WITH data AS (
SELECT * FROM (VALUES (1, '10,15'), (2, NULL)) x(id, task_ids)
),
task AS (
SELECT * FROM (VALUES ('10', 'a'), ('15', 'b')) x(id, task_name)
)
SELECT
d.id, d.task_ids
-- array_agg will obviously capture NULL task_name comping from LEFT JOIN, so we need to filter out such results
IF(array_agg(t.task_name) IS NOT DISTINCT FROM ARRAY[NULL], NULL, array_agg(t.task_name)) task_names
FROM data d
-- split task_ids by `,`, convert into numbers, UNNEST into separate rows
LEFT JOIN UNNEST (split(d.task_ids, ',')) AS e(task_id) ON true
-- LEFT JOIN with task to pull the task name
LEFT JOIN task t ON e.task_id = t.id
-- aggregate back
GROUP BY d.id, d.task_ids;
You have a horrible data model, but you can do what you want with a bit of effort. Arrays are better than strings, so I'll just use that:
select t.id, t.task_id, array_agg(tt.task_name) as task_names
from t left join lateral
unnest(split(t.task_ids, ',')) u(task_id)
on 1=1 left join
tasks tt
on tt.task_id = u.task_id
group by t.id, t.task_id;
I don't have Presto on hand to test this. But this or some minor variant should do what you want.
EDIT:
This version might work:
select t.id, t.task_id,
(select array_agg(tt.task_name)
from unnest(split(t.task_ids, ',')) u(task_id) join
tasks tt
on tt.task_id = u.task_id
) as task_names
from t ;

Confusing use of cte ,inner join and union all

I am confused about the use of inner join on the CTE here. what is in a as appears in the inner join at the end and what is in cte1 c?
WITH cte1 AS
(SELECT id,geographyname,
OriginalGoals,
ParentGeographyname,
0 AS HierarchyLevel,
paradigm
FROM businessobject_RefinementMaster
WHERE Id = #Geo
UNION ALL
SELECT a.id,
a.geographyname,
a.OriginalGoals,
a.ParentGeographyName,
HierarchyLevel-1 AS HierarchyLevel,
a.paradigm
FROM businessobject_RefinementMaster a
INNER JOIN cte1 c ON c.ParentGeographyname = a.geographyname
AND c.paradigm=a.paradigm )
what will be the result of this query?
This is a recursive CTE (hidden-RBAR). I'll try to comment it in a way, that you can understand, what is going on:
WITH cte1 AS
(
/*
This is called "anchor" and reads the "head" lines of a hierarchy
*/
SELECT id,
geographyname,
OriginalGoals,
ParentGeographyname,
0 AS HierarchyLevel, --obviously this starts with a "0"
paradigm
FROM businessobject_RefinementMaster --The source-table
WHERE Id = #Geo --You read elements with Id=#Geo. This is - probably - one single element
--The next SELECT will be "added" to the result-set
UNION ALL
/*
The column-list must be absolutely the same (count and type) of the anchor
*/
SELECT a.id,
a.geographyname,
a.OriginalGoals,
a.ParentGeographyName,
HierarchyLevel-1 AS HierarchyLevel, --this is simple counting. Started with 0 this will lead to -1, -2, -3...
a.paradigm
FROM businessobject_RefinementMaster a --same source-table as above
INNER JOIN cte1 c ON c.ParentGeographyname = a.geographyname --Find rows where the name of the element is the parent-name of the former element
AND c.paradigm=a.paradigm
)
/*
Return the result-set
*/
SELECT * FROM cte1
The result should be a full recursive list of parents to a given element.

PostgreSQL - how to query "result IN ALL OF"?

I am new to PostgreSQL and I have a problem with the following query:
WITH relevant_einsatz AS (
SELECT einsatz.fahrzeug,einsatz.mannschaft
FROM einsatz
INNER JOIN bergefahrzeug ON einsatz.fahrzeug = bergefahrzeug.id
),
relevant_mannschaften AS (
SELECT DISTINCT relevant_einsatz.mannschaft
FROM relevant_einsatz
WHERE relevant_einsatz.fahrzeug IN (SELECT id FROM bergefahrzeug)
)
SELECT mannschaft.id,mannschaft.rufname,person.id,person.nachname
FROM mannschaft,person,relevant_mannschaften WHERE mannschaft.leiter = person.id AND relevant_mannschaften.mannschaft=mannschaft.id;
This query is working basically - but in "relevant_mannschaften" I am currently selecting each mannschaft, which has been to an relevant_einsatz with at least 1 bergefahrzeug.
Instead of this, I want to select into "relevant_mannschaften" each mannschaft, which has been to an relevant_einsatz WITH EACH from bergefahrzeug.
Does anybody know how to formulate this change?
The information you provide is rather rudimentary. But tuning into my mentalist skills, going out on a limb, I would guess this untangled version of the query does the job much faster:
SELECT m.id, m.rufname, p.id, p.nachname
FROM person p
JOIN mannschaft m ON m.leiter = p.id
JOIN (
SELECT e.mannschaft
FROM einsatz e
JOIN bergefahrzeug b ON b.id = e.fahrzeug -- may be redundant
GROUP BY e.mannschaft
HAVING count(DISTINCT e.fahrzeug)
= (SELECT count(*) FROM bergefahrzeug)
) e ON e.mannschaft = m.id
Explain:
In the subquery e I count how many DISTINCT mountain-vehicles (bergfahrzeug) have been used by a team (mannschaft) in all their deployments (einsatz): count(DISTINCT e.fahrzeug)
If that number matches the count in table bergfahrzeug: (SELECT count(*) FROM bergefahrzeug) - the team qualifies according to your description.
The rest of the query just fetches details from matching rows in mannschaft and person.
You don't need this line at all, if there are no other vehicles in play than bergfahrzeuge:
JOIN bergefahrzeug b ON b.id = e.fahrzeug
Basically, this is a special application of relational division. A lot more on the topic under this related question:
How to filter SQL results in a has-many-through relation
Do not know how to explain it, but here is an example how I solved this problem, just in case somebody has the some question one day.
WITH dfz AS (
SELECT DISTINCT fahrzeug,mannschaft FROM einsatz WHERE einsatz.fahrzeug IN (SELECT id FROM bergefahrzeug)
), abc AS (
SELECT DISTINCT mannschaft FROM dfz
), einsatzmannschaften AS (
SELECT abc.mannschaft FROM abc WHERE (SELECT sum(dfz.fahrzeug) FROM dfz WHERE dfz.mannschaft = abc.mannschaft) = (SELECT sum(bergefahrzeug.id) FROM bergefahrzeug)
)
SELECT mannschaft.id,mannschaft.rufname,person.id,person.nachname
FROM mannschaft,person,einsatzmannschaften WHERE mannschaft.leiter = person.id AND einsatzmannschaften.mannschaft=mannschaft.id;