Merge two rows with parent and child ID's - sql

I've just started using SQL and I bumped into this problem. There must be a very easy solution I assume. All relations are saved in the relations table. Then there is a parent and a child. The parent being a company and the child being a contact of this company. Every relation has a RelID but the relation_relation table is used to split company and contact.
The problem I'm having is that I can't seem to get the company and the contact into one row like so:
| nameCompany | nameContact |
-----------------------------
|random B.V. | emmma |
|random B.V. | jason |
I have two tables which I want to query. These are simplified versions with a
few example values:
CREATE TABLE relations_relations (parentRelID INT, childRelID INT);
INSERT INTO `relations_relations` VALUES (1, 1);
INSERT INTO `relations_relations` VALUES (1, 2);
INSERT INTO `relations_relations` VALUES (1, 3);
and
CREATE TABLE relations (RelID, nameContact, nameCompany);
INSERT INTO `relations` VALUES (1, NULL, random B.V.);
INSERT INTO `relations` VALUES (2, emma, NULL);
INSERT INTO `relations` VALUES (3, jason, NULL);

You need to JOIN the relation table to itself via the relations_relations table:
SELECT p.nameCompany
,c.nameContact
FROM relations p
INNER JOIN relations_relations rr
ON p.RelID = rr.parentRelID
INNER JOIN relations c
ON c.RelID = rr.childRelID

i figured it out myself.
you're supposed to join two time and use a different condition
both times.
in this problem it should be:
Select *
from Test_Relations_Relations a
inner join Test_relations b
on a.childrelID = b.RelID
inner join Test_relations c
on a.parentrelID = b.relID

Related

SQL insert multiple rows depending on number of rows returned from subquery

I have 3 SQL tables Companies, Materials and Suppliers as follows.
Tables
I need to insert values into Suppliers from a list which contains Company Name and Material Name as headers. However, I have multiple companies with the same name in the database and i need to add a new value into suppliers for each one of those companies.
For e.g. my list containes values ['Wickes','Bricks'] . I have this sql below to add a new entry into the suppliers table but since i have multple companies called 'Wickes' I'll get an error as the subquery will return more than 1 value.
INSERT INTO Suppliers(Id,CompanyId,MaterialId) VALUES (NEWID(), (SELECT Id FROM Companies WHERE Name = 'Wickes'),(SELECT Id FROM Materials WHERE Name = 'Bricks'))
Whats the best solution to get the Id of all the companies there are called 'Wickes' and then add vales into the suppliers table with that Id and the relevant material Id of 'Bricks'.
You can use INSERT () SELECT.. rather than INSERT () VALUES(), e.g
INSERT INTO Suppliers (Id, CompanyId, MaterialId)
SELECT NEWID(), c.Id, m.Id
FROM Companies AS c
CROSS JOIN Materials AS m
WHERE c.Name = 'Wickes'
AND m.Name = 'Bricks';
This will ensure that if you have multiple companies/materials with the same name, all permutations are inserted. Example on db<>fiddle
Although based on your image Suppliers.Id is an integer, so I think NEWID() is not doing what you think it is here, you probably just want:
INSERT INTO Suppliers (CompanyId, MaterialId)
SELECT c.Id, m.Id
FROM Companies AS c
CROSS JOIN Materials AS m
WHERE c.Name = 'Wickes'
AND m.Name = 'Bricks';
And let IDENTITY take care of the Id column in Suppliers.
As a further aside, I've also just noted that MaterialId is VARCHAR in your Suppliers table, that looks like an error if it is supposed to reference the integer Id column in Materials.
INSERT INTO Suppliers(Id,CompanyId,MaterialId) VALUES (NEWID(), (SELECT distict Id FROM Companies WHERE Name = 'Wickes'),(SELECT distict Id FROM Materials WHERE Name = 'Bricks'));
If I understand rightly Companies are the suppliers and the Suppliers table is the one that says where you can buy each material from.
Why do you have duplicates? Do you have an account for different branches of Wickes for example? If they are really duplicates and you don't care which one you use a function like MIN() will do the job of ensuring that only one value is returned. If you have duplicates it would be a good idea to find a way of disactivating all except one. This will make is simpler for you everytime you want to deal with the supplier: minimum orders, chasing overdue orders, payments etc.
Also Companies.ID and Materials.ID should be foreign keys of the Suppliers table. It is also a good idea for the ID column to be auto-incrementing, which makes it easier to add new products as you do not need to specify the ID column.
If you cannot or do not want to modify the id column to auto-incrementing IDENTITY you can continue to use NEWID().
create table Companies(
id INT PRIMARY KEY NOT NULL IDENTITY,
name VARCHAR(25));
create table Materials(
id INT PRIMARY KEY NOT NULL IDENTITY,
name VARCHAR(25));
create table Suppliers(
id INT PRIMARY KEY NOT NULL IDENTITY,
CompanyId INT FOREIGN KEY REFERENCES Companies(id),
MaterialId INT FOREIGN KEY REFERENCES Materials(id)
);
INSERT INTO Companies (name) VALUES ('Wickes');
INSERT INTO Materials (name) VALUES ('Bricks');
INSERT INTO Suppliers ( CompanyId, MaterialId)
SELECT c.Id, M.Id
FROM Companies AS c
CROSS JOIN Materials AS m
WHERE c.Name = 'Wickes'
AND m.Name = 'Bricks';
SELECT * FROM Companies;
SELECT * FROM Materials;
SELECT * FROM Suppliers;
GO
id | name
-: | :-----
1 | Wickes
id | name
-: | :-----
1 | Bricks
id | CompanyId | MaterialId
-: | --------: | ---------:
1 | 1 | 1
db<>fiddle here
INSERT INTO SUPPLIERS
(ID, COMPANYID, MATERIALID)
VALUES (NEWID(),
(SELECT DISTINCT ID FROM COMPANIES WHERE NAME = 'Wickes'), (SELECT DISTINCT ID FROM MATERIALS WHERE NAME = 'Bricks'))

