SQL multi table query - mysql

I have three relations: Recipes(name, descr), Ingredients(recipeName, num, val) and Steps(recipeName, num, val). Each recipe will have one or more ingredients, and one or more steps. I am trying to write a query that will list the ingredients and steps for a single recipe. With this query
SELECT Recipes.name, Recipes.descr, Ingredients.num, Ingredients.val, Steps.num, Steps.val
FROM Recipes
LEFT OUTER JOIN Ingredients ON Recipes.id = Ingredients.recipeName
LEFT OUTER JOIN Steps ON Recipes.id = Steps.recipeName;
This query is like a cross product. So if my ingredients values for some recipe are 1,ing-a and 2,ing-b and steps values are 1,step-c, 2,step-d and 3,step-e, I get (order is Recipe.name, recipe.descr, Ingredients.num, Ingredients.val, Steps.num, Steps.val)
name, descr, 1, ing-a, 1, step-c
name, descr, 1, ing-a, 2, step-d
name, descr, 1, ing-a, 3, step-e
name, descr, 2, ing-b, 1, step-c
name, descr, 2, ing-b, 2, step-d
name, descr, 2, ing-b, 3, step-e
Is there a query that would return as shown below (recipe vals, then ingredients, then steps)
name
descr
1,ing-a
2,ing-b
1,step-c
2,step-d
3,step-e

Data
DROP TABLE iF EXISTS Recipes;
DROP TABLE iF EXISTS Ingredients;
DROP TABLE iF EXISTS Steps;
CREATE TABLE Recipes
(id INT,
name VARCHAR(100),
descr VARCHAR(100));
CREATE TABLE Ingredients
(recipeName INT,
num INT,
val VARCHAR(100));
CREATE TABLE Steps
(recipeName INT,
num INT,
val VARCHAR(100));
INSERT INTO Recipes VALUES (1, 'name', 'descr');
INSERT INTO Ingredients VALUES (1, 1, 'ing-a');
INSERT INTO Ingredients VALUES (1, 2, 'ing-b');
INSERT INTO Ingredients VALUES (1, 3, 'ing-c');
INSERT INTO Steps VALUES (1, 1, 'step-a');
INSERT INTO Steps VALUES (1, 2, 'step-b');
INSERT INTO Steps VALUES (1, 3, 'step-c');
Solution
SELECT v1.display
FROM ( SELECT Recipes.id AS sort_key, 1 AS seq, Recipes.name AS display
FROM Recipes
UNION ALL
SELECT Recipes.id AS sort_key, 2 AS seq, Recipes.descr AS display
FROM Recipes
UNION ALL
SELECT Ingredients.recipeName AS sort_key, 3 AS seq, CONCAT(Ingredients.num , ',' , Ingredients.val) AS display
FROM Ingredients
UNION ALL
SELECT Steps.recipeName AS sort_key, 4 AS seq, CONCAT(Steps.num , ',' , Steps.val) AS display
FROM Steps ) v1
ORDER BY v1.sort_key, v1.seq, v1.display;
Output
***display***
name
descr
1,ing-a
2,ing-b
3,ing-c
1,step-a
2,step-b
3,step-c

I just saw the question changed The following might not be valid.
You can use group concat to do this, like this:
SELECT
Recipes.name, Recipes.descr,
GROUP_CONCAT(CONCAT(Ingredients.num,' ',Ingredients.val) SEPARATOR ', ') AS Ingredients,
GROUP_CONCAT(CONCAT(Steps.num,' ',Steps.val) SEPARATOR ', ') AS Steps
FROM Recipes
LEFT OUTER JOIN Ingredients ON Recipes.id = Ingredients.recipeName
LEFT OUTER JOIN Steps ON Recipes.id = Steps.recipeName;
GROUP BY Recipes.name, Recipes.descr

Related

How to apply mysql join on a column having array field in table?

I have two tables
users, products
users table
id product_id
1 [1,2,3]
2 [5,6]
3 [4]
Products table
id product
1 Milk
2 Bread
3 Soup
4 Noodles
6 Suger
7 Biscuits
8 Raw Eggs
How to apply joins on these tables.
Here is what i am trying to do.
select * from products join users ON find_in_set(products.id, users.product_id)
But, The output is incorrect.
Please guide me how to implement this.
Here's how to fix it:
CREATE TABLE user_products (
user_id INT,
product_id INT,
PRIMARY KEY (user_id, product_id)
);
Fill one user and one product id into each row.
INSERT INTO user_products (user_id, product_id)
VALUES (1,1), (1,2), (1,3), (2,5), (2,6), (3,4);
Now you can do the join this way:
SELECT * FROM users AS u
JOIN user_products AS up ON u.id = up.user_id
JOIN products AS p ON up.product_id = p.id;
Don't use JSON arrays for joins.
This is working for me for few data from your sample instead of find_in_set i would recommend you use REGEXP_LIKE
WITH USERS AS (
SELECT 1 AS ID , '[1,2,3]' AS product_id UNION ALL
SELECT 2, '[5,6]' UNION ALL
SELECT 3, '[4]'
),
PRODUCTS AS (
SELECT 1 AS ID , 'Milk' AS PRODUCT UNION ALL
SELECT 2, 'Bread' UNION ALL
SELECT 3, 'Soup' UNION ALL
SELECT 4, 'Noodles'
)
select * from PRODUCTS join USERS ON REGEXP_LIKE(USERS.PRODUCT_id, PRODUCTS.id);
this is the query :
select * from PRODUCTS join USERS ON REGEXP_LIKE(USERS.PRODUCT_id, PRODUCTS.id)
output im getting is
# ID, PRODUCT, ID, product_id
1, Milk, 1, [1,2,3]
2, Bread, 1, [1,2,3]
3, Soup, 1, [1,2,3]
4, Noodles, 3, [4]

