Update Multiple Tables with an Especifc Order between them - sql

Ok, maybe is a tricky question, but I know it could happen to some of us. I will try to make it as shorter as possible.
I have 4 Tables, 1 parent table and 3 child tables. (for this example they have the same columns, in my real case the don't). Like this.
(All the columns in the 4 tables are integer)
PARENT
ID POSITION
1 1
2 1
3 1
4 1
CHILD1
IDPARENT, POSITION
1 2
1 3
1 4
1 6
2 3
2 4
3 2
3 5
CHILD2
IDPARENT, POSITION
1 5
1 7
2 2
3 3
CHILD3
IDPARENT, POSITION
4 2
4 3
3 6
All the tables have a column named position where this are the rules:
The table PARENT always have position = 1
The position number is a consecutive per parent who cannot be duplicated across all the tables. (i.e the parent 1, have children in child1, child2 and child3, but the position is consecutive 1,2,3,4,5,6,7 it doesnt get repeated across them)
The consecutive could not be consecutive in the same table (i.e. parent 1 has in child 1 the position 2 3 4 6, but the 5 and 7 are in the child 2).
A parent can and cannot have children in all the tables (It could have in child1 and child3 athe same time or just child2 or in no one).
These tables are static, it is always the parent and 3 children.
I have written a query to get the idparent with their respective childrens and position.
SELECT C.IDPARENT, '1' AS CHILD, c.POSITION
FROM Child1 c
UNION ALL
SELECT C.IDPARENT, '2' AS CHILD, c.POSITION
FROM Child2 c
UNION ALL
SELECT C.IDPARENT, '3' AS CHILD, c.POSITION
FROM Child3 c
UNION ALL
SELECT P.ID, '0' AS CHILD, P.POSITION
FROM PARENT P
ORDER BY IDPARENT, POSITION, CHILD
It gets this output with the information above.
IDPARENT CHILD POSITION
1 0 1
1 1 2
1 1 3
1 1 4
1 2 5
1 1 6
1 2 7
2 0 1
2 2 2
2 1 3
2 1 4
3 0 1
3 1 2
3 2 3
3 1 5
3 3 6
4 0 1
4 3 2
4 3 3
As you can see, the information get correctly outputed and thats what i want. But there is Bad record here
3 2 3
3 1 5
It jumps from 3 to 5 instead to 4 because sometimes in the table those records get deleted by an outside web application.
So after all of this, this is my problem what refers to the subject of this question.
How could I make a massive update to all those tables, in a proper order, that records like
3 2 3
3 1 5
3 3 6
get converted into
3 2 3
3 1 4
3 3 5
Note: In my example i just did one bad record. In my real case, i got a lot of them.
I have written that query so far, and that's the data that I want to update, but I know you cannot update massive tables, just one by one, but I dont know how to update them with the consecutive order if I just can update one table at time. Because when the next query runs to update the next table, it will have now different data.
Thanks to all of you in advance for reading this long question and for the help provided.

;with cte as
(
SELECT C.IDPARENT, c.POSITION
FROM Child1 c
UNION ALL
SELECT C.IDPARENT, c.POSITION
FROM Child2 c
UNION ALL
SELECT C.IDPARENT, c.POSITION
FROM Child3 c
UNION ALL
SELECT P.ID, P.POSITION
FROM PARENT P
)
select C.*, row_number() over(partition by IDPARENT order by POSITION) as rn
into #tmp
from cte as C
update C
set Position = T.rn
from Child1 as C
inner join #tmp as T
on C.IDParent = T.IDParent and
C.Position = T.Position
update C
set Position = T.rn
from Child2 as C
inner join #tmp as T
on C.IDParent = T.IDParent and
C.Position = T.Position
update C
set Position = T.rn
from Child3 as C
inner join #tmp as T
on C.IDParent = T.IDParent and
C.Position = T.Position
drop table #tmp
Above is the query for you. Here you can try with sample data in table variables. The syntax for the inserts is for SQL Server 2008.

Related

How to select path from nested sets excluding subtrees

I have an SQLite database, which contains trees with red and black nodes (not red-black trees though). Trees are stored as nested sets (https://en.wikipedia.org/wiki/Nested_set_model)
Name TreeId Left Right IsBlack
A 1 1 8 1
B 1 2 7 0
C 1 3 6 1
D 1 4 5 1
A 2 1 10 1
B 2 2 5 0
C 2 6 9 1
D 2 3 4 1
D 2 7 8 1
Both B and C nodes with TreeId = 2 point to D node. So D is written twice.
These trees may contain only black or only red nodes
I would like to select all paths for the specified node, that do not contain red nodes, i. e. exclude red nodes and all their subtrees from result
Examples:
For record:
Name TreeId Left Right IsBlack
A 1 1 8 1
the result will be:
Name TreeId Left Right IsBlack
A 1 1 8 1
For record:
Name TreeId Left Right IsBlack
C 1 3 6 1
the result will be:
Name TreeId Left Right IsBlack
C 1 3 6 1
D 1 4 5 1
And, finally, for record:
Name TreeId Left Right IsBlack
A 2 1 10 1
The result will be:
Name TreeId Left Right IsBlack
A 2 1 10 1
C 2 6 9 1
D 2 7 8 1
To keep it simple, let's assume that there is another query, which selects TreeId, Left and Right parameters of the searched node by its name.
So I came up with the following query (for node A):
SELECT Nodes.* FROM Nodes
LEFT JOIN Nodes as n ON Nodes.[TreeId] = n.TreeId AND n.IsBlack = 0 AND Nodes.Left >= n.Left AND Nodes.Right <= n.Right
AND n.Left >= 1 AND n.Right <= 10
WHERE Nodes.TreeId = 2 AND Nodes.Left >= 1 AND Nodes.[Right] <= 10 AND n.Name IS NULL
The query seems to work, but it's terribly slow because of left join even with indexes.
So I was wondering, is there a way to optimize the query in terms of SQLite avoiding left join (for example using inner joins, unions, etc)
P.S. I cannot change the way the data is stored, but I can modify database schema (add new fields).
P.P.S. I understand, that I can query all results, and then filter them on the code side. The other option is to store two types of trees in db: one with red and black nodes, and the other one with black nodes only. However, both of these solutions are last resorts.
Thanks in advance
Okay, I don't know if it'll be faster or not - can't tell with only a few rows of data, but this one uses a recursive CTE to at least get the same results as your examples:
WITH RECURSIVE n AS
(SELECT * FROM nodes WHERE name= ?1 AND treeid = ?2 AND isblack = 1
UNION ALL
SELECT n2.name, n2.treeid, n2.left, n2.right, n2.isblack
FROM n
JOIN nodes AS n2
ON n2.treeid = n.treeid
AND (n2.left = n.left + 1 OR n2.right = n.right - 1)
WHERE n2.isblack = 1)
SELECT * FROM n ORDER BY name
You'll probably want an index on nodes(isblack, treeid, name) (And don't forget to run a PRAGMA optimize once in a while.) Bind/replace ?1 and ?2 with the obvious values for a particular run of the query.

Get all of child nodes in a hierarchical data in SQL

I have the following sql table:
id parent_id
1 null
2 1
3 4
4 8
5 1
6 2
7 6
8 null
How can i get all of child nodes of some specific node?
For example with id = 1:
1 2 5 6 7
with id = 8
8 4 2
I've figured out in another blog, hope it will help other people:
with RECURSIVE cte(id,parent_id) as (
select
id,
parent_id
from forum
where id = 2
UNION ALL
select
forum.id,
forum.parent_id
from forum
inner join cte on forum.parent_id = cte.id
)
select * from cte

Oracle SQL index rows based on group by/ parent row

I have 1 table, which refers by value to rows in the same table
Example table:
ID PARENT_ID NAME
1 0 john
2 1 jane
3 2 smigy
4 2 gujo
5 1 duby
6 1 ruby
7 5 foo
8 2 bar
9 3 baz
10 3 qux
The root-parent has parent 0 (just so it wouldn't be null), in this case there's
1 root-parent - parent(0)=1.
root-parent has 1lvl children - parent(1)=2;5;6.
1lvl children has 2lvl children - parent(2)=3;4;8. parent(5)=7. parent(6) has nothing.
2lvl children has 3lvl children - parent(3)=9;10. parent(4) has nothing. parent(8) has nothing.
There is no lvl4 children or anything with depth beyond 4.
And I need to create a script (presumably SQL query - need to avoid function/procedure/etc.) that would index rows based on their position under their parent.
Just like if I'd select all root-parent's and get (rownum-1)
The goal table should look like this:
ID PARENT_ID NAME ROW_INDEX
1 0 john 0
2 1 jane 0
3 2 smigy 0
4 2 gujo 1
5 1 duby 1
6 1 ruby 2
7 5 foo 0
8 2 bar 2
9 3 baz 0
10 3 qux 1
I'm planing to add this column and thus the query will be executed only once. I've played by selecting seperate depth rows, but then I don't really know how to count inside/between group by (even if that is possible).
P.S. A better/good column name suggestion would also be very appreciated.
User row_number()
select mt.*, row_number() over(partition by parent_id order by id) - 1 as rn
from MyTable mt

Remove Duplicate Nodes from SQL Server Hierarchy

Looking at the below data is there an easy way to find IDs that exist at a higher level under the same leaf and remove them.
E.g. ID 4,5 exists in rows 4,5 , 8,9 and 12,13.
i want to remove rows 4,5 as the same IDs exist further down the hierarchy (rows 8,9), but rows 12,13 stay as they are on a separate leaf.
Rows to be Removed
row ID Path
1 1 /1/
2 2 /1/2/
3 3 /1/2/3/
4 4 /1/2/3/4/
5 5 /1/2/3/5/
6 6 /1/2/3/6/
7 7 /1/2/3/6/7/
8 4 /1/2/3/6/4/
9 5 /1/2/3/6/5/
10 8 /1/2/8/
11 7 /1/2/8/7/
12 4 /1/2/8/4/
13 5 /1/2/8/5/
example for max ID len = 1
select *
--delete t1
from table as t1
where exists(
select *
from table as t2
where left(t2.path, len(t1.path - 2)) = left(t1.path, len(t1.path - 2))
and charindex(right(t1.path, 2), t2.path, len(t1.path)) > 0
)
This is my approach it should work for any length of ID:
with
conversed as (
select substring(reverse(path),CHARINDEX('/',reverse(path),2), len(path)-CHARINDEX('/',reverse(path),2)) inverse_path, id, t.row
from t)
select c.id, c.row keeper, c1.row deletethis
from conversed c join conversed c1
on c.id = c1.id
and c.row <> c1.row
and c.inverse_path like '%' +c1.inverse_path;
OUTPUT
id keeper deletethis
4 8 4
5 9 5

Oracle , select relation

I have 2 tables in oracle DB: Items and Relationship.
items:
ID
---
1
2
3
4
5
6
7
relationship:
ID parent child
--------------------
1 1 2
2 1 3
3 1 4
4 2 5
5 2 6
6 3 7
In the relationship table, I'm storing the hierarchial structure of the "items" (do not ask why it's stored in different tables).
The question:
When I execute this query:
SELECT PARENT_ID, CHILD_ID, CONNECT_BY_ISLEAF, MAX(LEVEL) OVER () + 1 - LEVEL as rev_level
FROM relationship
CONNECT BY PRIOR PARENT_ID = CHILD_ID
START WITH CHILD_ID = 7;
I do not see the root parent because he doesn't exist in this table as a child.
The question is how can I add the root parent(ID = 1) to the query result or join it whith the "items" table and keep the result columns (level and isleaf).
CONNECT_BY_ISLEAF works the other way around when using bottom up search (see this link http://technology.amis.nl/2009/11/14/oracle-11gr2-alternative-for-connect_by_isleaf-function-for-recursive-subquery-factoring-dedicated-to-anton/)
I assume that you want to show more data about the item (like name) If that is the case just left join the items table.
SELECT PARENT_ID AS PARENT_ID,CHILD_ID, i.name AS CHILD_NAME,
CONNECT_BY_ISLEAF,
MAX(LEVEL) OVER () + 1 - LEVEL AS rev_level
FROM items i
LEFT JOIN relationship r ON (i.id = r.CHILD_ID)
CONNECT BY PRIOR PARENT_ID = CHILD_ID
START WITH CHILD_ID = 7
ORDER BY REV_LEVEL;
check this SQLfiddle: http://sqlfiddle.com/#!4/5c9fa/17
In addition check this post about bottom up searches (http://bitbach.wordpress.com/2010/10/18/implementing-bottom-up-path-traversal-for-hierarchical-tables/)
Notice that you have both directions - parent and child.
pick one and dont mix the two.
1 with x as (
2 select 1 as id, 1 as parent, 2 as child from dual union all
3 select 2, 1 , 3 from dual union all
4 select 3 ,1, 4 from dual union all
5 select 4 ,2, 5 from dual union all
6 select 5 ,2, 6 from dual union all
7 select 6 ,3, 7 from dual)
8 select *
9 from x
10 sTART WITH child = 7
11* CONNECT BY PRIOR id= CHILD
SQL> /
ID PARENT CHILD
---------- ---------- ----------
6 3 7
5 2 6
4 2 5
3 1 4
2 1 3
1 1 2
connection is made by prior id = child and not prior parent = child