would I need to use a Union here, a Join, or something else?

I cant figure out if what i'm needing to do here is a Join statement, or a Union.
Pets
Id name color
1 wiskers grey
2 midnight black
3 ralph yellow
4 Bob brown
Shots table
Id Rabbies a123
2 Yes No
4 No No
Notes tables
Id Notes
4 This pet is blind
2 This pet has no owner
The result im looking for:
Id Name Color Rabbies A123 Notes
1 Wiskers grey Null Null Null
2 midnight black Yes No This pet has no owner
......
I think you want left joins:
select p.*, s.rabies, s.a123, n.notes
from pets p left join
shots s
on s.id = p.id left join
notes n
on n.id = p.id;
Joins and Unions are both used to combine data, and both could potentially be used here. However, I would recommend using a Join, Joins combine columns from different tables, which seems to be what you want to include. You want all columns included for a single row (for the ID of the animal).
https://www.codeproject.com/Articles/1068500/What-Is-the-Difference-Between-a-Join-and-a-UNION
Try that link for more information.
If this is the case
create table Shots (id serial primary key, Rabbies varchar, a123 varchar, pet_id int);
insert into shots (pet_id, Rabbies, a123) values (2, 'Yes','No'), (4, 'No','No');
create table notes (id serial primary key, notes varchar, pet_id int);
insert into notes (pet_id, notes) values (4, 'This pet is blind'), (2, 'This pet has no owner');
select p.id, p.name, p.color, s.rabbies, s.a123, n.notes
from pets p
left join shots s on p.id = s.id
left join notes n on p.id = n.id;

How to join tables , query sql

