SQL combine foreign key values of all parents into child - sql

In an informix database, I have a hierarchical tree structure, where a child entry can have any level of parents (parent, grandparent, etc). via relation to its parent entry.
Every entry has a collection of attribute names and values.
The tables are modeled is as follows:
node:
+-------------+----------------------+--------+
| id | parn_id | name |
+-------------+----------------------+--------+
| int | int | string |
| primary key | existing id, or null | |
+-------------+----------------------+--------+
vals:
+-----------------------+-------------+---------+
| id | atr_id | atr_val |
+-----------------------+-------------+---------+
| int | int | string |
| foreign key from node | primary key | |
+-----------------------+-------------+---------+
look:
+-----------------------+--------+
| atr_id | name |
+-----------------------+--------+
| int | string |
| foreign key from vals | |
+-----------------------+--------+
I need a sql query which returns all of the parent's (vals, look) pairs when asking for a child.
For example, if I have
Parent: (valP, nameP), (valP2, nameP2)
*
* * * Child (valC, nameC)
*
* * * GrandChild (valGC, nameGC)
And I query for the GrandChild, I want it to return:
GrandChild (valP, nameP), (valP2, nameP2), (valC, nameC), (valGC, nameGC)

Using a recent Informix version ( I am using Informix 14.10.FC1 ) you can use the CONNECT BY clause to work with hierarchical queries.
The setup, based on your description:
CREATE TABLE node
(
id INTEGER PRIMARY KEY,
parn_id INTEGER,
name CHAR( 20 ) NOT NULL
);
INSERT INTO node VALUES ( 1, NULL, 'Node_A' );
INSERT INTO node VALUES ( 2, NULL, 'Node_B' );
INSERT INTO node VALUES ( 3, NULL, 'Node_C' );
INSERT INTO node VALUES ( 4, 2, 'Node_D' );
INSERT INTO node VALUES ( 5, 3, 'Node_E' );
INSERT INTO node VALUES ( 6, 3, 'Node_F' );
INSERT INTO node VALUES ( 7, 4, 'Node_G' );
CREATE TABLE vals
(
id INTEGER NOT NULL REFERENCES node( id ),
atr_id INTEGER PRIMARY KEY,
atr_val CHAR( 20 ) NOT NULL
);
INSERT INTO vals VALUES ( 1, 1, 'Value_A_1' );
INSERT INTO vals VALUES ( 2, 2, 'Value_B_1' );
INSERT INTO vals VALUES ( 2, 3, 'Value_B_2' );
INSERT INTO vals VALUES ( 3, 4, 'Value_C_1' );
INSERT INTO vals VALUES ( 3, 5, 'Value_C_2' );
INSERT INTO vals VALUES ( 4, 6, 'Value_D_1' );
INSERT INTO vals VALUES ( 5, 7, 'Value_E_1' );
INSERT INTO vals VALUES ( 5, 8, 'Value_E_2' );
INSERT INTO vals VALUES ( 6, 9, 'Value_F_1' );
INSERT INTO vals VALUES ( 7, 10, 'Value_G_1' );
CREATE TABLE look
(
atr_id INTEGER NOT NULL REFERENCES vals( atr_id ),
name CHAR( 20 ) NOT NULL
);
INSERT INTO look VALUES ( 1, 'Look_A_1' );
INSERT INTO look VALUES ( 2, 'Look_B_1' );
INSERT INTO look VALUES ( 3, 'Look_B_2' );
INSERT INTO look VALUES ( 4, 'Look_C_1' );
INSERT INTO look VALUES ( 5, 'Look_C_2' );
INSERT INTO look VALUES ( 6, 'Look_D_1' );
INSERT INTO look VALUES ( 7, 'Look_E_1' );
INSERT INTO look VALUES ( 8, 'Look_E_2' );
INSERT INTO look VALUES ( 9, 'Look_F_1' );
INSERT INTO look VALUES ( 10, 'Look_G_1' );
we can use the CONNECT BY to find the child parents, for example:
-- Starting from 'Node_G'
SELECT
n.id,
n.parn_id,
n.name,
CONNECT_BY_ROOT n.name AS starting_node
FROM
node AS n
START WITH n.name = 'Node_G'
CONNECT BY PRIOR n.parn_id = n.id
ORDER BY
n.name
;
-- RESULTS:
id parn_id name starting_node
2 Node_B Node_G
4 2 Node_D Node_G
7 4 Node_G Node_G
And then we can join with the attribute tables:
SELECT
vt1.starting_node,
v.atr_val,
l.name
FROM
(
SELECT
n.id,
n.parn_id,
n.name,
CONNECT_BY_ROOT n.name AS starting_node
FROM
node AS n
START WITH n.name = 'Node_G'
CONNECT BY PRIOR n.parn_id = n.id
) AS vt1
INNER JOIN vals AS v
ON
v.id = vt1.id
INNER JOIN look AS l
ON
l.atr_id = v.atr_id
ORDER BY
vt1.starting_node, v.atr_val
;
-- RESULTS:
starting_node atr_val name
Node_G Value_B_1 Look_B_1
Node_G Value_B_2 Look_B_2
Node_G Value_D_1 Look_D_1
Node_G Value_G_1 Look_G_1
If we remove the START WITH clause, we get the hierarchical results for each node:
SELECT
vt1.starting_node,
v.atr_val,
l.name
FROM
(
SELECT
n.id,
n.parn_id,
n.name,
CONNECT_BY_ROOT n.name AS starting_node
FROM
node AS n
CONNECT BY PRIOR n.parn_id = n.id
) AS vt1
INNER JOIN vals AS v
ON
v.id = vt1.id
INNER JOIN look AS l
ON
l.atr_id = v.atr_id
ORDER BY
vt1.starting_node, v.atr_val
;
-- RESULTS:
starting_node atr_val name
Node_A Value_A_1 Look_A_1
Node_B Value_B_1 Look_B_1
Node_B Value_B_2 Look_B_2
Node_C Value_C_1 Look_C_1
Node_C Value_C_2 Look_C_2
Node_D Value_B_1 Look_B_1
Node_D Value_B_2 Look_B_2
Node_D Value_D_1 Look_D_1
Node_E Value_C_1 Look_C_1
Node_E Value_C_2 Look_C_2
Node_E Value_E_1 Look_E_1
Node_E Value_E_2 Look_E_2
Node_F Value_C_1 Look_C_1
Node_F Value_C_2 Look_C_2
Node_F Value_F_1 Look_F_1
Node_G Value_B_1 Look_B_1
Node_G Value_B_2 Look_B_2
Node_G Value_D_1 Look_D_1
Node_G Value_G_1 Look_G_1

