copy rows recursively in self-referencing table in sql - sql

I have a table like this :
CREATE TABLE IF NOT EXISTS THING(
Id int NOT NULL IDENTITY(1, 1),
IdParent int,
randomtext varchar(255)
)
I would like to copy given list of ids with their children, grandchildren, but when I try it keeps ancient parent Id so the hierarchy is not kept
given something like this :
| Id | IdParent | random text |
-------------------------------
| 1 | 0 | "aaaaaaaaa" |
| 2 | 1 | "tt" |
| 3 | 2 | "third" |
| 4 | 0 | "fourth" |
| 5 | 0 | "randOther" |
If I give the ids list (1, 4), and another parntid value like "10", it copies rows with id 1 and 4 with the new parent id as 10 but the duplicated children get new inserted parents so it keeps the hierarchy
| Id | IdParent | random text |
-------------------------------------
| 1 | 0 | "aaaaaaaaa" |
| 2 | 1 | "tt" |
| 3 | 2 | "third" |
| 4 | 0 | "fourth" |
| 5 | 0 | "randOther" |
| 6 | 10 | "aaaaaaaaa -copy" |
| 7 | 6 | "tt- copy" |
| 8 | 7 | "third- copy" |
| 9 | 10 | "fourth- copy" |
All i managed to do for now is place all copies into the new parent with this query but I don't want to place all children in the new parent
WITH HIERARCHY (Id)
AS (SELECT Id FROM THING
WHERE Id IN (1,4)
UNION ALL
SELECT e.Id
FROM THING e
INNER JOIN HIERARCHY h ON e.IdParent = h.Id
)
INSERT INTO THING (IdParent, randomtext)
SELECT 10, randomtext
FROM THING
WHERE Id IN (SELECT * FROM HIERARCHY)
(the query(ies) should work in sql server 2008 and oracle)

Related

Count without using functions (like count) oracle