I have the following recipe database tables and their data .
how i can find the total number of recipes , number of category for each Ingredient ? I used many joining methods but i couldn't do the query i want.
I need as out put
Ingredient id , how much recipe we can find this ingredient in, how much categories we can find this ingredients in.
This is my attempt
The problem with my attempt is if i had ingredient who is in one recipe and in two categories
It will show in results that this ingredient is in 2 recipe , 2 categories
SELECT
I.idIng,COUNT(CI.idcat)AS "CAT FOR ING" , COUNT(RI.idRecipe )AS "RECETTE
FOR
ING"
FROM
INGREDIENT I
LEFT JOIN
Ingredient_Recipe RI ON I.idIng = ri.idIng
RIGHT JOIN
Ingredient_Catigory CI ON I.idIng = CI.idIng
GROUP BY
I.idIng
ORDER BY
I.idIng;
Below is some test data:
Here i created my category its will have many to many relation with Ingredient.
-- creating table cat category
CREATE TABLE category (
idCat INT NOT NULL PRIMARY KEY,
nomCat INT NOT NULL
);
Here i created my Recipe table its will have many to many relation with Ingredient.
-- creating table cat Recipe
CREATE TABLE Recipe(
idRecipe INT NOT NULL PRIMARY KEY,
nameRecipe VARCHAR2(30) NOT NULL
);
This is my Ingredient table that will be link with both Recipe ,category .
-- creating table Ingredient
CREATE TABLE Ingredient(
idIng INT NOT NULL PRIMARY KEY ,
nameIng VARCHAR2(30) NOT NULL
);
This is the intermediate table between Ingredient ,category because the relation is many to many.
-- creating table Ingredient_category
CREATE TABLE Ingredient_category (
idIng INT NOT NULL,
idCat INT NOT NULL,
CONSTRAINT idIng_FK FOREIGN KEY (idIng) REFERENCES Ingredient(idIng),
CONSTRAINT idCat_FK FOREIGN KEY (idCat) REFERENCES category(idCat)
);
This is the intermediate table between Ingredient ,Recipe because the relation is many to many.
-- creating table Ingredient_Recipe
CREATE TABLE Ingredient_Recipe(
idIng INT NOT NULL,
idRecipe INT NOT NULL,
CONSTRAINT idIngRecipe_FK FOREIGN KEY (idIng) REFERENCES
Ingredient(idIng),
CONSTRAINT idRecipe_FK FOREIGN KEY (idRecipe) REFERENCES Recipe(idRecipe)
);
Here we insert the data for testing.
-- insert data into Recipe
INSERT INTO Recipe VALUES(1,'SOUP');
INSERT INTO Recipe VALUES(2,'FRIED');
INSERT INTO Recipe VALUES(3,'BURGER');
-- insert data into category
INSERT INTO category VALUES(1,'VEGES');
INSERT INTO category VALUES(2,'DAIRY');
INSERT INTO category VALUES(3,'MEAT');
INSERT INTO category VALUES(4,'ANIMAL PRODUCT');
-- insert data into Ingredient
INSERT INTO Ingredient VALUES (1,'Eggs');
INSERT INTO Ingredient VALUES (2,'milk');
INSERT INTO Ingredient VALUES (3,'Beef');
INSERT INTO Ingredient VALUES (4,'chess');
-- insert data into Ingredient_Catigory
INSERT INTO Ingredient_Catigory VALUES(1,4);
INSERT INTO Ingredient_Catigory VALUES(2,2);
INSERT INTO Ingredient_Catigory VALUES(2,4);
INSERT INTO Ingredient_Catigory VALUES(3,3);
INSERT INTO Ingredient_Catigory VALUES(3,4);
INSERT INTO Ingredient_Catigory VALUES(4,2);
INSERT INTO Ingredient_Catigory VALUES(4,4);
-- insert data into Ingredient_Recip
INSERT INTO Ingredient_Recipe VALUES (1,2);
INSERT INTO Ingredient_Recipe VALUES (1,3);
INSERT INTO Ingredient_Recipe VALUES (2,1);
INSERT INTO Ingredient_Recipe VALUES (3,3);
INSERT INTO Ingredient_Recipe VALUES (3,2);
I think you're looking for COUNT(DISTINCT value), you're almost there. Try something like this;
SELECT
I.idIng,
COUNT(DISTINCT CI.idCat)AS [Categories],
COUNT(DISTINCT RI.idRecipe)AS [Recipes]
FROM #INGREDIENT I
LEFT JOIN #Ingredient_Recipe RI ON I.idIng = ri.idIng
LEFT JOIN #Ingredient_Category CI ON I.idIng = CI.idIng
GROUP BY
I.idIng
ORDER BY
I.idIng
The results look like this;
idIng Categories Recipes
1 1 2
2 2 1
3 2 2
4 2 0
Please note, I think a bit of the spelling was incorrect in the sample data but I've corrected it on my test system (and I've used #TempTables). I've changed your RIGHT JOIN to a LEFT JOIN (as a note, I've never seen a need to use RIGHT JOIN in production code, try to avoid them).
Edit: I've just noticed that this is now an Oracle question, the query above has only been tested on SQL Server although a cursory glance at the documentation shows that the syntax should be the same for Oracle too.
select * from Ingredient_Recipe a join Ingredient_category b on a.idIng=b.idIng join Ingredient c on a.idIng=c.idIng join Recipe d on a.idRecipe=d.idRecipe join category e on b.idCat=e.idCat
try this out

