SQL Recursion: Get the main state off of a sub state - sql

I found this nice example of SQL recursion with a CTE, but fail to apply it to my table:
http://walkingoncoals.blogspot.de/2009/12/fun-with-recursive-sql-part-1.html
I have the following table (ObjectStates):
ID Title ParentID
1 Draft null
2 Green null
3 Red null
4 Foo 1
5 Bar 4
I am trying to create a function which returns the "main" state when queried. Example:
GetMainState(5)
-- Shall return 1
GetMainState(4)
-- Shall return 1
GetMainState(2)
-- Shall return 2
I have so far:
CREATE FUNCTION [dbo].[GetMainObjectState] (#ObjectStateID INT)
RETURNS TABLE
AS
RETURN
(
WITH StateRecurcsion(ID, ParentID, Level) AS
(
SELECT ID, ParentID, 0
FROM ObjectStates
WHERE ID = #ObjectStateID
UNION ALL
SELECT uOS.ID, uOS.ParentID, sOS.Level+1
FROM ObjectStates uOS, StateRecurcsion sOS
WHERE uOS.ParentID= sOS.ID
)
SELECT os.ID, os.Title, sos.Level
FROM ObjectStates os, StateRecurcsion sos
WHERE os.ID = sos.ID
)
GO
I tried to create the function just as in the tutorial shown above, but somehow I'm not getting the correct results.

You could create a CTE containing a "root" value and then query it within your function e.g.:
;WITH CTEHierarchy
AS (
SELECT
ID
,0 AS LEVEL
,ID AS root
FROM ObjectStates
WHERE ParentID IS NULL
UNION ALL
SELECT
ObjectStates.ID
,LEVEL + 1 AS LEVEL
,[root]
FROM ObjectStates
INNER JOIN CTEHierarchy uh ON uh.id = ObjectStates.ParentID
)
SELECT [root]
FROM CTEHierarchy
WHERE ID = #ObjectStateID

Related

Getting different Hierarchy levels and Path in Snowflake SQL

I am trying to replicate the result of a qlik function called Hierarchy. It creates the Hierarchy with different Levels and also gives the Hierarchy Path. The Code that i am using so far is giving me the Levels but with an error that Level2 values are also coming in Level1.
CREATE OR REPLACE TRANSIENT TABLE "Hierarchy" ( "NodeID" VARCHAR, "ParentID" VARCHAR, "NodeName" String)
Insert into "Hierarchy"("NodeID", "ParentID","NodeName")
VALUES
('1','4','London'),
('2','3','Munich'),
('3','5','Germany'),
('4','5','UK'),
('5','', 'Europe');
with recursive CteTree as
(Select "NodeID","ParentID","NodeName" as "NodeName1",
CAST (NULL AS varchar(255)) as "NodeName2",
CAST (NULL AS varchar(255)) as "NodeName3",
0 as NodeName
from "Hierarchy"
Where "ParentID" is not null
UNION ALL
Select child."NodeID", child."ParentID", "NodeName1",
Case When NodeName+1 = 1 then "NodeName" else "NodeName2" end,
Case When NodeName+1 = 2 then "NodeName" else "NodeName3" end,
NodeName+1
from CteTree
Join "Hierarchy" child
ON child."ParentID" = CteTree."NodeID"
)
select distinct * from CteTree order by "NodeName1"
The Output that it is producing:
Desired OutPut:
How can it be achieved?
with Hierarchy (NodeID, ParentID,NodeName) as (select * from VALUES
('1','4','London'),
('2','3','Munich'),
('3','5','Germany'),
('4','5','UK'),
('5','', 'Europe'))
select
sys_connect_by_path(NodeName, ' -> ') path
, NodeID
, ParentID
, NodeName
from
Hierarchy
START WITH ParentID =''
CONNECT BY PRIOR NodeID = ParentID ;
CREATE TABLE HIERARCHYWITHLEVEL(NODEID, PARENTID, NODENAME, LEVEL)
AS
WITH TREE AS
(SELECT NODEID, PARENTID, NODENAME, 1 AS LEVEL
FROM HIERARCHY
WHERE PARENTID = ''
UNION ALL
SELECT HIERARCHY.NODEID, HIERARCHY.PARENTID, HIERARCHY.NODENAME, LEVEL + 1
FROM HIERARCHY
JOIN TREE
ON HIERARCHY.PARENTID = TREE.NODEID
)
SELECT NODEID, PARENTID, NODENAME, LEVEL
FROM TREE;
NODEID
PARENTID
NODENAME
LEVEL
5
Europe
1
3
5
Germany
2
4
5
UK
2
2
3
Munich
3
1
4
London
3

SQL: Query complete hierarchy based on a primary key

We have a table like below
folderid name parent
==========================
1 one null
2 two 1
3 three 2
4 four 3
5 five 4
6 six 5
Is there a way to retrieve the complete list of records when given a folderid. For example if 1 is passed it should return the complete hierarchy till the leaf that is 6. If 6 is passed it should return the complete hierarchy till the root that is 1. If 4 is passed it should return the complete hierarchy from root to the leaf that is from 1 to 6.
You can use a recursive CTE:
with cte as (
select folderid
from t
where folderid = 1
union all
select t.folderid
from cte join
t
on cte.folderid = t.parent
)
select *
from cte
option (maxrecursion 0);
If you want additional columns, you can either include them in the recursive CTE or you can join them in the outer query.
Here is a db<>fiddle.
EDIT:
If you want to walk up and down the tree, I would recommend two CTEs:
with cte_c as (
select folderid, 1 as lev
from t
where folderid = 4
union all
select t.folderid, lev + 1
from cte_c join
t
on cte_c.folderid = t.parent
),
cte_p as (
select parent, 1 as lev
from t
where folderid = 4
union all
select t.parent as folderid, lev + 1
from cte_p join
t
on cte_p.parent = t.folderid
where t.parent is not null
)
select folderid
from cte_c
union all
select parent
from cte_p
where parent is not null
option (maxrecursion 0);
Here is a db<>fiddle for this version.

Return NULL from XML EXPLICIT subquery where no rows exist

This is probably simple to do, but I'm having a brain-fart on this one...
I'm using FOR XML EXPLICIT as part of a subquery so that I can explicitly define the format of the returned XML. Therefore I'm using UNION ALL to define that format.
This is working fine, but I need it to return NULL if there are no rows in that sub-query... at the moment it is returning an empty root element: <codes/>. That is because I need the first row for the definition.
Here is a sqlfiddlecom with everything below for you to look at.
This is a version of the TSQL as it currently is...
SELECT
P.[PROJECTID],
P.[PROJECTNAME],
( SELECT *
FROM (
SELECT
1 AS TAG,
NULL AS PARENT,
NULL AS 'codes!1',
NULL AS 'code!2!!element',
NULL AS 'code!2!split'
UNION ALL
SELECT
2 AS TAG,
1 AS PARENT,
NULL,
C.[CODE],
C.[SPLIT]
FROM [CODES] C
WHERE C.[PROJECTID] = P.[PROJECTID]
) AS [CODEXMLDATA]
FOR XML EXPLICIT
) AS [CODESXML]
FROM [PROJECTS] P
Example data would be along the lines of
PROJECTS table
PROJECTID PROJECTNAME
1 This
2 That
3 Other
CODES table
PROJECTID CODE SPLIT
1 ABC 45
1 BCD 65
2 CDE 100
The result is coming out as...
PROJECTID PROJECTNAME CODESXML
1 This <codes><code split="45">ABC</code><code split="55">BCD</code></codes>
2 That <codes><code split="100">CDE</code></codes>
3 Other <codes/>
The result I need is (note the NULL on the 3rd line)...
PROJECTID PROJECTNAME CODESXML
1 This <codes><code split="45">ABC</code><code split="55">BCD</code></codes>
2 That <codes><code split="100">CDE</code></codes>
3 Other NULL
Can anybody give me a hint how I can make it return NULL when there are no CODES?
Try this code
SELECT distinct
P.[PROJECTID],
P.[PROJECTNAME],
case when (p.projectid = c.projectid) then
( SELECT *
FROM (
SELECT
1 AS TAG,
NULL AS PARENT,
NULL AS 'codes!1',
NULL AS 'code!2!!element',
NULL AS 'code!2!split'
UNION ALL
SELECT
2 AS TAG,
1 AS PARENT,
NULL,
C.[CODE],
C.[SPLIT]
FROM [CODES] C
WHERE C.[PROJECTID] = P.[PROJECTID]
) AS [CODEXMLDATA]
FOR XML EXPLICIT
) else null end AS [CODESXML]
FROM [PROJECTS] P
left join [CODES] C on C.[PROJECTID] = P.[PROJECTID]
SQLFiddle : http://sqlfiddle.com/#!3/f8788/7/0

How to get the deepest levels of a hierarchical sql query

I'm using SQLServer 2008.
Say I have a recursive hierarchy table, SalesRegion, whit SalesRegionId and ParentSalesRegionId. What I need is, given a specific SalesRegion (anywhere in the hierarchy), retrieve ALL the records at the BOTTOM level.
I.E.:
SalesRegion, ParentSalesRegionId
1, null
1-1, 1
1-2, 1
1-1-1, 1-1
1-1-2, 1-1
1-2-1, 1-2
1-2-2, 1-2
1-1-1-1, 1-1-1
1-1-1-2, 1-1-1
1-1-2-1, 1-1-2
1-2-1-1, 1-2-1
(in my table I have sequencial numbers, this dashed numbers are only to be clear)
So, if the user enters 1-1, I need to retrieve al records with SalesRegion 1-1-1-1 or 1-1-1-2 or 1-1-2-1 (and NOT 1-2-2). Similarly, if the user enters 1-1-2-1, I need to retrieve just 1-1-2-1
I have a CTE query that retrieves everything below 1-1, but that includes rows that I don't want:
WITH SaleLocale_CTE AS (
SELECT SL.SaleLocaleId, SL.SaleLocaleName, SL.AccountingLocationID, SL.LocaleTypeId, SL.ParentSaleLocaleId, 1 AS Level /*Added as a workaround*/
FROM SaleLocale SL
WHERE SL.Deleted = 0
AND (#SaleLocaleId IS NULL OR SaleLocaleId = #SaleLocaleId)
UNION ALL
SELECT SL.SaleLocaleId, SL.SaleLocaleName, SL.AccountingLocationID, SL.LocaleTypeId, SL.ParentSaleLocaleId, Level + 1 AS Level
FROM SaleLocale SL
INNER JOIN SaleLocale_CTE SLCTE ON SLCTE.SaleLocaleId = SL.ParentSaleLocaleId
WHERE SL.Deleted = 0
)
SELECT *
FROM SaleLocale_CTE
Thanks in advance!
Alejandro.
I found a quick way to do this, but I'd rather the answer to be in a single query. So if you can think of one, please share! If I like it better, I'll vote for it as the best answer.
I added a "Level" column in my previous query (I'll edit the question so this answer is clear), and used it to get the last level and then delete the ones I don't need.
INSERT INTO #SaleLocales
SELECT *
FROM SaleLocale_GetChilds(#SaleLocaleId)
SELECT #LowestLevel = MAX(Level)
FROM #SaleLocales
DELETE #SaleLocales
WHERE Level <> #LowestLevel
Building off your post:
; WITH CTE AS
(
SELECT *
FROM SaleLocale_GetChilds(#SaleLocaleId)
)
SELECT
FROM CTE a
JOIN
(
SELECT MAX(level) AS level
FROM CTE
) b
ON a.level = b.level
There were a few edits in there. Kept hitting post...
Are you looking for something like this:
declare #SalesRegion as table ( SalesRegion int, ParentSalesRegionId int )
insert into #SalesRegion ( SalesRegion, ParentSalesRegionId ) values
( 1, NULL ), ( 2, 1 ), ( 3, 1 ),
( 4, 3 ), ( 5, 3 ),
( 6, 5 )
; with CTE as (
-- Get the root(s).
select SalesRegion, CAST( SalesRegion as varchar(1024) ) as Path
from #SalesRegion
where ParentSalesRegionId is NULL
union all
-- Add the children one level at a time.
select SR.SalesRegion, CAST( CTE.Path + '-' + cast( SR.SalesRegion as varchar(10) ) as varchar(1024) )
from CTE inner join
#SalesRegion as SR on SR.ParentSalesRegionId = CTE.SalesRegion
)
select *
from CTE
where Path like '1-3%'
I haven't tried this on a serious dataset, so I'm not sure how it'll perform, but I believe it solves your problem:
WITH SaleLocale_CTE AS (
SELECT SL.SaleLocaleId, SL.SaleLocaleName, SL.AccountingLocationID, SL.LocaleTypeId, SL.ParentSaleLocaleId, CASE WHEN EXISTS (SELECT 1 FROM SaleLocal SL2 WHERE SL2.ParentSaleLocaleId = SL.SaleLocaleID) THEN 1 ELSE 0 END as HasChildren
FROM SaleLocale SL
WHERE SL.Deleted = 0
AND (#SaleLocaleId IS NULL OR SaleLocaleId = #SaleLocaleId)
UNION ALL
SELECT SL.SaleLocaleId, SL.SaleLocaleName, SL.AccountingLocationID, SL.LocaleTypeId, SL.ParentSaleLocaleId, CASE WHEN EXISTS (SELECT 1 FROM SaleLocal SL2 WHERE SL2.ParentSaleLocaleId = SL.SaleLocaleID) THEN 1 ELSE 0 END as HasChildren
FROM SaleLocale SL
INNER JOIN SaleLocale_CTE SLCTE ON SLCTE.SaleLocaleId = SL.ParentSaleLocaleId
WHERE SL.Deleted = 0
)
SELECT *
FROM SaleLocale_CTE
WHERE HasChildren = 0

How to get the parent given a child in SQL SERVER 2005

I have a table like this
childid parentid
------------------------
1 0
2 1
3 2
4 2
5 3
6 4
7 0
8 7
9 8
10 1
If I give a childid as 5, the parentid will be 1(output)
If I give a childid as 9, the parentid will be 7.(output)
i.e. the root parentid is 0 and the query should stop there.
How to solve such a query?
Please help.
I think you should rename your child_id to node, your parent_id to child_of. Your column naming is a bit confusing
create table stack_overflow
(
node int, child_of int
);
insert into stack_overflow(node, child_of) values
(1,0),
(2,1),
(3,2),
(4,2),
(5,3),
(6,4),
(7,0),
(8,7),
(9,8),
(10,1);
This works on any CTE-capable RDBMS:
with find_parent(parent, child_of, recentness) as
(
select node, child_of, 0
from stack_overflow
where node = 9
union all
select i.node, i.child_of, fp.recentness + 1
from stack_overflow i
join find_parent fp on i.node = fp.child_of
)
select top 1 parent from find_parent
order by recentness desc
Output:
parent
7
[EDIT: more flexible and future-proof]:
with find_parent(node_group, parent, child_of, recentness) as
(
select node, node, child_of, 0
from stack_overflow
where node in (5,9)
union all
select fp.node_group, i.node, i.child_of, fp.recentness + 1
from stack_overflow i
join find_parent fp on i.node = fp.child_of
)
select q.node_group as to_find, parent as found
from find_parent q
join
(
select node_group, max(recentness) as answer
from find_parent
group by node_group
) as ans on q.node_group = ans.node_group and q.recentness = ans.answer
order by to_find
Output:
to_find found
5 1
9 7
If you're using Postgres, the above code could be shortened to:
with recursive find_parent(node_group, parent, child_of, recentness) as
(
select node, node, child_of, 0
from stack_overflow
where node in (5,9)
union all
select fp.node_group, i.node, i.child_of, fp.recentness + 1
from stack_overflow i
join find_parent fp on i.node = fp.child_of
)
select distinct on (node_group) node_group as to_find, parent as found
from find_parent
order by to_find, recentness desc
DISTINCT ON rocks! :-)
If ALL you want is the root ParentID, you can use this recursive function:
CREATE FUNCTION test_func
(
#ParentID int
)
RETURNS int
AS
BEGIN
DECLARE #result int;
DECLARE #childID int;
SET #childID = (SELECT ChildID FROM YourTable WHERE ParentID = #ParentID)
IF (#childID = 0)
SET #result = #ParentID
ELSE
SET #result = dbo.test_func(#childID)
RETURN #result
END
GO
then in your main query:
SELECT dbo.test_func(5)
Passing in 5 returns 1, 9 returns 7 based on your provided data. If you need every ParentID that is up that chain, you should probably use a CTE.
I think you want a recursive query, you should use Common Table Expressions. I will give you a link with an example very similar that the one you're using.
I think here is the solution. It helped me some months ago.
A simple of example of getting the parent ID matching a given child ID is:
select parentid
from MyTable
where childid = 5
However, for the data above, this will return no records.