I have two tables:
TABLE A :
CREATE TABLE z_ostan ( id NUMBER PRIMARY KEY,
name VARCHAR2(30) NOT NULL CHECK (upper(name)=name)
);
TABLE B:
CREATE TABLE z_shahr ( id NUMBER PRIMARY KEY,
name VARCHAR2(30) NOT NULL CHECK (upper(name)=name),
ref_ostan NUMBER,
CONSTRAINT fk_ref_ostan FOREIGN KEY (ref_ostan) REFERENCES z_ostan(id)
);
How can I find the second and third place "id" from -Table A- The least used table B in the table? Without using predefined functions like "count()"
This only processes existing references to Table A.
Updated for oracle (used 12c)
Without using any aggregate or window functions:
Sample data for Table: tblb
+----+---------+---------+
| id | name | tbla_id |
+----+---------+---------+
| 1 | TBLB_01 | 1 |
| 2 | TBLB_02 | 1 |
| 3 | TBLB_03 | 1 |
| 4 | TBLB_04 | 1 | 4 rows
| 5 | TBLB_05 | 2 |
| 6 | TBLB_06 | 2 |
| 7 | TBLB_07 | 2 | 3 rows
| 8 | TBLB_08 | 3 |
| 9 | TBLB_09 | 3 |
| 10 | TBLB_10 | 3 |
| 11 | TBLB_11 | 3 |
| 12 | TBLB_12 | 3 |
| 13 | TBLB_13 | 3 | 6 rows
| 14 | TBLB_14 | 4 |
| 15 | TBLB_15 | 4 |
| 16 | TBLB_16 | 4 | 3 rows
| 17 | TBLB_17 | 5 | 1 row
| 18 | TBLB_18 | 6 |
| 19 | TBLB_19 | 6 | 2 rows
| 20 | TBLB_20 | 7 | 1 row
+----+---------+---------+
There are many ways to express this logic.
Step by step with CTE terms.
The intent is (for each set of tbla_id rows in tblb)
generate a row_number (n) for the rows in each partition.
We would normally use window functions for this.
But I assume these are not allowed.
Use this row_number (n) to determine the count of rows in each tbla_id partition.
To find that count per partition, find the last row in each partition (from step 1).
Order the results of step 2 by n of these last rows.
Choose the 2nd and 3rd row of this result
Done.
WITH first AS ( -- Find the first row per tbla_id
SELECT t1.*
FROM tblb t1
LEFT JOIN tblb t2
ON t1.id > t2.id
AND t1.tbla_id = t2.tbla_id
WHERE t2.id IS NULL
)
, rnum (id, name, tbla_id, n) AS ( -- Generate a row_number (n) for each tbla_id partition
SELECT f.*, 1 FROM first f UNION ALL
SELECT n.id, n.name, n.tbla_id, c.n+1
FROM rnum c
JOIN tblb n
ON c.tbla_id = n.tbla_id
AND c.id < n.id
LEFT JOIN tblb n2
ON n.tbla_id = n2.tbla_id
AND c.id < n2.id
AND n.id > n2.id
WHERE n2.id IS NULL
)
, last AS ( -- Find the last row in each partition to obtain the count of tbla_id references
SELECT t1.*
FROM rnum t1
LEFT JOIN rnum t2
ON t1.id < t2.id
AND t1.tbla_id = t2.tbla_id
WHERE t2.id IS NULL
)
SELECT * FROM last
ORDER BY n, tbla_id OFFSET 1 ROWS FETCH NEXT 2 ROWS ONLY
;
Final Result, where n is the count of references to tbla:
+------+---------+---------+------+
| id | name | tbla_id | n |
+------+---------+---------+------+
| 20 | TBLB_20 | 7 | 1 |
| 19 | TBLB_19 | 6 | 2 |
+------+---------+---------+------+
Some intermediate results...
last CTE term result. The 2nd and 3rd rows of this become the final result.
+------+---------+---------+------+
| id | name | tbla_id | n |
+------+---------+---------+------+
| 17 | TBLB_17 | 5 | 1 |
| 20 | TBLB_20 | 7 | 1 |
| 19 | TBLB_19 | 6 | 2 |
| 7 | TBLB_07 | 2 | 3 |
| 16 | TBLB_16 | 4 | 3 |
| 4 | TBLB_04 | 1 | 4 |
| 13 | TBLB_13 | 3 | 6 |
+------+---------+---------+------+
rnum CTE term result. This provides the row_number over tbla_id partitions ordered by id
+------+---------+---------+------+
| id | name | tbla_id | n |
+------+---------+---------+------+
| 1 | TBLB_01 | 1 | 1 |
| 2 | TBLB_02 | 1 | 2 |
| 3 | TBLB_03 | 1 | 3 |
| 4 | TBLB_04 | 1 | 4 |
| 5 | TBLB_05 | 2 | 1 |
| 6 | TBLB_06 | 2 | 2 |
| 7 | TBLB_07 | 2 | 3 |
| 8 | TBLB_08 | 3 | 1 |
| 9 | TBLB_09 | 3 | 2 |
| 10 | TBLB_10 | 3 | 3 |
| 11 | TBLB_11 | 3 | 4 |
| 12 | TBLB_12 | 3 | 5 |
| 13 | TBLB_13 | 3 | 6 |
| 14 | TBLB_14 | 4 | 1 |
| 15 | TBLB_15 | 4 | 2 |
| 16 | TBLB_16 | 4 | 3 |
| 17 | TBLB_17 | 5 | 1 |
| 18 | TBLB_18 | 6 | 1 |
| 19 | TBLB_19 | 6 | 2 |
| 20 | TBLB_20 | 7 | 1 |
+------+---------+---------+------+
There are a few other ways to tackle this problem in just SQL.

T-SQL Query to Pull a Parent's Tags and all Children's Tags

