Join operations in SQL - 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

Related

SQL inner join condition by actual date

I need to join actual document to people. Documents has date issued (passport, for example).
SQL Fiddle: http://sqlfiddle.com/#!9/3a8118/2/0
Table structure:
CREATE TABLE people
(
p_id INT NOT NULL AUTO_INCREMENT,
p_name VARCHAR(50) NOT NULL,
PRIMARY KEY(p_id)
);
INSERT INTO people (p_id, p_name)
VALUES (1, 'Name_1'),
(2, 'Name_2');
CREATE TABLE documents
(
d_id INT NOT NULL AUTO_INCREMENT,
d_people INT(10) NOT NULL,
d_date VARCHAR(10) NOT NULL,
PRIMARY KEY(d_id)
);
INSERT INTO documents (d_id, d_people, d_date)
VALUES (1, 1, '01.01.2022'),
(2, 2, '01.12.2021'),
(3, 1, '05.02.2022'),
(4, 1, '10.02.2022'),
(5, 2, '04.01.2022'),
(6, 1, '20.01.2022');
Query: condition is select actual document when date is 21.01.2022, it must return d_id = 6:
SELECT *
FROM people
INNER JOIN documents ON d_people = p_id
WHERE p_id = 1 AND ??? d_date 21.01.2022 ???
;
I need to do an inner join to return only this row:
use this query:
Fiddle
SELECT * FROM people
INNER JOIN documents ON d_people = p_id
WHERE p_id = 1 and d_date='20.01.2022';

SQL combine foreign key values of all parents into child

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

How to update a column based on matching ID's

I create two tables housing_listing and buyer. How to update the value of sold in housing_listing table to TRUE if the id (transaction_id) of housing_listing matches the id (transaction_id) of buyer?
Creating tables:
CREATE TABLE housing_listing (
transaction_id INT PRIMARY KEY,
number_of_bedrooms INT,
number_of_bathrooms INT,
listing_price INT,
listing_agent TEXT,
agent_email TEXT,
listing_office TEXT,
date_of_listing DATETIME,
zip_code INT,
sold BOOL,
Commission INT
);
CREATE TABLE buyer (
transaction_id INT PRIMARY KEY,
buyer_name TEXT,
buyer_id INT,
sale_price INT,
date_of_sale DATETIME,
selling_agent TEXT
)
Inserting data to buyer table:
INSERT INTO buyer VALUES (1, "Ania Kraszka", 1, 2000000,'2020/02/27','FADU');
INSERT INTO buyer VALUES (2, "Ania Kraszka", 2, 2000000,'2011/02/27','FADU');
Inserting data to housing_listing table:
INSERT INTO housing_listing VALUES (1, 3, 2, 2000000, 'Liza','liza#uba.ar', 'UBA','2018/02/27',45049, 'FALSE',0);
INSERT INTO housing_listing VALUES (2, 2, 1, 3000, 'Tom','tom#utn.ar', 'UTN','2011/02/27',45049,'FALSE',0);
INSERT INTO housing_listing VALUES (9, 1, 1, 40000, 'Tom','tom#fadu.ar', 'FADU','2011/02/27',45049, 'FALSE',0);
You can use a correlated subquery:
update housing_listing
set sold = true
where exists (select 1
from buyer b
where b.transaction_id = housing_listing.transaction_id
);
I assume you mean that the transaction ids match.
You can do something like this-
UPDATE housing_listing
SET sold = 'TRUE'
WHERE transaction_id IN (SELECT transaction_id FROM buyer);
SELECT * FROM housing_listing;
1|3|2|2000000|Liza|liza#uba.ar|UBA|2018/02/27|45049|TRUE|0
2|2|1|3000|Tom|tom#utn.ar|UTN|2011/02/27|45049|TRUE|0
9|1|1|40000|Tom|tom#fadu.ar|FADU|2011/02/27|45049|FALSE|0

Relational database design issue - category scoped tags