MYSQL - Updating count in one tabled based on two other tables joined

I have 3 related tables. Adults, Children and AC. Adults contains an INT column to count high school seniors. Children contains a column with year of highs school graduation. AC links the adult.id to the children.id.
CREATE TABLE adults (
id INT,
name VARCHAR(10),
seniors INT DEFAULT 0
) ;
INSERT INTO adults (id, name) VALUES
(1, 'adam'),
(2, 'bob');
CREATE TABLE children (
id INT,
name VARCHAR(10),
grad VARCHAR(4)
) ;
INSERT INTO children (id, name, grad) VALUES
(1, 'sally', '2016'),
(2, 'johnny', '2017'),
(3, 'eric', '2016'),
(4, 'billy', '2016'),
(5, 'rachel', '2016');
CREATE TABLE pc (
id INT,
a_id INT,
c_id INT
) ;
INSERT INTO pc (id, a_id, c_id) VALUES
(1, 1, 1),
(2, 1, 2),
(3, 1, 3),
(4, 2, 3),
(5, 2, 2);
SQLFiddle: http://sqlfiddle.com/#!2/89281e
So I want to update adults.seniors to the count of '2016' children they're linked to. So adult #1 would be "2" (sally and eric), and adult #2 "1" (eric).
The real data will be run across 25,000+ children being matched up to 40,000+ parents with a row count on the "pc" table above 3,000,000 rows - so looking for efficiency. I started working down this path but a) it's not working for obvious reasons and b) I doubt it would be efficient...
UPDATE adults a SET
seniors = (
SELECT p.a_id, count(*)
FROM pc p
INNER JOIN children c ON c.id = p.c_id
WHERE c.grad = '2016'
GROUP BY p.c_id)
WHERE p.a_id = a.id;
I'm thinking there has to be a better way of doing this with joins but can't seem to wrap my head around it.
You should be looking for this update statement:
UPDATE adults a
JOIN
(SELECT
p.a_id, COUNT(*) childrencount
FROM
pc p
INNER JOIN children c ON c.id = p.c_id
WHERE
c.grad = '2016'
GROUP BY p.a_id) c ON (a.id = c.a_id)
SET
seniors = c.childrencount;

How to vertically join the same table

So I have a table like this:
DECLARE #Nodes TABLE (ID INT, Name VARCHAR(100), ParentID INT)
INSERT INTO #Nodes VALUES
(1, 'Test', NULL),
(2, 'Test', 1),
(3, 'Test', 2),
(5, 'Test', 3),
(6, 'Test', 1)
Now I would like to query that table, retrieving node with ID 3 and all parent nodes. ParentID would be a foreign key to the same table's ID column. The number of possible parent nodes is infinite. Result would be this:
ID Name ParentID
1 Test NULL
2 Test 1
3 Test 2
What is the best way to do this?
You can use a recursive common table expression:
WITH Nodes
AS(
SELECT 1 AS relationLevel, child.*
FROM #Nodes child
WHERE child.ID = 3
UNION ALL
SELECT relationLevel+1, parent.*
FROM Nodes nextOne
INNER JOIN #Nodes parent ON parent.ID = nextOne.ParentID
)
SELECT * FROM Nodes order by ID
DEMO
DECLARE #ID INT=3
;with cte as(
select ID,Name,ParentID FROM #Nodes WHERE ID=#ID
UNION ALL
SELECT n.ID,n.Name,n.ParentID FROM cte inner join #Nodes n on cte.ParentID= n.ID
)
SELECT ID,Name,ParentID FROM cte
ORDER BY ParentID

Equations in SQL (MySQL)