Backgroud
I have a DB Schema in SQL Server that looks like the following:
http://sqlfiddle.com/#!18/dc3cf/3
| id | tag | child_id |
|---- |----- |---------- |
| 1 | A | |
| 1 | | 4 |
| 2 | C | |
| 3 | C | |
| 4 | B | |
| 4 | | 5 |
| 5 | D | |
| 5 | E | |
Each 'id' record may have a child (which is stored in the same table). Each child may have any number of sub children. I won't know the number of hierarchy levels but it will probably be no more than 10 levels deep.
In the example, there are:
3 root elements: id's 1, 2, 3
1 child and 1 child's child of id 1: id's 4 and 5 (respectively)
Question
I need to be able to query to get a result of all of an id's tags including all of it's children's tags. For example, I would need my output to be the following based on the table data above:
| id | tag |
|---- |----- |
| 1 | A |
| 1 | B |
| 1 | D |
| 1 | E |
| 2 | C |
| 3 | C |
| 4 | B |
| 5 | D |
| 5 | E |
Note that I need the children to still appear.
Is this possible without joining the table to itself 'n' many times where 'n' is the number of hierarchy levels?
Edit
To clarify, each id is also a root elements. So the only way to know if an ID is also a child is to look and see if the id has another record where it has a child_id. I made another version of the SQL Fiddle that demonstrates this point. Note that id 2 now has a child:
http://sqlfiddle.com/#!18/422f9/1
| id | tag | child_id |
|---- |----- |---------- |
| 1 | A | |
| 1 | | 4 |
| 2 | C | |
| 2 | | 5 |
| 3 | C | |
| 4 | B | |
| 4 | | 5 |
| 5 | D | |
| 5 | E | |
Yes, it is possible without joining the table n-times, you can use a CTE (Common Table Expression), see Docs
In your situation the code should be something like:
WITH cte (id, tag, child_id, parent) AS
(
SELECT id, tag, child_id, id
FROM demo
WHERE id NOT IN(SELECT child_id FROM demo WHERE child_id IS NOT NULL)
UNION ALL
SELECT demo.id, demo.tag, demo.child_id, cte.parent
FROM demo
JOIN cte
ON cte.child_id = demo.id
)
SELECT parent, tag
FROM cte
WHERE tag IS NOT NULL
UNION
SELECT id, tag
FROM demo
WHERE tag IS NOT NULL
ORDER BY parent, tag
Edit: Determine base elements dynamically.

SQL Update a table column with a sequence of values