Join on resultant table of another join without using subquery,CTE or temp tables

My question is can we join a table A to resultant table of inner join of table A and B without using subquery, CTE or temp tables ?
I am using SQL Server.
I will explain the situation with an example
The are two tables GoaLScorers and GoalScoredDetails.
GoaLScorers
gid Name
-----------
1 A
2 B
3 A
GoalScoredDetails
DetailId gid stadium goals Cards
---------------------------------------------
1 1 X 2 1
2 2 Y 5 2
3 3 Y 2 1
The result I am expecting is if I select a stadium 'X' (or 'Y')
I should get name of all who may or may not have scored there, also aggregate total number of goals,total cards.
Null value is acceptable for names if no goals or no cards.
I can get the result I am expecting with the below query
SELECT
gs.name,
SUM(goal) as TotalGoals,
SUM(cards) as TotalCards
FROM
(SELECT
gid, stadium, goal, cards
FROM
GoalScoredDetails
WHERE
stadium = 'Y') AS vtable
RIGHT OUTER JOIN
GoalScorers AS gs ON vtable.gid = gs.gid
GROUP BY
gs.name
My question is can we get the above result without using a subquery or CTE or temp table ?
Basically what we need to do is OUTER JOIN GoalScorers to resultant virtual table of INNER JOIN OF GoalScorers and GoalScoredDetails.
But I am always faced with ambiguous column name error as "gid" column is present in GoalScorers and also in resultant table. Error persists even if I try to use alias for column names.
I have created a sql fiddle for this her: http://sqlfiddle.com/#!3/40162/8
SELECT gs.name, SUM(gsd.goal) AS totalGoals, SUM(gsd.cards) AS totalCards
FROM GoalScorers gs
LEFT JOIN GoalScoredDetails gsd ON gsd.gid = gs.gid AND
gsd.Stadium = 'Y'
GROUP BY gs.name;
IOW, you could push your where criteria onto joining expression.
The error Ambiguous column name 'ColumnName' occurs when SQL Server encounters two or more columns with the same and it hasn't been told which to use. You can avoid the error by prefixing your column names with either the full table name, or an alias if provided. For the examples below use the following data:
Sample Data
DECLARE #GoalScorers TABLE
(
gid INT,
Name VARCHAR(1)
)
;
DECLARE #GoalScoredDetails TABLE
(
DetailId INT,
gid INT,
stadium VARCHAR(1),
goals INT,
Cards INT
)
;
INSERT INTO #GoalScorers
(
gid,
Name
)
VALUES
(1, 'A'),
(2, 'B'),
(3, 'A')
;
INSERT INTO #GoalScoredDetails
(
DetailId,
gid,
stadium,
goals,
Cards
)
VALUES
(1, 1, 'x', 2, 1),
(2, 2, 'y', 5, 2),
(3, 3, 'y', 2, 1)
;
In this first example we recieve the error. Why? Because there is more than one column called gid it cannot tell which to use.
Failed Example
SELECT
gid
FROM
#GoalScoredDetails AS gsd
RIGHT OUTER JOIN #GoalScorers as gs ON gs.gid = gsd.gid
;
This example works because we explicitly tell SQL which gid to return:
Working Example
SELECT
gs.gid
FROM
#GoalScoredDetails AS gsd
RIGHT OUTER JOIN #GoalScorers as gs ON gs.gid = gsd.gid
;
You can, of course, return both:
Example
SELECT
gs.gid,
gsd.gid
FROM
#GoalScoredDetails AS gsd
RIGHT OUTER JOIN #GoalScorers as gs ON gs.gid = gsd.gid
;
In multi table queries I would always recommend prefixing every column name with a table/alias name. This makes the query easier to follow, and reduces the likelihood of this sort of error.