I have a table with a cost_maintence column that has cost for the entire year(52) weeks. I also have a table of renters, and a table of renter_units where there is a week_owned column that has the week number the renter rented. I am trying to figure out how I could calculate the cost for each renter. The equation I came up with is:
what each person owes = (cost_maintence/52) * #weeks each renter
rented
Is there any way I could get the value from a query?
create table renters(
id,
lname,
fname,
phone_num);
create table unit(
id,
unit_name,
unit_num,
cost_maintence);
create table renters_unit(
renters_id,
unit_id,
week_owned);
This is the query I came up with but I have no way of testing it out
select r.lname, r.fname, count(ru.week_owned),
sum(u.cost_maintence/52*count(ru.week_owned))
from renters r, renters_units ru, units u
where r.id = ru.renter_id
and ru.unit_id = u.id
and u.unit_name =unitname
and u.unit_num = unitnum
group by lname
order by lname,fname asc;
Here's an example. The inner query will get you amount owed per item, and the outer query sums that to find the total owed per person.
SELECT fname, SUM(owes) AS total_due
FROM (
SELECT r.fname,
r.id,
u.unit_name,
u.cost_maintence/52*COUNT(ru.week_owned) AS owes
FROM renters AS r
INNER JOIN renters_unit AS ru ON r.id = ru.renters_id
INNER JOIN unit AS u ON u.id = ru.unit_id
GROUP BY r.id, u.id
) AS t
GROUP BY id
Try it out with a SQLFiddle demo
Example Schema:
create table renters(
id int,
lname varchar(20),
fname varchar(20),
phone_num varchar(20));
create table unit(
id int,
unit_name varchar(30),
unit_num int,
cost_maintence int);
create table renters_unit(
renters_id int,
unit_id int,
week_owned int);
INSERT INTO renters VALUES (1, 'Peterson', 'Chaz', '8675309');
INSERT INTO unit VALUES (1, 'Skateboard', 1337, 52);
INSERT INTO unit VALUES (2, 'Flamethrower', 5432, 104);
INSERT INTO renters_unit VALUES (1, 1, 1);
INSERT INTO renters_unit VALUES (1, 1, 2);
INSERT INTO renters_unit VALUES (1, 1, 4);
INSERT INTO renters_unit VALUES (1, 2, 4);
INSERT INTO renters_unit VALUES (1, 2, 5);
By this, we can see that Chaz should owe $7 for the year (had a skateboard for 3 weeks at $1 per week, and a flamethrower for 2 weeks at $2 per week).
The inner query gives the following:
FNAME UNIT_NAME OWES
Chaz Skateboard 3
Chaz Flamethrower 4
And the outer:
FNAME TOTAL_DUE
Chaz 7
SELECT t.renters_id, SUM(u.cost_maintence)/52
FROM unit u JOIN renters_unit t ON t.unit_id = u.id
GROUP BY t.renters_id

Select primary photo if exist else pick first uploaded photo

I am trying to retrieve a single picture set by the user as the primary picture from table as below:
SELECT p.*, ph.* FROM place AS p
INNER JOIN photo as ph
ON p.place_id = ph.place_id
WHERE ph.primary_pic = 'X';
But not all user has set their primary picture, resulting in the query does not return anything.
IF(query is empty)
//perform SQL again with primary_pic = ''
Is there any ways or syntax that could be use to query this with one single SQL statement?
I was working in MS SQL but I don't see anything MS-specific except for the table variables. So if you change that to existing tables, it should run on MySQL too. (I am not sure but I guess MySQL should have EXCEPT set operation.)
-- sample data start
declare #place as table (plid int, plname nvarchar(100))
declare #photo as table (phid int, phname nvarchar(100), plid int, primary_pic nvarchar(1))
insert into #place values (1, 'aaa')
insert into #place values (2, 'bbb')
insert into #photo values (1, 'aaa_1.jpg', 1, '')
insert into #photo values (2, 'aaa_2.jpg', 1, 'X')
insert into #photo values (3, 'aaa_3.jpg', 1, '')
insert into #photo values (4, 'aaa_4.jpg', 1, '')
insert into #photo values (5, 'bbb_1.jpg', 2, '')
insert into #photo values (6, 'bbb_2.jpg', 2, '')
insert into #photo values (7, 'bbb_3.jpg', 2, '')
insert into #photo values (8, 'bbb_4.jpg', 2, '')
-- sample data end
-- note: #place and #photo are table variables in MS SQL
select p.*, ph2.*
from #place p inner join #photo ph2 on p.plid = ph2.plid
inner join (
select ph.plid, ph.primary_pic, min(ph.phid) phid
from #photo ph inner join
(select distinct plid from #photo where primary_pic <> 'X'
except
select distinct plid from #photo where primary_pic = 'X') hasnoprimary
on hasnoprimary.plid = ph.plid
group by ph.plid, ph.primary_pic
union
select ph.plid, ph.primary_pic, min(ph.phid) phid
from #photo ph inner join
(select distinct plid from #photo where primary_pic = 'X') hasprimary
on hasprimary.plid = ph.plid
where primary_pic = 'X'
group by ph.plid, ph.primary_pic
) trickypart on trickypart.phid = ph2.phid
Method: (1) get two lists of place IDs. One for which there is a primary photo and another for which there isn't (this is where I used EXCEPT). (2) join the photos table to both of them separately to get the photo IDs. For the first list, it is what was marked with X, for the second it is the minimum of all photo IDs. (3) make a union of the two. (4) join it back to places and photos.
In MS SQL it works ang gives the following for the sample data above:
plid plname phid phname plid primary_pic
----------- -------------- ----------- -------------- ----------- -----------
1 aaa 2 aaa_2.jpg 1 X
2 bbb 5 bbb_1.jpg 2