I have a situation where I am required to create a copy of the data of one table within itself with a different range of foreign key in one of the columns. For example:
--------------------------------------------------------------
|TYPES |ITEMS |SUBITEMS |
|--------------|----------------------|----------------------|
| ID | VALUE | ID | VALUE | TYPEID | ID | VALUE | ITEMID |
|----|---------|----|--------|--------|----|--------|--------|
| 1 | TYPE1 | 1 | ITEMA | 1 | 1 | SUB1 | 1 |
| 2 | TYPE2 | 2 | ITEMB | 1 | 2 | SUB2 | 2 |
| | | 3 | ITEMC | 1 | 3 | SUB3 | 3 |
| | | 4 | ITEMD | 2 | | | |
| | | 5 | ITEME | 2 | | | |
| | | 6 | ITEMF | 2 | | | |
--------------------------------------------------------------
Here I have to copy from SUBITEMS and insert back but with ITEMIDs that have TYPEID as 2 resulting in the following example:
--------------------------------------------------------------
|TYPES |ITEMS |SUBITEMS |
|--------------|----------------------|----------------------|
| ID | VALUE | ID | VALUE | TYPEID | ID | VALUE | ITEMID |
|----|---------|----|--------|--------|----|--------|--------|
| 1 | TYPE1 | 1 | ITEMA | 1 | 1 | SUB1 | 1 |
| 2 | TYPE2 | 2 | ITEMB | 1 | 2 | SUB2 | 2 |
| | | 3 | ITEMC | 1 | 3 | SUB3 | 3 |
| | | 4 | ITEMD | 2 | 4 | SUB1 | 4 |
| | | 5 | ITEME | 2 | 5 | SUB2 | 5 |
| | | 6 | ITEMF | 2 | 6 | SUB3 | 6 |
--------------------------------------------------------------
EDIT 2: If the amount of rows differ in either of the tables (4 Items while 3 SubItems or 3 Items while 4 SubItems) then only those rows should be considered that are enough for a 1:1 relation between the two tables (3 result since that is the least count among either) as shown in the following example.
--------------------------------------------------------------
|TYPES |ITEMS |SUBITEMS |
|--------------|----------------------|----------------------|
| ID | VALUE | ID | VALUE | TYPEID | ID | VALUE | ITEMID |
|----|---------|----|--------|--------|----|--------|--------|
| 1 | TYPE1 | 1 | ITEMA | 1 | 1 | SUB1 | 1 |
| 2 | TYPE2 | 2 | ITEMB | 1 | 2 | SUB2 | 2 |
| | | 3 | ITEMC | 1 | 3 | SUB3 | 3 |
| | | 4 | ITEMD | 2 | 4 | SUB1 | 4 |
| | | 5 | ITEME | 2 | 5 | SUB2 | 5 |
| | | 6 | ITEMF | 2 | 6 | SUB3 | 6 |
| | | 7 | ITEMG | 2 | | | |
--------------------------------------------------------------
Of course the actual data isn't as simple and has many other types and items n subitems and the required IDs would be missing some sequence like 10001, 10008, 40042, etc with many other columns all defining what data is being copied and which IDs need to be thrown over them. It's just the matter of how each data row obtained should get mapped 1:1 to each ID obtained (assuming both as if in their own temp tables before the moment of this merger). Following is a sample of what I am able to do so far:
CREATE TABLE #SubItemsTemp (Value VARCHAR(100))
CREATE TABLE #ItemIDsTemp (TypeID INT)
INSERT INTO #SubItemsTemp (Value)
SELECT
SI.Value
FROM
SubItems SI
JOIN Items IT ON SI.ItemID = IT.ID
WHERE
IT.TypeID = 1
INSERT INTO #ItemIDsTemp(Value)
SELECT IT.ID
FROM Items IT
WHERE IT.TypeID = 2
--What next?
EDIT 1: Forgot to mention the actual question line... How to insert them together into the SUBITEMS table such that the second example comes to fruition?
Footnote: This is a extreme simplification of the actual queries that have several joins to get to "TYPE"
Try this query. Query assumes that ID column in SUBITEMS table is identity and will work only with TypeId's 1 and 2
declare #TYPES table(ID int, VALUE varchar(100))
declare #ITEMS table(ID int, VALUE varchar(100), TYPEID int)
declare #SUBITEMS table(ID int identity(1,1), VALUE varchar(100), ITEMID int)
insert into #TYPES values (1, 'TYPE1'), (2, 'TYPE2')
insert into #ITEMS values (1, 'ITEMA', 1), (2, 'ITEMB', 1), (3, 'ITEMC', 1), (4, 'ITEMD', 2), (5, 'ITEME', 2), (6, 'ITEMF', 2), (7, 'ITEMG', 2)
insert into #SUBITEMS values ('SUB1', 1), ('SUB2', 2), ('SUB3', 3)
; with cte_1 as (
select
s.VALUE, rn = row_number() over (order by i.ID)
from
#ITEMS i
join #SUBITEMS s on s.ITEMID = i.ID
where
i.TYPEID = 1
)
, cte_2 as (
select
ID, rn = row_number() over (order by ID)
from
#ITEMS
where
TYPEID = 2
)
insert into #SUBITEMS
select
a.VALUE, b.ID
from
cte_1 a
join cte_2 b on a.rn = b.rn
select * from #SUBITEMS
Output
ID Value ItemId
------------------
1 SUB1 1
2 SUB2 2
3 SUB3 3
4 SUB1 4
5 SUB2 5
6 SUB3 6

Select Tree Structure from two tables

let us assume I have Table A
| PK | Name |
-------------
| 1 | AA |
| 2 | BB |
| 3 | CC |
and table B
| PK | FK | Value |
-------------------
| 1 | 1 | i |
| 2 | 1 | j |
| 3 | 2 | x |
| 4 | 2 | y |
| 5 | 3 | l |
| 6 | 3 | k |
how can I select the below result
| PK | Name |
-------------
| 1 | AA |
| 1 | i |
| 2 | j |
| 2 | BB |
| 3 | x |
| 4 | y |
| 3 | CC |
| 3 | l |
| 4 | k |
List parents and under each parent list its children
Many Thanks for help
Very interesting table design. I believe it's just a matter of unioning your data and then ordering your results how you want them. If it's only a single level child-parent relationship, this should work just fine.
If Object_Id('tempdb..#TableA') Is Not Null Drop Table #TableA;
If Object_Id('tempdb..#TableB') Is Not Null Drop Table #TableB;
Select * Into #TableA
From (Values (1,'AA'),(2,'BB'),(3,'CC')) As a(PK,[Name])
Select * Into #TableB
From (Values (1,1,'i'),(2,1,'j'),(3,2,'x'),(4,2,'y'),(5,3,'l'),(6,3,'k')) As a(PK,FK,[Value])
;With Cte
As
(
Select PK,[Name],PK As OrderID,0 As LevelID /*Used to ensure parents are put above children*/
From #TableA
Union All
Select PK,[Value],FK,1
From #TableB
)
Select [PK], [Name]
From Cte
Order By OrderID,LevelID
Results:
PK | Name
1 | AA
1 | i
2 | j
2 | BB
3 | x
4 | y
3 | CC
5 | l
6 | k
Note: My last two rows(l and k) are a bit different than results. I assumed you it was typo when you put 3 and 4 as the id's rather than 5 and 6

