I have the following hierarchical structure:
A -> E -> C -> D
|
|
|-> B -> D
Here is the closure table I've come up with:
| Ancestor | Descendant | Depth |
| A | A | 0 |
| B | B | 0 |
| C | C | 0 |
| D | D | 0 |
| E | E | 0 |
| A | E | 1 |
| A | B | 1 |
| A | C | 2 |
| E | C | 1 |
| A | D | 3 |
| E | D | 2 |
| C | D | 1 |
| A | D | 2 |
| B | D | 1 |
I want to remove the link between B and D, and therefore I want to delete the link between A and D (the one of depth 2). The problem is that I don't want to delete the link between A and D of depth 3 since I didn't delete the link between C and D.
For the moment, here is the SQL statement to list the links I want to delete:
SELECT link.ancestor, link.descendant, link.depth
FROM closure_table p,
closure_table link,
closure_table c
WHERE p.ancestor = link.ancestor
AND c.descendant = link.descendant
AND p.descendant = B
AND c.ancestor = D;
but this statement give me rows I don't want to delete:
| Ancestor | Descendant | Depth |
| A | D | 2 |
| A | D | 3 | <- As said before, I want to keep this one
| B | D | 1 |
You can select the ancestor-descendant pair that has the minimum depth of all of those same ancestor-descendant pairs:
with edges(s, e) as (
-- the pairs to be removed
select 'A', 'D'
union all
select 'B', 'D'
),
n_l as (
select c.* from closure c where c.ancestor != c.descendant
)
select c.* from n_l c where exists (select 1 from edges e where e.s = c.ancestor and e.e = c.descendant)
and c.depth = (select min(c1.depth) from n_l c1 where c1.ancestor = c.ancestor and c1.descendant = c.descendant);
Output:
ancestor
descendant
depth
A
D
2
B
D
1
I think I’ve found the solution, for those who are interested:
declare #Descendant nchar(10) = 'D';
declare #Ancestor nchar(10) = 'B';
with cte as
(
select Ancestor, Depth
from closure_table
where Descendant = #Descendant
and Ancestor = #Ancestor
and Depth = 1
union all
select r.Ancestor, l.Depth + 1 as Depth
from cte as l
join closure_table as r on r.Descendant = l.Ancestor
where r.Depth = 1
)
delete closure_table
from closure_table
join cte on cte.Ancestor = closure_table.Ancestor and cte.Depth = closure_table.Depth
where closure_table.Descendant = #Descendant;
Related
I want to optimize my query to use CTE and some windows functions for better improvement. I am updating existing rows with specified data from other row but they have the same ID number. The code is for MS SQL.
LinkTable:
ID | TYPE | value1 | Value2 | Value3
-----------------------------------------------
234 | MAT | a | b | c
234 | PMS | null | null | null
234 | AMN | null | null | null
45 | MAT | x | m | n
45 | LKM | null | null | null
45 | DFG | null | null | null
3 | MAT | k | s | q
3 | LKM | null | null | null
The result should be:
ID | TYPE | value1 | Value2 | Value3
-----------------------------------------------
234 | MAT | a | b | c
234 | PMS | a | b | c
234 | AMN | a | b | c
45 | MAT | x | m | n
45 | LKM | x | m | n
45 | DFG | x | m | n
3 | MAT | k | s | q
3 | LKM | k | s | q
I used this code:
UPDATE m
SET m.[value1] = l.[value1]
, m.[value2] = l.[value2]
, m.[value2] = l.[value3]
FROM #LinkTable m
INNER JOIN #LinkTable l on l.[ID] = m.[ID]
WHERE l.[type] = 'MAT'
It updates also the main row from which i take the values.
Could anyone help?
Your code is basically fine, but I would add some filters:
UPDATE m
SET m.[value1] = l.[value1],
m.[value2] = l.[value2],
m.[value3] = l.[value3]
FROM #LinkTable m JOIN
#LinkTable l
ON l.[ID] = m.[ID]
WHERE l.[type] = 'MAT' AND
m.type <> 'MAT';
Note: You also have an error in the SET clause. The column value2 is set twice.
You can use window functions like this:
UPDATE m
SET m.[value1] = m.MATvalue1
, m.[value2] = m.MATvalue2
, m.[value3] = m.MATvalue3
FROM (
SELECT *,
MATvalue1 = MIN(CASE WHEN m.[type] = 'MAT' THEN l.value1 END) OVER (PARTITION BY m.ID),
MATvalue2 = MIN(CASE WHEN m.[type] = 'MAT' THEN l.value2 END) OVER (PARTITION BY m.ID),
MATvalue3 = MIN(CASE WHEN m.[type] = 'MAT' THEN l.value3 END) OVER (PARTITION BY m.ID)
FROM #LinkTable m
) m
WHERE m.[type] <> 'MAT';
Note that this may not necessarily be more performant than Gordon's answer
Say I have a table like this:
+----+-------+
| id | value |
+----+-------+
| 1 | a |
| 1 | b |
| 2 | c |
| 2 | d |
| 3 | e |
| 3 | f |
+----+-------+
And I want to select all rows with id that are not a, and change their id to a; select all rows with id that are not b, and change the id to b; and select all rows with id that are not c, and change their id to c.
Here is the output I want:
+----+-------+
| id | value |
+----+-------+
| 1 | c |
| 1 | d |
| 1 | e |
| 1 | f |
| 2 | a |
| 2 | b |
| 2 | e |
| 2 | f |
| 3 | a |
| 3 | b |
| 3 | c |
| 3 | d |
+----+-------+
The only solution I can think of is through cross join and distinct:
select distinct a.id, b.value
from table a
cross join table b
where a.id != b.id
Is there any other way to avoid such expensive operation?
I think the typical way to write this is to generate all pairs of id and value and then remove the ones that exist:
select i.id, v.value
from (select distinct id from t) i cross join
(select distinct value from t) v left join
t
on t.id = i.id and t.value = i.value
where t.id is null;
First, I don't think this is what your query does. But this is what you seem to be describing.
From a performance perspective, you might have other sources for i and v that don't require subqueries. If so, use those for performance.
Finally, I don't think you can do much to improve the performance of this, apart from using explicit tables -- and perhaps having appropriate indexes on all the tables.
I am trying to identify groupings of accounts from a Parent-Child association table in SQL. Rather than a big hierarchy tree, I am dealing with many small trees and I need to identify each Tree as a unique Group in order to label related accounts.
I have two tables, a table of all Unique ID's:
+------+-------+
| ID | Group |
+------+-------+
| A | NULL |
| B | NULL |
| C | NULL |
| etc. | NULL |
+------+-------+
And a Table showing Parent - Child association between them:
+--------+-------+
| Parent | Child |
+--------+-------+
| A | D |
| A | E |
| B | F |
| B | G |
| B | C |
| C | H |
+--------+-------+
I Need to Fill the Group field of my first table so that I can identify all accounts which have a direct or indirect relationship eg:
+----+-------+
| ID | Group |
+----+-------+
| A | 1 |
| B | 2 |
| C | 2 |
| D | 1 |
| E | 1 |
| F | 2 |
| G | 2 |
| H | 2 |
+----+-------+
Where I'm struggling is that a Parent could be a Child to another Parent eg:
Parent B -> Parent -> C -> Child H
These form a Group but there is no direct link between B and H and I am struggling to find a reliable way to identify all associated ID's
This type of logic requires a recursive CTE. The idea is to start at the parents and work your way down the hierarchy:
with cte as (
select row_number() over (order by node) as grp,
n.node as ultimate_parent, n.node as node, 1 as lev
from nodes n
where not exists (select 1 from pc where pc.child = n.node)
union all
select cte.grp, cte.ultimate_parent, pc.child, lev + 1
from cte join
pc
on cte.node = pc.parent
)
update nodes
set grp = cte.grp
from cte
where cte.node = nodes.node;
Here is a db<>fiddle.
I have a SQLite table with comments like:
Id | replyId | commentID_parentID | usernameChannelId | channelId
1 | NULL | NULL | a | g
2 | NULL | NULL | b | k
NULL | 1.k | 1 | a | p
NULL | 1.p | 1 | c | i
3 | NULL | NULL | d | h
NULL | 2.k | 2 | g | g
and a table with channels like:
I want to know which user (userChannelId) replied to which user.
So I take a row with a comment and check if:
Id == NULL? Then it's a reply -> get userChannelId where commentID_parentID == Id
Id != NULL? Then it's a main comment -> userChannelId replied to channelId
And result should be:
userChannelId_Source | userChannelId_Target
a | g
b | k
a | a
c | a
g | b
Comment "d" has no entry where commentID_parentID == Id so it's left out.
How can I do that in SQL when I query in the same table?
It's a rather complicated requirement but I think that a conditional self join will do it:
select t.usernameChannelId userChannelId_Source,
case
when t.id is not null then tt.channelId
else tt.usernameChannelId
end userChannelId_Target
from tablename t inner join tablename tt
on tt.id = coalesce(t.id, t.commentID_parentID)
and exists (
select 1 from tablename
where commentID_parentID = t.id
or (commentID_parentID is null and t.id is null)
)
See the demo.
Results:
| userChannelId_Source | userChannelId_Target |
| -------------------- | -------------------- |
| a | g |
| a | a |
| c | a |
| b | k |
| g | b |
I have table structure like below
id |parent|value
1 | 0 | a |
2 | 1 | b |
3 | 4 | c |
4 | 0 | d |
5 | 0 | e |
I want to display only rows that have a relation parent child
like:
id |parent|value
1 | 0 | a |
2 | 1 | b |
3 | 4 | c |
4 | 0 | d |
every child should have a parent
every parent should have at least one child
This is my code but it does not work properly:
select a.id, a.parent,a.value
from myTable a inner join myTable b
on a.id = b.parent
union
select b.id, b.child,b.value
from myTable a inner join myTable b
on a.id = b.parent;
SELECT
*
FROM
yourTable
WHERE
parent != 0
OR EXISTS (SELECT *
FROM yourTable children
WHERE chilren.parent = yourTable.id
)
The first condition checks if the row points to a parent, and the second condition checks if the row has any children.