Related

Join operations in SQL

I should retrieve the IDs and names of ingredients that are not contained by any ice cream and then
sort the output rows in ascending order by ingredient ID. I don't quite understand how JOIN -operation works in this exercise.
I tried e.g. the following but it gives too many rows in the output.
SELECT ingredient.ingredient_id, ingredient.ingredient_name
FROM ingredient
LEFT JOIN contains ON ingredient.ingredient_id != contains.ingredient_id
LEFT JOIN ice_cream ON ice_cream.ice_cream_id != contains.ice_cream_id
ORDER BY ingredient.ingredient_id;
Output columns should be as follows:
ingredient_id ingredient_name
----------------------------
6 Dark chocolate
and here are the tables:
CREATE TABLE manufacturer (
manufacturer_id INT,
manufacturer_name VARCHAR(30) NOT NULL,
country VARCHAR(30) NOT NULL,
PRIMARY KEY (manufacturer_id),
UNIQUE (manufacturer_name)
);
-- jäätelöitä
-- ice creams
CREATE TABLE ice_cream (
ice_cream_id INT,
ice_cream_name VARCHAR(30) NOT NULL,
manufacturer_id INT NOT NULL,
manufacturing_cost NUMERIC(4,2),
PRIMARY KEY (ice_cream_id),
UNIQUE (ice_cream_name),
FOREIGN KEY (manufacturer_id) REFERENCES manufacturer
);
-- aineksia
-- ingredients
-- plant_based arvo 0 merkitse ei ja arvo 1 merkitsee kyllä
-- plant_based value 0 means no and value 1 means yes
CREATE TABLE ingredient (
ingredient_id INT,
ingredient_name VARCHAR(30) NOT NULL,
kcal INT,
protein NUMERIC(3,1),
plant_based INT,
PRIMARY KEY (ingredient_id),
UNIQUE (ingredient_name)
);
-- jäätelöt sisältävät aineksia
-- ice creams contain ingredients
CREATE TABLE contains(
ice_cream_id INT NOT NULL,
ingredient_id INT NOT NULL,
quantity INT,
PRIMARY KEY (ice_cream_id, ingredient_id),
FOREIGN KEY (ice_cream_id) REFERENCES ice_cream,
FOREIGN KEY (ingredient_id) REFERENCES ingredient
);
--Ice cream manufacturers
INSERT INTO manufacturer VALUES (
1, 'Jen & Berry', 'Canada'
);
INSERT INTO manufacturer VALUES (
2, '4 Friends', 'Finland'
);
INSERT INTO manufacturer VALUES (
3, 'Gelatron', 'Italy'
);
--Ice cream
INSERT INTO ice_cream VALUES (
1, 'Plain Vanilla', 1, 1.00
);
INSERT INTO ice_cream VALUES (
2, 'Vegan Vanilla', 2, 0.89
);
INSERT INTO ice_cream VALUES (
3, 'Super Strawberry', 2, 1.44
);
INSERT INTO ice_cream VALUES (
4, 'Very plain', 2, 1.20
);
--Ingredients
INSERT INTO ingredient VALUES (
1, 'Cream', 400, 3, 0
);
INSERT INTO ingredient VALUES (
2, 'Coconut cream', 230, 2.3, 1
);
INSERT INTO ingredient VALUES (
3, 'Sugar', 387, 0, 1
);
INSERT INTO ingredient VALUES (
4, 'Vanilla extract', 12, 0, 1
);
INSERT INTO ingredient VALUES (
5, 'Strawberry', 33, 0.7, 1
);
INSERT INTO ingredient VALUES (
6, 'Dark chocolate', 535, 8, 1
);
--Contains
INSERT INTO contains VALUES (
1, 1, 70
);
INSERT INTO contains VALUES (
1, 3, 27
);
INSERT INTO contains VALUES (
1, 4, 3
);
INSERT INTO contains VALUES (
2, 2, 74
);
INSERT INTO contains VALUES (
2, 3, 21
);
INSERT INTO contains VALUES (
2, 4, 5
);
INSERT INTO contains VALUES (
3, 1, 60
);
INSERT INTO contains VALUES (
3, 3, 10
);
INSERT INTO contains VALUES (
3, 5, 30
);
INSERT INTO contains VALUES (
4, 2, 95
);
INSERT INTO contains VALUES (
4, 4, 5
);
You can join the "contains" table with the "ingredient" table using a LEFT JOIN and filter all values that have NULL in the "contains.ice_cream_id" field:
SELECT i.ingredient_id,
i.ingredient_name
FROM ingredient i
LEFT JOIN contains c
ON i.ingredient_id = c.ingredient_id
WHERE c.ice_cream_id IS NULL
It appears you are asking to find where somethig does not exist, so it would make sense to express that using not exists
So you just need to find the ingredients that don't exist in the list of contents:
select ingredient_id, ingredient_name
from ingredient i
where not exists (
select * from contains c
where c.ingredient_id = i.ingredient_id
);
Demo Fiddle

