I have a table that tracks changes. One column is called beforename and the other aftername.
Some sample data might be:
Parent
Child
a
b
b
c
c
d
I am trying to write a query that will self reference itself in such a way as to return the changes ie:
a -> b -> c -> d (the arrows are just for notation here)
Is this possible to do in SQL?
My database is SQL Server
You could use a recursive common table expression (CTE);
WITH cte(Root, Level, Parent, Child) AS (
SELECT Parent, 0, Parent, Child FROM Table1
WHERE Parent NOT IN (SELECT Child FROM Table1)
UNION ALL
SELECT cte.Root, cte.Level + 1, t1.Parent, t1.Child
FROM cte
JOIN Table1 t1
ON cte.Child = t1.Parent AND cte.Level < 10
)
SELECT * FROM cte ORDER BY Root, Level;
Basically, a recursive CTE uses a base case (find all starting points). THis one uses;
SELECT Parent, 0, Parent, Child FROM Table1
WHERE Parent NOT IN (SELECT Child FROM Table1)
...which finds all parents, that is rows where the parent is never mentioned in the table as a child. It then sets those parent as a "Root" and sets level to 0.
It then proceeds to find the child rows for that root using;
SELECT cte.Root, cte.Level + 1, t1.Parent, t1.Child
FROM cte
JOIN Table1 t1
ON cte.Child = t1.Parent AND cte.Level < 10
...which basically just finds the next child (a row which has this rows child as a parent) while keeping the root and increasing level by 1.
It also limits the recursion to 10 levels just in case there is a loop in the data.
A dbfiddle to test with
Related
Suppose I have the following tree:
The table that stores the data is presented below accordingly:
node_id
parent_id
A
null
B
A
C
A
D
B
I want to select all node ancestors for every node in the tree.
the root node has no ancestors so it'll not show up.
Example of what I want -
node_id
ancestor_id
B
A
C
A
D
B
D
A
Glad for any help :)
I am working with pgsql.
You need a recursive query :
WITH RECURSIVE cte AS (
SELECT node_id, parent_id AS ancestor_id
FROM your_table
WHERE parent_id is not null
UNION ALL
SELECT children.node_id, parent.parent_id
FROM cte AS chidren
INNER JOIN your_table AS parent
ON parent.node_id = children.parent_id
)
SELECT *
FROM cte ;
This query may not stop if you have a loop in the node_id => paren_id tree. In order to avoid infinite loops in the recursive query, you have to add a test :
WITH RECURSIVE cte AS (
SELECT node_id, parent_id AS ancestor_id, array[node_id, parent_id] AS check
FROM your_table
WHERE parent_id is not null
UNION ALL
SELECT children.node_id, parent.parent_id, children.check || parent.parent_id
FROM cte AS chidren
INNER JOIN your_table AS parent
ON parent.node_id = children.parent_id
WHERE NOT children.check #> array[parent.parent_id] -- this condition will avoid infinite loops
)
SELECT node_id, ancestor_id
FROM cte ;
I'm sure this has been asked before, but I can't seem to find any questions that help.
Here's an example of a table:
ID Name Parent ID
---------------------------
1 Ice cream 3
2 Chips 4
3 Sweets null
I'm trying to figure out how to write a single query which, given ID=1, will return me rows 1 and 3. Is this possible without making two queries?
Also, is there a way to return the information of the parent as a custom column? So, rather than returning 2 rows, returning the row where id=1 with parent_id=3 added on?
You can use union all and exists:
select * from mytable where parent_id = 3
union all
select t.*
from mytable t
where exists (select 1 from mytable t1 where t1.parent_id = t.id and t.parent_id = 3)
If you want to do this over multiple levels of hierarchy, then you would typically use a recursive query. The syntax slightly varies accross databases (and not all of them support recursion), but the idea is:
with recursive cte as (
select * from mytable where parent_id = 3
union all
select t.*
from cte c
inner join mytable t on t.parent_id = c.id
)
select * from cte
This is how a straight up noob would do it. Hold tight for someone to give a better way
SELECT ID, Name, Parent_ID
FROM table
WHERE ID = 1
UNION
SELECT ID, Name, Parent_ID
FROM table
WHERE ID = (SELECT Parent_ID FROM table WHERE ID = 1)
Are you looking for something like this?:
select child.ID, child.Name, parent.ID as ParentId, parent.Name as ParentName
from T child left outer join T parent on parent.Id = child.parent_id;
I have the following data in my database:
Parent Child
101 102
101 103
101 104
101 105
101 106
My parameter is 106. And using the parameter I want to retrieve all the other children under its parent which is 101. I tried using the recursive method but it didn't work given the following data. Is there another way to formulate a query?
Assuming you want to get siblings of the value #p0, you can use a simple self-join:
SELECT p.Child
FROM Table1 c
INNER JOIN Table1 p ON c.Parent = p.Parent
WHERE c.Child = #p0
AND p.Child <> #p0
The not-equal clause here makes sure you get siblings not including the value you searched for. Remove it as necessary.
SQL Fiddle example
Since you mention recursion though, perhaps you want the entire tree starting at the parent of the value #p0. In which case, you can use a recursive CTE:
WITH parent AS (
SELECT Parent
FROM Table1
WHERE Child = #p0
), tree AS (
SELECT x.Parent, x.Child
FROM Table1 x
INNER JOIN parent ON x.Parent = parent.Parent
UNION ALL
SELECT y.Parent, y.Child
FROM Table1 y
INNER JOIN tree t ON y.Parent = t.Child
)
SELECT Parent, Child
FROM tree
SQL Fiddle examples using your data and
with additional data to demonstrate the recursive CTE
SQL Authority has a blog with a very nice explanation of how to perform a Hierarchical Query using a Recursive CTE
http://blog.sqlauthority.com/2012/04/24/sql-server-introduction-to-hierarchical-query-using-a-recursive-cte-a-primer/
Regards
select child
from my_table T1
where exists (select 1 from my_table T2 where child = #parameter and T1.parent = T2.parent)
WITH parent AS (
SELECT Parent
FROM Table1
WHERE Child = #p0
), tree AS (
SELECT x.Parent, x.Child
FROM Table1 x
INNER JOIN parent ON x.Parent = parent.Parent
UNION ALL
SELECT y.Parent, y.Child
FROM Table1 y
INNER JOIN tree t ON y.Parent = t.Child
)
SELECT Parent, Child
FROM tree
I have a little bit complicated query.
I have table
tbl_Categories {CategoryID, Name, CategoryId_fk}
and it it a self referenced table.
When CategoryId_fk is NULL then that row is parent when there is a value that it is a child.
I have problem to select all children (it's where CategoryId_fk is not null) and rows where CategoryId_fk is null and doesn't have children.
I tried something but doesn't work:
SELECT a.*
FROM tbl_Categories a
WHERE NOT EXISTS (
SELECT 1 FROM tbl_Categories b
WHERE b.CategoryId_fk= a.CategoryId_fk
)
You have matched both b and a on the foreign key.
I might be misinterpreting your question, most of the times users want to find all childs for a given parent, but below query returns what I think you need.
/* All parents without children */
SELECT a.*
FROM tbl_Categories a
WHERE NOT EXISTS (
SELECT *
FROM tbl_Categories b
WHERE b.CategoryId_fk = a.CategoryId
)
/* All children */
UNION ALL
SELECT a.*
FROM tbl_Categories a
WHERE CategoryId_fk IS NOT NULL
03-30 13:17]
I'm trying to use a t-sql against SQL-Server 2005 with asp 3.0. What I want to do is first select all rows that match a certain condition and then I want to know how how many children are connected to that row based on it's ChildID.
This can of course be done by just doing two queries against the db one to get the matching rows for the Parent and one for each row that was found to know how many children have that as Parent.
I have two tables, table1 contains parents and children and table2 contains the information and the id here is ofcourse the id that is saved in parent and children in table1.
Now I want to first find all the children to a certain parent, and in the same sql-statement I want to know how many children is connected to each child.
So Parent 807 has two children, 808 and 809. 808 has 5 children and 809 has 72 children, how can I with one statement get both the information of children 808 and 809 and the numer of children connected to each of them.
My SQL looks like this (gets the children to parent 807)
SELECT Parent, Child, ID, FieldX, FieldY
FROM Tabel1 INNER JOIN Tabel2 ON Parent = ID
WHERE (Parent = 807)
The statement I use to get the numer of children connected to f.ex. 808 is as follows.
SELECT COUNT(*) AS AntalPoster
FROM Tabel1 INNER JOIN Tabel2 ON Child = ID
WHERE (Parent = 808)
What I would like to do is the following, but I would like to substitute the value 808 with the value each record gives me for the child aka SearchID.
SELECT Parent, Child AS SearchID, ID, FieldX, FieldY,
(SELECT COUNT(*) AS [Antal sidor]
FROM Tabel1 INNER JOIN Tabel2 ON Child = ID
WHERE (Parent = 808)) AS AntalPoster
FROM Tabel1 INNER JOIN Tabel2 ON Child = ID
WHERE (Parent = 807)
The problem I get is that "Invalid column name 'SearchID'", what I can understand is that I can't get the value that I find in SerachID to be used as a value in the next SQL-statements where clause.
Is this possible, and if so how?
Thanks in advance.
So, I understand, basically Table1 contains hierarchy information, and Table2 details.
Assuming that, here's my approach:
SELECT
h.Parent,
h.ChCount,
d.* /* you may want to expand it to a specific Table2 column list */
FROM (
/* first, get the children and their child counts */
SELECT
p.Parent,
p.Child,
COUNT(*) AS ChCount
FROM Table1 p
INNER JOIN Table1 c ON p.Child = c.Parent
GROUP BY p.Parent, p.Child
WHERE p.Parent = 807
) h
/* next, get the children's details */
INNER JOIN Table2 d ON h.Child = d.ID
Now I want to first find all the children to a certain parent, and in the same sql-statement I want to know how many children is connected to each child.
This is a way to do that.
declare #Table1 table (Table1ID int, ParentID int)
-- Root
insert into #Table1 values(807, null)
-- Children to 807
insert into #Table1 values(808, 807)
insert into #Table1 values(809, 807)
-- Children to 808
insert into #Table1 values(810, 808)
-- Children to 809
insert into #Table1 values(811, 809)
insert into #Table1 values(812, 809)
select
T1.*,
(select count(*)
from #Table1 as T2
where T1.Table1ID = T2.ParentID) as ChildCount
from #Table1 as T1
where T1.ParentID = 807
Result
Table1ID ParentID ChildCount
----------- ----------- -----------
808 807 1
809 807 2
Replace
WHERE (Parent = SearchID))
by
WHERE (Parent = ParentID))
But you're better of using
SELECT Parent, Child, ID, FieldX, FieldY, COUNT(*) AS AntalPoster
FROM Tabel1 INNER JOIN Tabel2 ON Child = ID
WHERE (Parent = [ParentID])
GROUP
BY Parent, Child, ID, FieldX, FieldY;
If you define a "alias" for a column you can't use this name inside the query earlier than within a group by or order by clause.
As is your example SearchID is just a name for column Child you should use WHERE (PARENT = Child)
You're kinda close.
SELECT Parent_ID, COUNT(*) AS AntalPoster FROM Tabel1 INNER JOIN Tabel2 ON Child = ID
Group by Parent_ID
Now I don' quite understand from reading your question what the 'ID' field is called...but the results of this query should give the Parent_ID along with the count of the number of child records (If I've read your setup corectly). Join to that subquerty to get the count.
SELECT Parent, Child.AntalPoster, ID, FieldX, FieldY
FROM Tabel1 INNER JOIN Tabel2 ON Parent = ID
inner join (SELECT Parent_ID, COUNT(*) AS AntalPoster FROM Tabel1 INNER JOIN Tabel2 ON Child = ID Group by Parent_ID) child on child.id = t1.ID
WHERE (Parent = 807)
Whats really confusing me in your example...if I'm still off with what you're going for...quickly giving us the schema would help. Is the table setup so it has Parent, Child, ID? If so, how does the work? I'd expect Parent_ID, Child_ID...bit confused on the third "ID" column, what does it hold thats unique from the parent,child columns that already exist?.
In any case, the logic is setup a subquery so it reads Key , Count(1). Then join to that subquery on the Key to obtain the count value the subquery returns.