I need held understanding how to work with the following example.
Let's say I have these two tables:
**Table Name: Children**
Child Shirt_Color_ID Parent
Bob 1 Kate
Kate 2 Jack
Jack 3 Jill
. . .
. . .
**Table Name: Shirt_Colors**
Shirt_Color_ID Shirt_Color
1 Red
2 Blue
3 White
And I want to return a following table:
Child Child_Shirt_Color Parent Parent_Shirt_Color
Bob Red Kate Blue
How would I get the Parent_Shirt_Color in?
I got how to show Child, Child_Shirt_Color, Parent:
select
Children.Child,
Shirt_Colors.Shirt_Color,
Children.Parent
from
Children,
Shirt_Colors
where
Shirt_Colors.Shirt_Color_ID = Children.Shirt_Color_ID and
Children.Child = 'Bob';
Other examples I have looked at for this, talked about using "WITH," but get errors every time I try saying it is unsupported. Also, I have a very very long relation between parents and children, so I do not want the entire list returned - only 2-3 generations.
Using Oracle
Any help would be appreciated. Thanks!
You need CTE and used it for recursive queries.
http://technet.microsoft.com/en-us/library/ms186243(v=sql.105).aspx
Try following code:
DROP TABLE Children
DROP TABLE Shirt_Colors
CREATE TABLE Children(
Child varchar(20),
Shirt_Color_ID int,
Parent varchar(20)
)
CREATE TABLE Shirt_Colors
(
Shirt_Color_ID int,
Shirt_Color varchar(20)
)
INSERT INTO Shirt_Colors (Shirt_Color_ID, Shirt_Color)
VALUES (1, 'Red'),
(2, 'Blue'),
(3, 'White'),
(4, 'Yellow')
INSERT INTO Children (Child, Shirt_Color_ID, Parent)
VALUES ('Bob', 1, 'Kate'),
('Kate', 2, 'Jack'),
('Jack', 3, 'Jill'),
('Jill', 4, NULL)
select * from Children
;
WITH CTE (Child, Shirt_Color, Parent)
AS
(
SELECT
C.Child,
SC.Shirt_Color,
C.Parent
FROM Children C
INNER JOIN Shirt_Colors SC
ON C.Shirt_Color_ID = SC.Shirt_Color_ID
WHERE C.Parent IS NULL
UNION ALL
SELECT
C.Child,
SC.Shirt_Color,
C.Parent
FROM CTE
INNER JOIN Children C
ON CTE.Child = C.Parent
INNER JOIN Shirt_Colors SC
ON C.Shirt_Color_ID = SC.Shirt_Color_ID
)
SELECT
Child,
Shirt_Color,
Parent
FROM CTE
Related
Say I have the below table which holds customer data:
DECLARE #customer TABLE (ref varchar(10), RepName varchar(10), City varchar(10))
INSERT INTO #customer
SELECT 'CustomerA', 'Tom', 'London' UNION ALL
SELECT 'CustomerC', 'John', 'London'
and I have 2 other identical tables SourceA and SourceB which holds customer data as well,
I have a script which compares data among these 3 tables and inserts the details into the below table:
DECLARE #diffs TABLE (ref varchar(10), existing_value varchar(100), nev_value varchar(100), source_table varchar(100), column_name varchar(100))
INSERT INTO #diffs
SELECT 'CustomerA', 'Tom', 'Tom A', 'SourceA', 'RepName' UNION ALL
SELECT 'CustomerA', 'Tom', 'Tom Ax', 'SourceB', 'RepName' UNION ALL
SELECT 'CustomerC', 'London', 'New York', 'SourceA', 'City'
This table highlights that the rep name in our Customer table is different than sourceA and sourceB. Current value is Tom, but sourceA has the value as Tom A and sourceB has it as Tom Ax, and it also highlights the difference in city but the city is only different in sourceA.
And I use the below table to understand which source to use when I am updating the Customers table:
DECLARE #temp TABLE (column_name varchar(100), source_to_use varchar(100), source_priority int)
INSERT INTO #temp
SELECT 'RepName', 'SourceA', 1 UNION ALL
SELECT 'RepName', 'SourceB', 2 UNION ALL
SELECT 'City', 'SourceB', 1 UNION ALL
SELECT 'City', 'SourceA', 2
Based on this I need to update the rep name with Tom A and city with New York based on the source_priority. Before writing the update statement I have tried to get the right rows using this:
SELECT *
FROM #diffs d
LEFT OUTER JOIN #temp t ON t.column_name = d.column_name and d.source_table = t.source_to_use
AND source_priority = CASE WHEN EXISTS
(
SELECT source_priority
FROM #temp x
Where source_priority = 1 AND d.source_table = x.source_to_use
) THEN 1 ELSE 2 END
But this does not give me what I want, is there anyway of querying these tables and update the customers table with the differences based on priority?
Thanks
One way that I can think to do this. Flatten the three tables into a single table with different columns. Then use cross apply to choose the value from #temp. Here is an example that assumes that customers has rows for all customers:
select c.ref, repname.repname
from (select c.*, ca.repname as repname_a, ca.city = city_a,
cb.repname as repname_b, cb.city as city_b
from customers c left join
customersa ca
on c.ref = ca.ref left join
customersb cb
on c.ref = cb.ref
) c cross apply
(select top 1
(case when source_to_use = 'source_a' and repname_a is not null then name_a
when source_to_use = 'source_b' and repname_b is not null then repname_b
when source_to_use = 'source' and repname is not null then repname
end) as repname
from #temp t
where t.column_name = 'repname'
order by priority
) repname;
I'd go for a CTE. Something like this should work:
WITH cte AS (
SELECT d.*, t.source_priority
FROM #diffs d
LEFT OUTER JOIN #temp t
ON t.column_name = d.column_name
AND d.source_table = t.source_to_use
), mins AS (
SELECT ref, column_name, MIN(source_priority) source_priority
FROM cte
GROUP BY ref, column_name
)
SELECT cte.ref, cte.column_name, cte.new_value
FROM cte INNER JOIN mins
ON cte.ref = mins.ref
AND cte.column_name = mins.column_name
AND cte.source_priority = mins.source_priority
I have two tables that keep track of permissions for groups of users. The first table is just two columns, an identifier and a name, used solely for the names of the permissions. The second table is where the permissions are applied and parent permissions are assigned to create an hierarchy. My problem is that I'm using joins to create a permission hierarchy "string" based on parent permissions and, without knowing how deep that parent recursion might go, I have no way of knowing how many joins to make. My questions is, is there a more correct way to solve this problem?
I've included a complete working script, but I stripped unnecessary columns:
CREATE TABLE #TempPermissions
(
Permission_ID INT IDENTITY,
Permission VARCHAR(50)
)
CREATE TABLE #TempAppPermissions
(
AppPermission_ID INT IDENTITY,
Permission_ID INT,
Parent_ID INT
)
INSERT INTO #TempPermissions VALUES ('Users')
INSERT INTO #TempPermissions VALUES ('Add')
INSERT INTO #TempPermissions VALUES ('Edit')
INSERT INTO #TempPermissions VALUES ('Remove')
INSERT INTO #TempPermissions VALUES ('Permissions')
INSERT INTO #TempPermissions VALUES ('Configure')
INSERT INTO #TempAppPermissions VALUES (1, -1)
INSERT INTO #TempAppPermissions VALUES (2, 1)
INSERT INTO #TempAppPermissions VALUES (3, 1)
INSERT INTO #TempAppPermissions VALUES (4, 1)
INSERT INTO #TempAppPermissions VALUES (5, 1)
INSERT INTO #TempAppPermissions VALUES (6, 5)
SELECT app.AppPermission_ID,
(CASE WHEN NOT child3.Permission IS NULL THEN '/' + child3.Permission ELSE '' END)+
(CASE WHEN NOT child2.Permission IS NULL THEN '/' + child2.Permission ELSE '' END)+
'/' + child1.Permission AS PermissionString
FROM #TempAppPermissions app
INNER JOIN #TempPermissions child1
ON child1.Permission_ID = app.Permission_ID
LEFT JOIN #TempAppPermissions parent1
ON parent1.AppPermission_ID = app.Parent_ID
LEFT JOIN #TempPermissions child2
ON child2.Permission_ID = parent1.Permission_ID
LEFT JOIN #TempAppPermissions parent2
ON parent2.AppPermission_ID = parent1.Parent_ID
LEFT JOIN #TempPermissions child3
ON child3.Permission_ID = parent2.Permission_ID
DROP TABLE #TempPermissions, #TempAppPermissions
This provides me with the results:
AppPermission_ID PermissionString
1 /Users
2 /Users/Add
3 /Users/Edit
4 /Users/Remove
5 /Users/Permissions
6 /Users/Permissions/Configure
This works fine as is, but if I were to go another parent deep with:
INSERT INTO #TempPermissions VALUES ('Reports')
INSERT INTO #TempAppPermissions VALUES (7, 6)
I would have to compensate for it with another set of joins and another case expression in the select statement:
(CASE WHEN NOT child4.Permission IS NULL THEN '/' + child4.Permission ELSE '' END)+
...
LEFT JOIN #TempAppPermissions parent3
ON parent3.AppPermission_ID = parent2.Parent_ID
LEFT JOIN #TempPermissions child4
ON child4.Permission_ID = parent3.Permission_ID
If I do not, I will end up losing the topmost parent on the last result:
1 /Users
2 /Users/Add
3 /Users/Edit
4 /Users/Remove
5 /Users/Permissions
6 /Users/Permissions/Configure
7 /Permissions/Configure/Reports
Technically, I could repeat this any number of times to compensate for how deep that structure may go, but I have the feeling there is probably a better approach this problem. Thanks in advance.
I would use CTE (Common Table Expressions).
;WITH t AS (
SELECT 1 AS iteration, p.Permission_ID AS PermissionID, p.Permission_ID, CAST(N'/' + p.Permission AS NVARCHAR(MAX)) AS Permission
FROM #TempPermissions AS p
UNION ALL
SELECT iteration + 1, t.PermissionID, p.Parent_ID, COALESCE(N'/' + (SELECT s.Permission FROM #TempPermissions AS s WHERE s.Permission_ID = p.Parent_ID), N'') + t.Permission
FROM t INNER JOIN #TempAppPermissions AS p ON t.Permission_ID = p.Permission_ID
)
SELECT PermissionID, Permission FROM t
WHERE Permission_ID = -1
ORDER BY PermissionID, Iteration
Let me know if this helps!
Supplementing the top answer here since I can't post the code in a comment. After playing with JoeFletch's code a bit, I realized the actual recursion should only be happening with the table #TempAppPermissions. With JoeFletch's code, I would hit the maximum recursion of 100 on a larger table. Also the iteration is unimportant.
One thing to note is "SELECT r.AppPermission_ID," on line 13 in the recursion, because I need that child's ID (not the parent's) from #TempAppPermissions to reference back to the user to see if they have that permission. The "Permission_ID" from #TempPermissions is omitted from the select because it is only necessary to get the actual permission name and "Parent_ID" is only used to filter out single instances of dependent permissions.
Thanks again, JoeFletch.
;WITH r AS
(
SELECT p.AppPermission_ID,
p.Parent_ID,
CAST('/' + (SELECT s.Permission
FROM #TempPermissions AS s
WHERE s.Permission_ID = p.Permission_ID)
AS NVARCHAR(MAX)) AS Permission
FROM #TempAppPermissions p
UNION ALL
SELECT r.AppPermission_ID,
p.Parent_ID,
COALESCE(N'/' + (SELECT s.Permission
FROM #TempPermissions AS s
WHERE s.Permission_ID = p.Permission_ID), N'')
+ r.Permission
FROM r
INNER JOIN #TempAppPermissions p
ON p.AppPermission_ID = r.Parent_ID
)
SELECT r.AppPermission_ID, r.Permission
FROM r
WHERE r.Parent_ID = -1
ORDER BY r.AppPermission_ID ASC
(Using Postgresql 9.1)
I have a tree structure in the database and I need to sum the node's values. There are two caveats:
Not all nodes have a value.
If a parent node has a value, ignore the child values.
While recursing the tree is easy with the powerful recursive WITH clause, it's enforcing these two caveats that is breaking my code. Here's my setup:
CREATE TABLE node (
id VARCHAR(1) PRIMARY KEY
);
INSERT INTO node VALUES ('A');
INSERT INTO node VALUES ('B');
INSERT INTO node VALUES ('C');
INSERT INTO node VALUES ('D');
INSERT INTO node VALUES ('E');
INSERT INTO node VALUES ('F');
INSERT INTO node VALUES ('G');
CREATE TABLE node_value (
id VARCHAR(1) PRIMARY KEY,
value INTEGER
);
INSERT INTO node_value VALUES ('B', 5);
INSERT INTO node_value VALUES ('D', 2);
INSERT INTO node_value VALUES ('E', 0);
INSERT INTO node_value VALUES ('F', 3);
INSERT INTO node_value VALUES ('G', 4);
CREATE TABLE tree (
parent VARCHAR(1),
child VARCHAR(1)
);
INSERT INTO tree VALUES ('A', 'B');
INSERT INTO tree VALUES ('B', 'D');
INSERT INTO tree VALUES ('B', 'E');
INSERT INTO tree VALUES ('A', 'C');
INSERT INTO tree VALUES ('C', 'F');
INSERT INTO tree VALUES ('C', 'G');
This gives me the following tree (nodes and values):
A
|--B(5)
| |--D(2)
| |--E(0)
|
|--C
|--F(3)
|--G(4)
Given the rules above, here are the expected sum values:
A = (5 + 3 + 4) = 12
B = 5
D = 2
E = 0
C = (3 + 4) = 7
F = 3
G = 4
I have written the following SQL, but I can't integrate the recursive UNION and JOIN logic to enforce rule #1 and #2:
WITH recursive treeSum(root, parent, child, total_value) AS (
SELECT tree.parent root, tree.parent, tree.child, node_value.value total_value
FROM tree
LEFT JOIN node_value ON node_value.id = tree.parent
UNION
SELECT treeSum.root, tree.parent, tree.child, node_value.value total_value
FROM tree
INNER JOIN treeSum ON treeSum.child = tree.parent
LEFT JOIN node_value ON node_value.id = tree.parent
)
SELECT root, sum(total_value) FROM treeSum WHERE root = 'A' GROUP BY root
The query returns 10 for root A, but it should be 12. I know the UNION and/or JOIN logic is what's throwing this off. Any help would be appreciated.
EDIT: To clarify, the sum for A is 12, not 14. Given the rules, if a node has a value, grab that value and ignore its children. Because B has a value of 5 we ignore D and E. C has no value, so we grab its children, thus the sum of A = 5(B) + 3(F) + 4(G) = 12. I know it's odd but that's the requirement. Thanks.
EDIT 2: These results will be joined with external datasets so I can't hardcode the root in the WITH clause. For example, I might need something like this:
SELECT root, SUM(total_value) FROM treeSUM GROUP BY root WHERE root = 'A'
This tree is one of many so that means there's multiple roots, specified by calling code--not within the recursive clause itself. Thanks.
EDIT 3: An example of how this will be used in production is the roots will be specified by another table, so I can't hardcode the root into the recursive clause. There might be many roots from many trees.
SELECT id, SUM(COALESCE(value,0)) FROM treeSUM
INNER JOIN roots_to_select rts ON rts.id = treeSUM.id GROUP BY id
SOLUTION (Cleaned up from koriander's answer below)! The following allows roots to be specified by outside sources (either using roots_to_select or WHERE criteria:
WITH recursive roots_to_select AS (
SELECT 'A'::varchar as id
),
treeSum(root, id, value) AS (
select node.id as root, node.id, node_value.value
from node
inner join roots_to_select rts on (node.id = rts.id)
left join node_value on (node.id = node_value.id)
union
select treeSum.root, node.id, node_value.value
from treeSum
inner join tree on (treeSum.id = tree.parent)
inner join node on (tree.child = node.id)
left join node_value on (node.id = node_value.id)
where treeSum.value is null
)
select root, sum(coalesce(value, 0))
from treeSum
group by root
OUTPUT: 12
tested here:
with recursive treeSum(id, value) AS (
select node.id, node_value.value
from node
left join node_value on (node.id = node_value.id)
where node.id = 'A'
union
select node.id, node_value.value
from treeSum
inner join tree on (treeSum.id = tree.parent)
inner join node on (tree.child = node.id)
left join node_value on (node.id = node_value.id)
where treeSum.value is null
)
select sum(coalesce(value, 0)) from treeSum
Edit 1: to combine the result with other table, you can do:
select id, (select sum(coalesce(value, 0)) from treeSum) as nodesum
from node
inner join some_table on (...)
where node.id = 'A'
Edit 2: to support multiple roots based on your Edit 3, you can do (untested):
with recursive treeSum(root, id, value) AS (
select node.id as root, node.id, node_value.value
from node
inner join roots_to_select rts on (node.id = rts.id)
left join node_value on (node.id = node_value.id)
union
select treeSum.root, node.id, node_value.value
from treeSum
inner join tree on (treeSum.id = tree.parent)
inner join node on (tree.child = node.id)
left join node_value on (node.id = node_value.id)
where treeSum.value is null
)
select root, sum(coalesce(value, 0))
from treeSum
group by root
o_O
If I have the following records in a table:
Parent Child
1 2 <--
2 1 <--
3 2
3 4
etc...
And I want to identify records that are both the parent of their child AND the child of their parent such as the 2 records identified by arrows above, how would I accomplish this?
I am trying to run some recursive SQL on this table, but these items are causing an infinite loop. I would like to identify these items so they can be addressed manually.
My brain is fried-enough from messing with recursive queries, I have nothing left to solve this one. Please help :)
If understood you well, you don't need recursion at all:
SELECT a.parent, a.child
FROM table1 a
INNER JOIN table1 b ON (b.child=a.parent and a.child = b.parent)
You might want to use LEFT JOIN instead of INNER if you also need to display rows that don't satisfy condition .
The following query will work in your example case. If it needs more you'll have to extend the demonstration information
;WITH CTE_DATA AS (
Select Parent = 1, Child = 2
union Select Parent = 2, Child = 1
union Select Parent = 3, CHild = 2
union Select Parent = 3, Child = 4
)
select
d1.*
from
CTE_DATA d1
join CTE_DATA d2 on d1.Child = d2.Parent and d2.Child = d1.Parent
DECLARE #YourTable TABLE (Parent INT, Child INT)
INSERT INTO #YourTable
SELECT 1, 2
UNION
SELECT 2, 1
UNION
SELECT 3, 2
UNION
SELECT 3, 4
SELECT *
FROM #YourTable A
INNER JOIN #YourTable B
ON A.Parent = B.Child AND A.Child = B.Parent
I'm trying to write a recursive query in SQL Server that basically lists a parent-child hierarchy from a given parent. A parent can have multiple children and a child can belong to multiple parents so it is stored in a many-to-many relation.
I modified the following query from another somewhat related question, however this doesn't go all the way up to the tree and only selects the first level child...
DECLARE #ObjectId uniqueidentifier
SET #ObjectId = '1A213431-F83D-49E3-B5E2-42AA6EB419F1';
WITH Tree AS
(
SELECT A.*
FROM Objects_In_Objects A
WHERE A.ParentObjectId = #ObjectId
UNION ALL
SELECT B.*
FROM Tree A
JOIN Objects_In_Objects B
ON A.ParentObjectId = B.ObjectId
)
SELECT *
FROM Tree
INNER JOIN Objects ar on tree.ObjectId = ar.ObjectId
Does anyone know how to modify the query to go all the way down the 'tree'? Or is this not possible using the above construction?
Objects
Columns: ObjectId | Name
Objects_In_Objects
Columns: ObjectId | ParentObjectId
Sample data:
Objects
ObjectId | Name
1A213431-F83D-49E3-B5E2-42AA6EB419F1 | Main container
63BD908B-54B7-4D62-BE13-B888277B7365 | Sub container
71526E15-F713-4F03-B707-3F5529D6B25E | Sub container 2
ADA9A487-7256-46AD-8574-0CE9475315E4 | Object in multiple containers
Objects In Objects
ObjectId | ParentObjectId
ADA9A487-7256-46AD-8574-0CE9475315E4 | 71526E15-F713-4F03-B707-3F5529D6B25E
ADA9A487-7256-46AD-8574-0CE9475315E4 | 63BD908B-54B7-4D62-BE13-B888277B7365
63BD908B-54B7-4D62-BE13-B888277B7365 | 1A213431-F83D-49E3-B5E2-42AA6EB419F1
71526E15-F713-4F03-B707-3F5529D6B25E | 1A213431-F83D-49E3-B5E2-42AA6EB419F1
Such a recursive CTE (Common Table Expression) will goo all the way .
Try this:
;WITH Tree AS
(
SELECT A.ObjectID, A.ObjectName, o.ParentObjectID, 1 AS 'Level'
FROM dbo.Objects A
INNER JOIN dbo.Objects_In_Objects o ON A.ObjectID = o.ParentObjectID
WHERE A.ObjectId = #ObjectId -- use the A.ObjectId here
UNION ALL
SELECT A2.ObjectID, A2.ObjectName, B.ParentObjectID, t.Level + 1 AS 'Level'
FROM Tree t
INNER JOIN dbo.Objects_In_Objects B ON B.ParentObjectID = t.ObjectID
INNER JOIN dbo.Objects A2 ON A2.ObjectId = B.ObjectId
)
SELECT *
FROM Tree
INNER JOIN dbo.Objects ar on tree.ObjectId = ar.ObjectId
If you change this - does this work for you now? (I added a Level column - typically helps to understand the "depth" in the hierarchy for every row)
I do seem to get the proper output on my SQL Server instance, at least...
declare #Objects_In_Objects table
(
ObjectID uniqueidentifier,
ParentObjectId uniqueidentifier
)
declare #Objects table
(
ObjectId uniqueidentifier,
Name varchar(50)
)
insert into #Objects values
('1A213431-F83D-49E3-B5E2-42AA6EB419F1', 'Main container'),
('63BD908B-54B7-4D62-BE13-B888277B7365', 'Sub container'),
('71526E15-F713-4F03-B707-3F5529D6B25E', 'Sub container 2'),
('ADA9A487-7256-46AD-8574-0CE9475315E4', 'Object in multiple containers')
insert into #Objects_In_Objects values
('ADA9A487-7256-46AD-8574-0CE9475315E4', '71526E15-F713-4F03-B707-3F5529D6B25E'),
('ADA9A487-7256-46AD-8574-0CE9475315E4', '63BD908B-54B7-4D62-BE13-B888277B7365'),
('63BD908B-54B7-4D62-BE13-B888277B7365', '1A213431-F83D-49E3-B5E2-42AA6EB419F1'),
('71526E15-F713-4F03-B707-3F5529D6B25E', '1A213431-F83D-49E3-B5E2-42AA6EB419F1')
DECLARE #ObjectId uniqueidentifier
SET #ObjectId = '1A213431-F83D-49E3-B5E2-42AA6EB419F1';
WITH Tree AS
(
SELECT A.ObjectID,
A.ParentObjectId
FROM #Objects_In_Objects A
WHERE A.ParentObjectId = #ObjectId
UNION ALL
SELECT B.ObjectID,
B.ParentObjectId
FROM Tree A
JOIN #Objects_In_Objects B
ON B.ParentObjectId = A.ObjectId
)
SELECT *
FROM Tree
INNER JOIN #Objects ar on tree.ObjectId = ar.ObjectId;
Is this what you are looking for? https://data.stackexchange.com/stackoverflow/q/111357/
Can this help you ?
http://www.aghausman.net/sql_server/storingretrieving-hierarchical-data-in-sql-server-database.html