Nested join on same table (tree structure)

My date is organized in tree structure.
The following applies (Oracle SQL syntax):
CREATE TABLE TREE
(
NAME VARCHAR2(20),
ID NUMBER(10, 0),
PARENT NUMBER(10, 0)
)
;
INSERT INTO "TREE" (NAME, ID) VALUES ('a', '1');
INSERT INTO "TREE" (NAME, ID, PARENT) VALUES ('a.1', '2', '1');
INSERT INTO "TREE" (NAME, ID, PARENT) VALUES ('a.2', '3', '1');
INSERT INTO "TREE" (NAME, ID, PARENT) VALUES ('a.2.1', '4', '3');
INSERT INTO "TREE" (NAME, ID, PARENT) VALUES ('a.2.2', '5', '3');
INSERT INTO "TREE" (NAME, ID) VALUES ('b', '6');
I would like to return full tree by id, so for query :
select name, id <<<TODO LOGIC>> where id = 1
I would get
| name | id |
| a | 1 |
| a.1 | 2 |
| a.2 | 3 |
| a.2.1 | 4 |
| a.2.2 | 5 |
for a sub tree I would get:
select name, id <<<TODO LOGIC>> where id = 3
I would get
| name | id |
| a.2 | 3 |
| a.2.1 | 4 |
| a.2.2 | 5 |
Where as, for flat entry b, it would get
select name, id <<<TODO LOGIC>> where id = 6
I would get
| name | id |
| b | 6 |
It seems that plain left out join queries fails to fulfill this purpose, or am I missing something?
The following query does return the full structure, but when starting to filter with where statements it fails.
select t1.id t1Id, t2.id t2Id, t1.name t1Name, t2.name t2Name from tree t1 left outer join tree t2 on t1.id = t2.parent
When you have a tree structure, you likely need a hierarchical query. Here it is:
select t.*
from tree t
connect by prior t.id = t.parent
start with t.id = :id
order siblings by t.id
See Hierarchical Queries for details.
You can use start with - connect by syntax on Oracle. If I'm not mistaken, it goes like this
select * from Tree t
start with t.ID = 1 connect by prior t.ID = t.Parent
But I have no Oracle to check it right away. Maybe its prior t.Parent = t.ID. Beware that it can be slow sometimes, use with caution.
Alternative is to create table to store all indirect relationship between nodes (not just a-a.1, but also a-a.2.1 and so on). You can fill it using PL/SQL recursive stored procedure. Two ways:
Simple way is to make a procedure that will do complete refill of indirect table. You can call it before running reports.
If you need instant effects, you should write refill procedure so that it will update indirect relationship just for one record in tree. Then you prohibit direct inserts and updates to Tree and force them to go via stored PL/SQL procedures (like InsertTree/UpdateTree) which in turn will call procedure to update table with indirect relationships.
You could use union for this, and you need to limit the depth of the tree to make it possible to select it in one query.
SELECT id, name
FROM TREE as node
WHERE
node.id = :id
UNION
SELECT child1.id, child1.name
FROM TREE as node
inner join TREE as child1 on node.id = child1.parent
WHERE
node.id = :id
UNION
SELECT child2.id, child2.name
FROM TREE as node
inner join TREE as child1 on node.id = child1.parent
inner join TREE as child2 on child1.id = child2.parent
WHERE
node.id = :id
The problem here is, SQL is very bad in recursion (while relational structures are actually great in this).
To make it fully dynamic, use a query for each level in the tree, or use a database engine specific SQL extension if there is anything usable.