Oracle copying the tree in the table

I'm looking for some interesting simple algorithm to copy (clone) part of the tree to another part of the tree.
We have a table:
create table tree (
id integer not null,
parent_id integer,
value varchar2(255),
CONSTRAINT tab_pk PRIMARY KEY (id)
);
begin
insert into tree(id, parent_id, value) values (1, null, 'A');
insert into tree(id, parent_id, value) values (2, 1, 'B');
insert into tree(id, parent_id, value) values (3, 1, 'C');
insert into tree(id, parent_id, value) values (4, 2, 'BC');
insert into tree(id, parent_id, value) values (8, 4, 'BCX');
insert into tree(id, parent_id, value) values (5, 2, 'BD');
insert into tree(id, parent_id, value) values (6, 3, 'CA');
insert into tree(id, parent_id, value) values (7, 3, 'CD');
end;
/
https://dbfiddle.uk/?rdbms=oracle_18&fiddle=9fe802f144a3af0663754cfb3e8dc1ba
How to easily copy a tree "B" (ID = 2) with all children under "CA" (ID = 6)?
ORACLE 18-19c.
I tried to understand your question
Your original query provides this output
select level, id,
lpad ( ' ', level, ' ' ) || value name
from tree
start with parent_id is null
connect by prior id = parent_id;
LE ID NAME
1 1 A
2 2 B
3 4 BC
4 8 BCX
3 5 BD
2 3 C
3 6 CA
3 7 CD
Then you say you want the tree "B" (ID = 2) with all children under "CA" (ID = 6)
select level, id,
lpad ( ' ', level, ' ' ) || value name
from tree
start with id = 2
connect by prior id = parent_id
union all
-- tree to clone
select level, id,
lpad ( ' ', level, ' ' ) || value name
from tree
start with id = 6 or id=7
connect by prior id = parent_id;
LE ID NAME
1 2 B
2 4 BC
3 8 BCX
2 5 BD
1 6 CA
1 7 CD
db<>fiddle

