I have a table where each record contains a unique Numeric id and 2 parent ids (mother, father). I would like to find a way to list the parents(2), grandparents(4), great grandparents(8) and so on down to a specified level. Before I give up on pure SQL and do it in Python, can anyone tell me a way to do this?
You can try something like below, where once you pass child numericId, you get corresponding parents and from there, recursively you get higher levels.
You can filter the levels using parentlevel filter.
DECLARE #childNumericId AS INT
;WITH CTE_Ancestory AS (
SELECT numericId AS child, ParentId1 as father, parentId2 as mother, 1 as parentlevel
FROM tableName
WHERE numericId = #childNumericId
UNION ALL
SELECT t.NumericId AS child, t.ParentId1 as father, t.parentId2 as mother, c.parentlevel + 1 AS parentLevel
FROM tableName AS t
INNER JOIN CTE_Ancestory AS c ON t.numericId IN (c.father, c.mother)
)
SELECT *
from CTE_Ancestory
Where parentlevel < 4 -- number of levels you need
Related
I have below table with values as this. The highlighted values are newly added rows. Looking the levels I want to get the parent account for those values.
Eg for CGT losses level1ID = 3 Level2ID = 1 and level3ID = 2 means its parent of LevelDescription"CGT Losses Arising During The Tax Year From Sales of Investments"
Need help in grouping ID
I want output as
It's seems that you need to do a self join. You have not given your table properly (please display as text not image) and I haven't tested, the following should do what you need.
SELECT
c.level4ID account_no,
p.LevelDescription parent,
c.LevelDescription child
FROM
(SELECT * FROM table_name
WHERE Level4Id <> 0) c
JOIN
(SELECT * FROM table_name
WHERE Level4ID = 0 ) p
ON
c.level1Id=p.level1Id,
c.level2Id=p.level2Id,
c.level3Id=p.level3Id
ORDER BY
c.level4Id;
I have the below XML document stored in a TSQL variable with XML type:
<root>
<parent>
<child>Alice</child>
<child>Bob</child>
<child>Carol</child>
</parent>
<house>
<room><id>1</id></room>
<room><id>2</id></room>
<room><id>3</id></room>
</house>
</root>
I would like to iterate over "cousin" nodes (that is, nodes whose parents are siblings) and insert in a table one row per iteration and one column per cousin. So the result would be something like this:
Child | Room
------------
Alice | 1
Bob | 2
Carol | 3
(I know for a fact that there are as many rooms as children).
I feel like this is a simple task, but can't seem to find a way. I am a beginner in SQL Server and XPath, and probably lack the terminology to look for documentation.
What I've tried so far is to iterate over, say the child elements, and try to read the matching room element from there using ROW_NUMBER to pick the room I want:
INSERT INTO children (child, room)
SELECT
child = T.Item.value('(../parent/child/text())[' + (ROW_NUMBER() OVER(ORDER BY T.Item)) + ']', 'VARCHAR(10)'),
room = T.Item.value('(id/text())[1]', 'CHAR(1)')
FROM
#XML.nodes('root/house/room') AS T(Item)
But SQL server complains that the value() accepts only string literal as first argument (what kind of limitation is that??).
Any idea of how I could do that simply?
Not sure if this the best or shortest way, but it produces the output you require:
DECLARE #x XML='
<root>
<parent>
<child>Alice</child>
<child>Bob</child>
<child>Carol</child>
</parent>
<house>
<room><id>1</id></room>
<room><id>2</id></room>
<room><id>3</id></room>
</house>
</root>';
;WITH childs AS (
SELECT
n.e.value('.','NVARCHAR(128)') AS child,
id=ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM
#x.nodes('/root/parent/child') AS n(e)
),
room_ids AS (
SELECT
n.e.value('.','NVARCHAR(128)') AS room_id,
id=ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM
#x.nodes('/root/house/room/id') AS n(e)
)
SELECT
c.child,
r.room_id
FROM
childs AS c
INNER JOIN room_ids AS r ON
r.id=c.id
ORDER BY
c.id;
As a pure XPath query you might do this:
SELECT The.Room.value('id[1]','varchar(max)'),The.Room.value('let $r:=. return (../../parent/child[position()=$r]/text())[1]','varchar(max)')
FROM #YourXML.nodes('/root/house/room') AS The(Room)
But - to be honest - I'd prefer TT.'s solution. This solution relys on the rooms to be numbered from 1 to n without gaps. TT.'s solution would work even with unsorted room numbers...
I have a recursive tree database table
DataItem
Id (uniqueidentifier)
Parent_Id? (uniqueidentifier)
PositionInParent (int)
I've read some articles about Common Table Expressions, which allows me to recursively read the tree structure directly from SQL database, but all of them are very complicated and i cannot make them work.
I am trying to read recursively all the DataItems, starting from the root ones (which has no parent), and adding the children items (ordered by PositionInParent)
Please help me create this simple example, and from there i will add more logic if necessary.
;WITH HierarchyCTE (ParentId, Id, Level)
AS
(
SELECT e.ParentId, e.Id, 0 AS Level
FROM Employees AS e
WHERE ParentId IS NULL
UNION ALL
SELECT e.ParentId, e.Id, Level + 1
FROM Employees AS e
INNER JOIN HierarchyCTE AS h
ON e.ParentId = h.Id
)
SELECT ParentId, Id, Level AS PositionInParent
FROM HierarchyCTE
You can use condition WHERE ParentId = 0if ParentId of super parent is 0
I have a forum database that stores forum information in a single column. The forum allows for unlimited subforums.
Table name - forums
| ForumID | ParentForumID | Name | Description | TopicCount | ReplyCount | LastPost |
Given a ForumID as a parameter I am trying to SUM the TopicCount and ReplyCount for all child entries. I am also trying to return the latest LastPost, which is specified as DATETIME.
I've searched google and this forum and understand I should be using a recursive CTE but am having some difficulty understanding the syntax. Here is my CTE - work in progress.
WITH CTE (ForumID, ParentForumID)
AS
(
SELECT ForumID AS Descendant, ParentForumID as Ancestor
FROM forums
UNION ALL
SELECT e.Ancestor
FROM
CTE as e
INNER JOIN CTE AS d
ON Descendant = d.ParentForumID
)
SELECT e.Descendant, SUM(TopicCount) AS topics, SUM(ReplyCount) AS replys
FROM CTE e
WHERE e.Ancestor = 1
Where 1 = Parameter for the forum ID.
Thanks in advance for the help!
You're doing OK - you're quite close :-)
Basically, you need to:
define the initial forum to be picked before the CTE
create an "anchor" query to that forum defined
then iterate over all children and sum up the TopicCount and ReplyCount counters
So your code should look something like this:
DECLARE #RootForumID INT
SET #RootForumID = 1 -- or whatever you want...
;WITH CTE AS
(
-- define the "anchor" query - select the chosen forum
SELECT
ForumID, TopicCount, ReplyCount, LastPost
FROM
dbo.forums
WHERE
ForumID = #RootForumID
UNION ALL
-- select the child rows
SELECT
f.ForumID, f.TopicCount, f.ReplyCount, f.LastPost
FROM
dbo.forums f
INNER JOIN
CTE on f.ParentForumID = CTE.ForumID
)
SELECT
SUM(TopicCount) AS topics,
SUM(ReplyCount) AS replys,
MAX(LastPost) AS 'Latest Post'
FROM
CTE
Of course, you could wrap this into a stored procedure that would take the initial "root" ForumID as a parameter .
a few minutes ago i asked here how to get parent records with a recursive CTE.
This works now, but I get the wrong order(backwards, ordered by the PK idData) when i create a Table valued Function which returns all parents. I cannot order directly because i need the logical order provided by the CTE.
This gives the correct order(from next parent to that parent and so on):
declare #fiData int;
set #fiData=16177344;
WITH PreviousClaims(idData,fiData)
AS(
SELECT parent.idData,parent.fiData
FROM tabData parent
WHERE parent.idData = #fiData
UNION ALL
SELECT child.idData,child.fiData
FROM tabData child
INNER JOIN PreviousClaims parent ON parent.fiData = child.idData
)
select iddata from PreviousClaims
But the following function returns all records in backwards order(ordered by PK):
CREATE FUNCTION [dbo].[_previousClaimsByFiData] (
#fiData INT
)
RETURNS #retPreviousClaims TABLE
(
idData int PRIMARY KEY NOT NULL
)
AS
BEGIN
DECLARE #idData int;
WITH PreviousClaims(idData,fiData)
AS(
SELECT parent.idData,parent.fiData
FROM tabData parent
WHERE parent.idData = #fiData
UNION ALL
SELECT child.idData,child.fiData
FROM tabData child
INNER JOIN PreviousClaims parent ON parent.fiData = child.idData
)
INSERT INTO #retPreviousClaims
SELECT idData FROM PreviousClaims;
RETURN;
END;
select * from dbo._previousClaimsByFiData(16177344);
UPDATE:
Since everybody beliefs that the CTE is not ordering(Any "ordering" will be totally arbitrary and coincidental), i'm wondering why the opposite seems to be true. I have queried a child claim with many parents and the order in the CTE is exactly the logical order when i go from child to parent and so on. This would mean that the CTE is iterating from record to record like a cursor and the following select returns it in exact this order. But when i call the TVF i got the order of the primary key idData instead.
The solution was simple. I only needed to remove the parent key of the return-Table of the TVF. So change...
RETURNS #retPreviousClaims TABLE
(
idData int PRIMARY KEY NOT NULL
)
to...
RETURNS #retPreviousClaims TABLE
(
idData int
)
.. and it keeps the right "order" (same order they were inserted into the CTE's temporary result set).
UPDATE2:
Because Damien mentioned that the "CTE-Order" could change in certain circumstances, i will add a new column relationLevel to the CTE which describes the level of relationship of the parent records (what is by the way quite useful in general f.e. for a ssas cube).
So the final Inline-TVF(which returns all columns) is now:
CREATE FUNCTION [dbo].[_previousClaimsByFiData] (
#fiData INT
)
RETURNS TABLE AS
RETURN(
WITH PreviousClaims
AS(
SELECT 1 AS relationLevel, child.*
FROM tabData child
WHERE child.idData = #fiData
UNION ALL
SELECT relationLevel+1, child.*
FROM tabData child
INNER JOIN PreviousClaims parent ON parent.fiData = child.idData
)
SELECT TOP 100 PERCENT * FROM PreviousClaims order by relationLevel
)
This is an exemplary relationship:
select idData,fiData,relationLevel from dbo._previousClaimsByFiData(46600314);
Thank you.
The correct way to do your ORDERing is to add an ORDER BY clause to your outermost select. Anything else is relying on implementation details that may change at any time (including if the size of your database/tables goes up, which may allow more parallel processing to occur).
If you need something convenient to allow the ordering to take place, look at Example D in the examples from the MSDN page on WITH:
WITH DirectReports(ManagerID, EmployeeID, Title, EmployeeLevel) AS
(
SELECT ManagerID, EmployeeID, Title, 0 AS EmployeeLevel
FROM dbo.MyEmployees
WHERE ManagerID IS NULL
UNION ALL
SELECT e.ManagerID, e.EmployeeID, e.Title, EmployeeLevel + 1
FROM dbo.MyEmployees AS e
INNER JOIN DirectReports AS d
ON e.ManagerID = d.EmployeeID
)
Add something similay to the EmployeeLevel column to your CTE, and everything should work.
I think the impression that the CTE is creating an ordering is wrong. It's a coincidence that the rows are coming out in order (possibly due to how they were originally inserted into tabData). Regardless, the TVF is returning a table so you have to explicitly add an ORDER BY to the SELECT you're using to call it if you want to guarantee ordering:
select * from dbo._previousClaimsByFiData(16177344) order by idData
There is no ORDER BY anywhere in sight - neither in the table-valued function, nor in the SELECT from that TVF.
Any "ordering" will be totally arbitrary and coincidental.
If you want a specific order, you need to specify an ORDER BY.
So why can't you just add an ORDER BY to your SELECT:
SELECT * FROM dbo._previousClaimsByFiData(16177344)
ORDER BY (whatever you want to order by)....
or put your ORDER BY into the TVF:
INSERT INTO #retPreviousClaims
SELECT idData FROM PreviousClaims
ORDER BY idData DESC (or whatever it is you want to order by...)