Parent Child Hierarchy to Return All Descendants with Corresponding Primary ID

I have a parent child hierarchy table. I am trying to return a list of all of the child ID's for each child ID. My table is defined as follows:
CREATE TABLE Organization_Hierarchy_Test (ORGANIZATION_ID INT, PARENT_ORG_ID INT);
INSERT INTO Organization_Hierarchy_Test (ORGANIZATION_ID, PARENT_ORG_ID)
VALUES(1,0), (2,1), (3,1), (4,2), (5,2), (6,2), (7,3), (8,3), (9,3), (10,3);
The results that I am after would look like this:
+-----------------+---------------+--------------------------+
| ORGANIZATION_ID | PARENT_ORG_ID | ORIGINAL_ORGANIZATION_ID |
+-----------------+---------------+--------------------------+
| 1 | 0 | 1 |
| 2 | 1 | 1 |
| 3 | 1 | 1 |
| 4 | 2 | 1 |
| 5 | 2 | 1 |
| 6 | 2 | 1 |
| 7 | 3 | 1 |
| 8 | 3 | 1 |
| 9 | 3 | 1 |
| 10 | 3 | 1 |
| 2 | 0 | 2 |
| 3 | 0 | 2 |
| 4 | 1 | 2 |
| 5 | 1 | 2 |
| 6 | 1 | 2 |
| 7 | 1 | 2 |
| 8 | 1 | 2 |
| 9 | 1 | 2 |
| 10 | 1 | 2 |
| 4 | 0 | 4 |
| 5 | 0 | 4 |
| 6 | 0 | 4 |
| 7 | 0 | 4 |
| 8 | 0 | 4 |
| 9 | 0 | 4 |
| 10 | 0 | 4 |
+-----------------+---------------+--------------------------+
The query that I have written gets me a list of all of the descendants for each organization_id, but I can not figure out how to return the same organization_id that is in fact related to all of the descendants.
I have tried adding a group by and returning the max id with little luck. I have a delivery date tomorrow and I am worried that I am not going to be able to work through this in time.
with descendants as
( select PARENT_ORG_ID, ORGANIZATION_ID, 1 as level
from Organization_Hierarchy_Test OH
union all
select d.PARENT_ORG_ID , OH1.ORGANIZATION_ID, d.level + 1
from descendants as d
join Organization_Hierarchy_Test OH1 on d.ORGANIZATION_ID = OH1.PARENT_ORG_ID
)
select ORGANIZATION_ID, PARENT_ORG_ID, level
from descendants
order by level, PARENT_ORG_ID, ORGANIZATION_ID
Any ideas on how to return the original Organization_ID along with all of the descendant organization_id's?
I am trying to push this to a tabular model and this will save me loads of time in processing the data.
Thanks very much in advance.
Change your CTE to simply include an extra column d.ORGANIZATION_ID AS Orig:
with descendants as
( select PARENT_ORG_ID, ORGANIZATION_ID, ORGANIZATION_ID AS Orig, 1 as level
from Organization_Hierarchy_Test OH
union all
select d.PARENT_ORG_ID , OH1.ORGANIZATION_ID, d.ORGANIZATION_ID AS Orig, d.level + 1
from descendants as d
join Organization_Hierarchy_Test OH1 on d.ORGANIZATION_ID = OH1.PARENT_ORG_ID
)