I'm designing a database where I have a number of products, each of which belongs to one and only one category.
Products are meant to be tagged, but only with tags allowed for the category they belong to.
This is what I've got so far:
Let's say I have these 2 categories:
Smartphones
Laptops
Tags for the "Smartphones" category:
Dual SIM
GPS
Tags for the "Laptop" category:
Backlit keyboard
HDMI
The problem with this design is that the database doesn't prevent a product from being tagged with a tag from another category: a bug in my app's code could easily cause a laptop to be tagged with the "Dual SIM" tag, which obviously is not what I want.
I would like to prevent this situation at database level with foreign keys and without using triggers. Is this possible at all?
I was able to do the following in Oracle. Note how the last insert fails, I believe this is what you are after.
CREATE TABLE product (
id INTEGER NOT NULL,
category_id INTEGER NOT NULL,
PRIMARY KEY ( id ),
CONSTRAINT uq_prod_cat UNIQUE ( id,category_id )
);
INSERT INTO product (
id,
category_id
) VALUES (
1,
1
);
CREATE TABLE tags (
id INTEGER NOT NULL,
category_id INTEGER NOT NULL,
PRIMARY KEY ( id ),
CONSTRAINT uq_tag_cat UNIQUE ( id,category_id )
);
INSERT INTO tags (
id,
category_id
) VALUES (
1,
1
);
INSERT INTO tags (
id,
category_id
) VALUES (
2,
1
);
INSERT INTO tags (
id,
category_id
) VALUES (
3,
2
);
CREATE TABLE product_tags (
id INTEGER NOT NULL,
product_id INTEGER NOT NULL,
category_id INTEGER NOT NULL,
tag_id INTEGER NOT NULL,
PRIMARY KEY ( id ),
FOREIGN KEY ( product_id,category_id )
REFERENCES product ( id,category_id ),
FOREIGN KEY ( tag_id,category_id )
REFERENCES tags ( id,category_id )
);
INSERT INTO product_tags (
id,
product_id,
category_id,
tag_id
) VALUES (
1,
1,
1,
1
);
1 row inserted.
INSERT INTO product_tags (
id,
product_id,
category_id,
tag_id
) VALUES (
2,
1,
1,
2
);
1 row inserted.
INSERT INTO product_tags (
id,
product_id,
category_id,
tag_id
) VALUES (
3,
1,
1,
3
);
Error starting at line : 35 in command -
INSERT INTO product_tags (id, product_id, category_id, tag_id) VALUES (3, 1, 1, 3)
Error report -
ORA-02291: integrity constraint (SYS_C008023) violated - parent key not found
My answer is pretty similar to that of Ashuntosh A, except that I would create a separate table for associating tags with categories, so that the schema allows for a tag to apply to more one category (e.g. both a tablet and phone could have a DualSim):
--TSQL
create table ProductCategory
(
id int primary key identity,
name varchar(50) not null
)
create table ProductTag
(
id int primary key identity,
name varchar(50) not null
)
create table TagCategory
(
tag_id int foreign key references ProductTag,
category int foreign key references ProductCategory,
primary key (tag_id, category)
)
create table Product
(
id int primary key identity,
type int foreign key references ProductCategory,
unique (id, type)
)
create table TaggedProduct
(
product int,
tag int,
type int,
primary key (product, tag),
foreign key (product, type) references Product (id, type),
foreign key (tag, type) references TagCategory (tag_id, category)
)
insert ProductCategory
select 'Laptop' union
select 'Phone'
insert ProductTag
select 'HDMI' union
select 'Backlit Keyboard' union
select 'Dual Sim' union
select 'GPS'
insert TagCategory
select 1, 1 union -- HDMI/LAPTOP
select 2, 1 union -- Backlit/LAPTOP
select 3, 2 union -- DualSim/PHONE
select 4, 2 -- GPS/PHONE
insert Product
select 1 --a laptop
insert TaggedProduct
select 1, 1, 1 --laptop has hdmi
union select 1, 2, 1 --laptop has backlit keyboard
insert TaggedProduct select 1, 3, 1
--fails because 'DualSim/Laptop' is not a valid category

SQL Count returns wrong value in my INNER JOIN Statement