Recursive CTE to find all records SQL Server

I am having below table structure. I want to write a recursive cte to get the bottom most table result.
Highly appreciate your help.
CREATE TABLE Jobcard (
jobcard_id INT NOT NULL PRIMARY KEY,
jobcard_name varchar(20) NOT NULL,
);
CREATE TABLE Vehicle (
vehicle_id INT NOT NULL PRIMARY KEY,
vehicle_name varchar(20) NOT NULL
);
CREATE TABLE Jobacard_vehicle (
jobcard_id INT NOT NULL,
vehicle_id INT NOT NULL
);
INSERT INTO Jobcard (jobcard_id, jobcard_name) VALUES
(1, 'Job1'),(2, 'Job2'),(3, 'Job3'),
(4, 'Job4'),(5, 'Job5'),(6, 'Job6'),
(7, 'Job7'),(8, 'Job8'),(9, 'Job9');
INSERT INTO Vehicle (vehicle_id, vehicle_name) VALUES
(1, 'Vehicle1'),(2, 'Vehicle2'),(3, 'Vehicle3'),
(4, 'Vehicle4'),(5, 'Vehicle5'),(6, 'Vehicle6');
INSERT INTO Jobacard_vehicle (jobcard_id, vehicle_id) VALUES
(3, 1),(4, 2),(5, 3),
(9, 6),(7, 2),(5, 4),
(8, 4),(6, 1),(3, 5);
jobcard_id, vehicle_id
--------------------------
3 1
4 2
5 3
9 6
7 2
5 4
8 4
6 1
3 5
I want to get this result from Jobacard_vehicle table when I pass the vehicle id as 3 as
vehicle id 3 is having jobcard 5
jobcard 5 is having vehicle 4
again vehicle 4 referred to jobcard 8
means all the jobcard refered by 3 or its counter part jobcards vehicles
jobcard_id, vehicle_id
--------------------------
5 3
5 4
8 4
Thank you.
Try to save full path and check it in the next recursion
DECLARE #startVehicleID int=3
;WITH vehCTE AS(
SELECT jobcard_id,vehicle_id,CAST(CONCAT('(',jobcard_id,',',vehicle_id,')') AS varchar(MAX)) [path]
FROM Jobacard_vehicle
WHERE vehicle_id=#startVehicleID
UNION ALL
SELECT v.jobcard_id,v.vehicle_id,c.[path]+CONCAT('(',v.jobcard_id,',',v.vehicle_id,')')
FROM Jobacard_vehicle v
JOIN vehCTE c ON (v.jobcard_id=c.jobcard_id OR v.vehicle_id=c.vehicle_id) AND CHARINDEX(CONCAT('(',v.jobcard_id,',',v.vehicle_id,')'),c.[path])=0
)
SELECT *
FROM vehCTE
ORDER BY [path]
If you need to check Job->Vehicle->Job->Vehicle->... then I think you can use the following
DECLARE #startVehicleID int=3
;WITH vehCTE AS(
SELECT
jobcard_id,
vehicle_id,
CAST(CONCAT('(',jobcard_id,',',vehicle_id,')') AS varchar(MAX)) [path],
1 NextIsJob
FROM Jobacard_vehicle
WHERE vehicle_id=#startVehicleID
UNION ALL
SELECT
v.jobcard_id,
v.vehicle_id,
c.[path]+CONCAT('(',v.jobcard_id,',',v.vehicle_id,')'),
IIF(c.NextIsJob=1,0,1)
FROM Jobacard_vehicle v
JOIN vehCTE c ON ((c.NextIsJob=1 AND v.jobcard_id=c.jobcard_id) OR (c.NextIsJob=0 AND v.vehicle_id=c.vehicle_id)) AND CHARINDEX(CONCAT('(',v.jobcard_id,',',v.vehicle_id,')'),c.[path])=0
)
SELECT *
FROM vehCTE
ORDER BY [path]

Select all recursive value with one query SQLite

i have some another case :
first I created a table:
CREATE TABLE tree(
id_tree integer PRIMARY KEY AUTOINCREMENT,
id_boss TEXT,
id_child TEXT,
answ TEXT);
insert some values :
INSERT INTO tree(id_boss,id_child,answ) VALUES('1','8','T');
INSERT INTO tree(id_boss,id_child,answ) VALUES('1',null,'F');
INSERT INTO tree(id_boss,id_child,answ) VALUES('8','P1','T');
INSERT INTO tree(id_boss,id_child,answ) VALUES('8','2','F');
INSERT INTO tree(id_boss,id_child,answ) VALUES('2','P2','T');
INSERT INTO tree(id_boss,id_child,answ) VALUES('2','P3','F');
and execute query:
WITH RECURSIVE
ancestors(id, answ) AS (
VALUES('P3', 'T')
UNION ALL
SELECT tree.id_boss, tree.answ
FROM tree JOIN ancestors ON tree.id_child = ancestors.id
)
SELECT id FROM ancestors WHERE answ = 'T';
the result is :
P3
1
that for P3 , and i want to make list with all recursive value , so it will be like this :
1 --- // P3
1 --- // P1
8 --- // P1
2 --- // P2
1 --- // P2
Is this somewhere near what you are looking for?
WITH seed (id, answ) as ( VALUES('P3', 'T') )
, ancestors1(id, answ) AS (
select * from seed
UNION ALL
SELECT tree.id_boss, tree.answ
FROM tree, ancestors1 where tree.id_child = ancestors1.id
)
, ancestors2(id, answ) AS (
select * from seed
UNION ALL
SELECT tree.id_boss, tree.answ
FROM tree, ancestors2 where tree.id_child = ancestors2.id
)
select id from (
select * from ancestors1
union all
select * from ancestors2
);
I had to rephrase certain things inorder to get thenm to compile with db2 (remove RECURSIVE, and use implicit join)