I am quite new to SQL and I am having some difficulty with my COUNT() feature.
It keeps returning the wrong COUNT() value as I am trying to calculate the TOTAL number of specific cars sold by users. My tables and results are here:
http://sqlfiddle.com/#!9/d2ef0/5
My Schema:
CREATE TABLE IF NOT EXISTS Users(
userID INT NOT NULL AUTO_INCREMENT,
username VARCHAR(50) NOT NULL,
forename VARCHAR(50) NOT NULL,
surname VARCHAR(50) NOT NULL,
PRIMARY KEY (userID)
);
CREATE TABLE IF NOT EXISTS CarType(
carTypeID INT NOT NULL AUTO_INCREMENT,
description VARCHAR(80),
PRIMARY KEY (carTypeID)
);
CREATE TABLE IF NOT EXISTS Country(
countryID INT NOT NULL AUTO_INCREMENT,
name VARCHAR(100),
PRIMARY KEY (countryID)
);
CREATE TABLE IF NOT EXISTS Cars(
carID INT NOT NULL AUTO_INCREMENT,
carTypeID INT NOT NULL,
countryID INT NOT NULL,
description VARCHAR(100) NOT NULL,
make VARCHAR(100) NOT NULL,
model VARCHAR(100),
FOREIGN KEY (carTypeID) REFERENCES CarType(carTypeID),
FOREIGN KEY (countryID) REFERENCES Country(countryID),
PRIMARY KEY (carID)
);
CREATE TABLE IF NOT EXISTS Likes(
userID INT NOT NULL,
carID INT NOT NULL,
likes DOUBLE NOT NULL,
FOREIGN KEY (userID) REFERENCES Users(userID),
FOREIGN KEY (carID) REFERENCES Cars(carID)
);
CREATE TABLE IF NOT EXISTS Sold(
userID INT NOT NULL,
carID INT NOT NULL,
FOREIGN KEY (userID) REFERENCES Users(userID),
FOREIGN KEY (carID) REFERENCES Cars(carID)
);
INSERT INTO Users VALUES
(NULL, "micheal", "Micheal", "Sco"),
(NULL, "bensco", "Ben", "Sco"),
(NULL, "shanemill", "Shane", "Miller");
INSERT INTO CarType VALUES
(NULL, "Saloon"),
(NULL, "HatchBack"),
(NULL, "Low Rider");
INSERT INTO Country VALUES
(NULL, "UK"),
(NULL, "USA"),
(NULL, "JAPAN"),
(NULL, "GERMANY");
INSERT INTO Cars VALUES
(NULL, 1, 2, "Ford Mustang lovers", "Mustang", "Ford"),
(NULL, 2, 3, "Drift Kings", "Skyline", "Nissan"),
(NULL, 3, 1, "British classic", "Cooper", "Mini");
INSERT INTO Likes VALUES
(1, 1, 3),
(1, 2, 2),
(2, 3, 5),
(2, 3, 7),
(2, 3, 1),
(2, 3, 2);
INSERT INTO Sold VALUES
(1, 2),
(1, 3),
(1, 1),
(2, 2),
(2, 3),
(3, 1),
(3, 3);
This is the Sold table:
userID carID
1 2
1 3
1 1
2 2
2 3
3 1
3 3
This is my complex query:
SELECT DISTINCT Cars.carID, Cars.description, Cars.model, Country.name,
CarType.description, ROUND(AVG(Likes.likes)), COUNT(*)
FROM Cars
INNER JOIN Sold ON
Cars.carID = Sold.carID
INNER JOIN Country ON
Cars.countryID = Country.countryID
INNER JOIN CarType ON
Cars.carTypeID = CarType.carTypeID
INNER JOIN Likes ON
Cars.carID = Likes.carID
GROUP BY Cars.carID
The actual result from this complex SQL Query:
carID description model name description ROUND(AVG(Likes.likes)) COUNT(*)
1 Ford Mustang lovers Ford USA Saloon 3 2
2 Drift Kings Nissan JAPAN HatchBack 2 2
3 British classic Mini UK Low Rider 4 12
For example, the result for the last one is incorrect - it should not be 12
Would be nice if someone could tell me where I went wrong
Thanks
You are attempting to aggregate across two different dimensions -- Sold and Likes. The result is a Cartesian product of rows for each car, and this throws off the aggregations.
The solution is the pre-aggregate the results along each dimension:
SELECT c.carID, c.description, c.model, cy.name, ct.description,
l.avgLikes, s.NumSold
FROM Cars c INNER JOIN
(SELECT s.CarId, COUNT(*) as NumSold
FROM Sold s
GROUP BY s.CarId
) s
ON c.carID = s.carID INNER JOIN
Country cy
ON c.countryID = cy.countryID INNER JOIN
CarType ct
ON c.carTypeID = ct.carTypeID LEFT JOIN
(SELECT l.carId, AVG(Likes) as avgLikes
FROM Likes l
GROUP BY CarId
) l
ON c.carID = l.carID;
Here is the SQL Fiddle.
If all you want is the total number of specific cars sold by user then all your info is in the sold table. This query will give you what you want by carID. You can use that as a subquery if you want to join on other tables to get more info.
SELECT userID, carID, count(*) as totalSold FROM Sold GROUP BY userID, carID;