How to display row data into different column ORACLE

I'm new to SQL and currently this is what I'm trying to do:
Display multiple rows of data into different columns within the same row
I have a table like this:
CREATE TABLE TRIPLEG(
T# NUMBER(10) NOT NULL,
LEG# NUMBER(2) NOT NULL,
DEPARTURE VARCHAR(30) NOT NULL,
DESTINATION VARCHAR(30) NOT NULL,
CONSTRAINT TRIPLEG_PKEY PRIMARY KEY (T#, LEG#),
CONSTRAINT TRIPLEG_UNIQUE UNIQUE(T#, DEPARTURE, DESTINATION),
CONSTRAINT TRIPLEG_FKEY1 FOREIGN KEY (T#) REFERENCES TRIP(T#) );
INSERT INTO TRIPLEG VALUES( 1, 1, 'Sydney', 'Melbourne');
INSERT INTO TRIPLEG VALUES( 1, 2, 'Melbourne', 'Adelaide');
The result should be something like this:
T# | ORIGIN | DESTINATION1 | DESTINATION2
1 | SYDNEY | MELBORUNE | ADELAIDE
The origin is the DEPARTURE.
DESTINATION1 could either be DEPARTURE OR DESTINATION.
DESTINATION2 is the DESTINATION.
The query should include the COUNT(T#) < 3 since I only need to display the records less than 3. How can i do this with 2 relational view to achieve this result?
Try this:
select t1.T#,
(select t2.departure from tripleg t2 where t1.T# = t2.T# and t2.LEG# = 1) Origin,
(select t2.destination from tripleg t2 where t1.T# = t2.T# and t2.LEG# = 1) Destination1,
(select t2.destination from tripleg t2 where t1.T# = t2.T# and t2.LEG# = 2) Destination2
from tripleg t1
group by t1.T#
having count(1) < 3
You can make use of hierarchical queries.
Data Setup:
CREATE TABLE TRIPLEG ( T# NUMBER ( 10 ) NOT NULL,
LEG# NUMBER ( 2 ) NOT NULL,
DEPARTURE VARCHAR ( 30 ) NOT NULL,
DESTINATION VARCHAR ( 30 ) NOT NULL );
INSERT INTO
TRIPLEG
VALUES
( 1,
1,
'Sydney',
'Melbourne' );
INSERT INTO
TRIPLEG
VALUES
( 1,
2,
'Melbourne',
'Adelaide' );
INSERT INTO
TRIPLEG
VALUES
( 2,
1,
'A',
'B' );
INSERT INTO
TRIPLEG
VALUES
( 2,
2,
'B',
'C' );
INSERT INTO
TRIPLEG
VALUES
( 2,
3,
'C',
'D' );
INSERT INTO
TRIPLEG
VALUES
( 3,
1,
'A',
'B' );
COMMIT;
Query:
SELECT
T#,
CONNECT_BY_ROOT DEPARTURE
|| SYS_CONNECT_BY_PATH ( DESTINATION,
' ~ ' )
AS ROUTE
FROM
TRIPLEG
WHERE
LEVEL = 2
CONNECT BY
NOCYCLE DEPARTURE = PRIOR DESTINATION
START WITH
T# = '1';
Explanation:
Connect rows based on the relationship between Departure and Destination.
Filter for rows satisfying a transit (Strictly)
LEG# is not used.
Kindly try this
select t#,regexp_substr(tree,'[^:]+',2) origin,
regexp_substr(tree,'[^:]+',2,2) destination1,
regexp_substr(tree,'[^:]+',2,3) destination2
from
(
select a.*,
sys_connect_by_path(departure,':') tree,
level lvl
from tripleg a
connect by nocycle prior destination = departure and prior t# = t#
start with leg# = 1
and level < = 3
)
where lvl = 3;