How to join queries with a subquery? - sql

So I'm a total newbie trying to solve this exercise where I have to find all the dishes that are marked as Vegetarian but contain Turkey meat in their ingredients.
This is what I've tried (this is where I inner join 3 tables to find the ingredients):
SELECT Name
FROM Dishes
INNER JOIN DishesIngredients ON DishesIngredients.DishId = s.Id
INNER JOIN Ingredients ON DishesIngredients.IngredientID = Ingredients.ID
this is where I can't seem to be able to join the subquery to identify the Vegetarian tag:
WHERE Ingredients.Name = 'Turkey meat' =
(SELECT Name
FROM Tags
INNER JOIN DishesTags ON DishesTags.TagID = Tags.ID
INNER JOIN Dishes ON DishesTags.DishID = Dishes.ID)
The diagram of the database is here for reference:

Let first find out how many dishes have Turkey meat as ingredient.
You have:
SELECT D.ID
FROM
Dishes D
JOIN DishIngredients DI ON D.ID = DI.DishID
JOIN Ingredients I ON DI.IngredientID = I.ID
WHERE I.Name LIKE 'Turkey meat'
Then get all dishes with tag 'Vegetarian'.
SELECT D.ID
FROM
Dishes D
JOIN DishIngredients DI ON D.ID = DI.DishID
JOIN Ingredients I ON DI.IngredientID = I.ID
JOIN DishesTags DT on D.ID = DT.DishID
JOIN Tags T ON DT.TagID = T.ID
WHERE I.Name LIKE 'Turkey meat'
AND T.Name = 'Vegetarian'

You could use exists and subqueries:
select d.*
from dishes d
where
exists (
select 1
from dishestags dt
innerjoin tags t on t.id = dt.tagid
where dt.dishid = d.id and t.name = 'Vegetarian'
)
and exists (
select 1
from dishesingredients di
inner join ingredients i on i.id = di.ingredientid
where di.dishid = d.id and i.name = 'Turkey'
)

Related

Find all recipes with just vegan products

I have been trying to find all recipes with just vegan products but so far I just have this:
SELECT DISTINCT r.*
FROM recipes r INNER JOIN recipe_product rp ON r.id = rp.recipe_id
INNER JOIN products p ON rp.product_id = p.id
INNER JOIN product_specificdiet ps ON p.id = ps.product_id
INNER JOIN specificdiets sd ON ps.specific_diet_id = sd.id
WHERE sd.type = 'VEGAN';
But that query is giving me all the products that are vegan for all the recipes.
No idea how could I resolve it.
Thanks!
Two rules:
A product is vegan, if one of its specific diets is 'VEGAN'.
A recipe is vegan, if it only consists of vegan products.
In other words: A vegan recipe is a recipe for which NOT EXISTS any product that is NOT IN the set of vegan products.
select *
from recipes r
where not exists
(
select null -- non-vegan ingredient
from recipe_product rp
where rp.recipe_id = r.id
and rp.product_id not in
(
select ps.product_id -- vegan product
from product_specificdiet ps
join specificdiets sd ON ps.specific_diet_id = sd.id
where sd.type = 'VEGAN'
)
);
Use aggregation and a HAVING clause:
SELECT r.*
FROM recipes r JOIN
recipe_product rp
ON r.id = rp.recipe_id JOIN
products p
ON rp.product_id = p.id JOIN
product_specificdiet ps
ON p.id = ps.product_id JOIN
specificdiets sd
ON ps.specific_diet_id = sd.id
GROUP BY r.id
HAVING COUNT(*) FILTER (sd.type = 'VEGAN') = COUNT(*);
An alternative method would filter out ingredients that are not vegan and check for zero of them:
HAVING COUNT(*) FILTER (sd.type <> 'VEGAN') = 0;
Postgres is one of the databases that allows you to specify SELECT r.* even though you are only aggregating by r.id -- because that is allowed when the aggregation key is a primary key. In other databases, you may need to list the columns both in the SELECT and the GROUP BY if you want additional columns.
SELECT
re.*
FROM
recipes re
WHERE
re.id NOT IN (
SELECT
r.id
FROM
recipes r
LEFT JOIN
recipe_product rp ON r.id = rp.recipe_id
LEFT JOIN
products p ON rp.product_id = p.id
LEFT JOIN
product_specificdiet ps ON p.id = ps.product_id
LEFT JOIN
specificdiets sd ON ps.specific_diet_id = sd.id
WHERE
ISNULL(sd.type, '') != 'VEGAN';
)
Maybe by joining to a sub-query.
One that uses a filters the specific diets that only uses vegan types.
Untested notepad scribble :
SELECT r.*
FROM recipes r
JOIN
(
SELECT rp.recipe_id
FROM recipe_product rp
INNER JOIN products p
ON p.id = rp.product_id
INNER JOIN product_specificdiet ps
ON ps.product_id = p.id
LEFT JOIN specificdiets sd
ON sd.id = ps.specific_diet_id
GROUP BY rp.recipe_id
HAVING COUNT(CASE WHEN sd.type = 'VEGAN' THEN sd.id END) = COUNT(*)
) q
ON q.recipe_id = r.id;

Oracle SQL How to Count Column Value Occurences and Group BY during joins

I'm working on another SQL query, trying to group a collection of records while doing a count and joining tables. See below for goal, current query, and attached scripts for building and populating tables.
Show all customers who have checked more books than DVDs. Display
customer name, total book checkouts and total DVD checkouts. Sort
results by customer first name and last name.
SELECT C.CUSTOMER_FIRSTNAME, C.CUSTOMER_LASTNAME, COUNT(T.TRANSACTION_ID)
FROM customer C
INNER JOIN library_card LC ON C.CUSTOMER_ID = LC.CUSTOMER_ID
INNER JOIN transaction T ON LC.LIBRARY_CARD_ID = T.LIBRARY_CARD_ID
INNER JOIN physical_item P ON T.PHYSICAL_ITEM_ID = P.PHYSICAL_ITEM_ID
INNER JOIN catalog_item CT ON P.CATALOG_ITEM_ID = CT.CATALOG_ITEM_ID
GROUP BY C.CUSTOMER_FIRSTNAME, C.CUSTOMER_LASTNAME
ORDER BY C.CUSTOMER_FIRSTNAME, C.CUSTOMER_LASTNAME;
Run first: https://drive.google.com/open?id=1PYAZV4KIfZtxP4eQn35zsczySsxDM7ls
Run second: https://drive.google.com/open?id=1pAzWmJqvD3o3n6YJqVUM6TtxDafKGd3f
EDIT
With some help from Mr. Barbaros I've come up with the below query, which is closer. However, this query isn't returning any results for DVDs, which leads me to believe it's a join issue.
SELECT C.CUSTOMER_FIRSTNAME, C.CUSTOMER_LASTNAME, COUNT(CT1.TYPE) AS BOOK_COUNT, COUNT(CT2.TYPE) AS DVD_COUNT
FROM customer C
INNER JOIN library_card LC ON C.CUSTOMER_ID = LC.CUSTOMER_ID
INNER JOIN transaction T ON LC.LIBRARY_CARD_ID = T.LIBRARY_CARD_ID
INNER JOIN physical_item P ON T.PHYSICAL_ITEM_ID = P.PHYSICAL_ITEM_ID
INNER JOIN catalog_item CT1 ON P.CATALOG_ITEM_ID = CT1.CATALOG_ITEM_ID AND CT1.TYPE = 'BOOK'
LEFT OUTER JOIN catalog_item CT2 ON P.CATALOG_ITEM_ID = CT2.CATALOG_ITEM_ID AND CT2.TYPE = 'DVD'
GROUP BY C.CUSTOMER_FIRSTNAME, C.CUSTOMER_LASTNAME, CT1.TYPE, CT2.TYPE
ORDER BY C.CUSTOMER_FIRSTNAME, C.CUSTOMER_LASTNAME;
Use "conditional aggregates" (use a case expression inside the aggregate function)
SELECT
C.CUSTOMER_FIRSTNAME
, C.CUSTOMER_LASTNAME
, COUNT( CASE WHEN CT.TYPE = 'BOOK' THEN T.TRANSACTION_ID END ) books
, COUNT( CASE WHEN CT.TYPE = 'DVD' THEN T.TRANSACTION_ID END ) dvds
FROM customer C
INNER JOIN library_card LC ON C.CUSTOMER_ID = LC.CUSTOMER_ID
INNER JOIN transaction T ON LC.LIBRARY_CARD_ID = T.LIBRARY_CARD_ID
INNER JOIN physical_item P ON T.PHYSICAL_ITEM_ID = P.PHYSICAL_ITEM_ID
INNER JOIN catalog_item CT ON P.CATALOG_ITEM_ID = CT.CATALOG_ITEM_ID
GROUP BY
C.CUSTOMER_FIRSTNAME
, C.CUSTOMER_LASTNAME
HAVING
COUNT( CASE WHEN CT.TYPE = 'BOOK' THEN T.TRANSACTION_ID END )
> COUNT( CASE WHEN CT.TYPE = 'DVD' THEN T.TRANSACTION_ID END )
ORDER BY
C.CUSTOMER_FIRSTNAME
, C.CUSTOMER_LASTNAME
;
You can use catalog_item table twice( think of as seperate tables for books and dvds ), and compare by HAVING clause as :
SELECT C.CUSTOMER_FIRSTNAME, C.CUSTOMER_LASTNAME,
COUNT(CT1.CATALOG_ITEM_ID) as "Book Checkout",
COUNT(CT2.CATALOG_ITEM_ID) as "DVD Checkout"
FROM customer C
INNER JOIN library_card LC ON C.CUSTOMER_ID = LC.CUSTOMER_ID
INNER JOIN transaction T ON LC.LIBRARY_CARD_ID = T.LIBRARY_CARD_ID
INNER JOIN physical_item P ON T.PHYSICAL_ITEM_ID = P.PHYSICAL_ITEM_ID
LEFT JOIN catalog_item CT1 ON P.CATALOG_ITEM_ID = CT1.CATALOG_ITEM_ID AND CT1.TYPE = 'BOOK'
LEFT JOIN catalog_item CT2 ON P.CATALOG_ITEM_ID = CT2.CATALOG_ITEM_ID AND CT1.TYPE = 'DVD'
GROUP BY C.CUSTOMER_FIRSTNAME, C.CUSTOMER_LASTNAME
HAVING COUNT(CT1.CATALOG_ITEM_ID) > COUNT(CT2.CATALOG_ITEM_ID)
ORDER BY C.CUSTOMER_FIRSTNAME, C.CUSTOMER_LASTNAME;
CUSTOMER_FIRSTNAME CUSTOMER_LASTNAME Book Checkout DVD Checkout
------------------ ----------------- ------------- -------------
Deena Pilgrim 3 1
Emile Cross 5 2
Please try to remove ,CT1.TYPE, CT2.TYPE on your group by clause.

Postgres: what's wrong with the syntax of this query?

I'm trying to write a fairly straightforward PSQL query to retrieve some data (I realise it's not the most efficient query right now):
SELECT c.name AS article, c.id AS article_id, t.name AS template, t.id AS template_id, brand_names, COUNT(p.component_id)
FROM publications p
INNER JOIN components c
(SELECT string_agg(b.name, ', ') AS brand_names
FROM brands b
INNER JOIN brands_components
ON b.id = brands_components.brand_id
WHERE brands_components.component_id = c.id
) brand_query
ON c.id = p.component_id
INNER JOIN brands_components bc
ON c.id = bc.component_id
AND bc.brand_id IN (16, 23, 24, 35, 37)
INNER JOIN components_templates ct
ON c.id = ct.component_id
INNER JOIN templates t
ON t.id = ct.template_id
This gives me a syntax error though on line 4. What's missing? If I run the subquery alone it works fine:
syntax error at or near "SELECT" LINE 4: (SELECT string_agg(b.name, ', ') AS brand_names ^ : SELECT c.name AS article, c.id AS article_id, t.name AS template, t.id AS template_id, brand_nam
The subquery is designed to retrieve all the brand names per component and display them in a single row instead of many. Their join table is brands_components.
A fiddle that is available here, the desired result should be something like:
article article_id template template_id count brands
--------------------------------------------------------------------------------------------------------------
component one | 1 | template one | 1 | 4 | brand one, brand two, brand three, brand four
Your immediate problem could be solved with a a lateral join:
SELECT c.name AS article, c.id AS article_id, t.name AS template, t.id AS template_id, brand_names, COUNT(p.component_id)
FROM publications p
JOIN components c ON c.id = p.component_id
JOIN brands_components bc ON c.id = bc.component_id AND bc.brand_id IN (1, 2, 3, 4)
JOIN LATERAL (
SELECT b.id, string_agg(b.name, ', ') AS brand_names
FROM brands b
JOIN brands_components ON b.id = brands_components.brand_id
WHERE brands_components.component_id = c.id
GROUP BY b.id
) brand_query ON brand_query.id = bc.brand_id
JOIN components_templates ct ON c.id = ct.component_id
JOIN templates t ON t.id = ct.template_id
GROUP BY 1,2,3,4
The above would still not run because the group by doesn't include the brand_names column. Postgres doesn't know that brand_names is already aggregates.
However, the derived table is not really needed if you move the aggregation to the outer query:
SELECT c.name AS article,
c.id AS article_id,
t.name AS template,
t.id AS template_id,
string_agg(b.name, ',') as brand_names,
COUNT(p.component_id)
FROM publications p
JOIN components c ON c.id = p.component_id
JOIN brands_components bc ON c.id = bc.component_id AND bc.brand_id IN (1, 2, 3, 4)
JOIN brands b on b.id = bc.brand_id
JOIN components_templates ct ON c.id = ct.component_id
JOIN templates t ON t.id = ct.template_id
GROUP BY c.name, c.id, t.name, t.id;
Try this Query:
SELECT c.name AS article, c.id AS article_id, t.name AS template, t.id AS template_id,MAX(brand_names) AS brand_names, COUNT(p.component_id) AS Counts
FROM publications p
INNER JOIN components c
ON c.id = p.component_id
INNER JOIN brands_components bc
ON c.id = bc.component_id
AND bc.brand_id IN (1, 2, 3, 4)
INNER JOIN components_templates ct
ON c.id = ct.component_id
INNER JOIN templates t
ON t.id = ct.template_id
INNER JOIN (SELECT string_agg(b.name, ', ') AS brand_names
FROM brands b
INNER JOIN brands_components bcc
ON b.id = bcc.brand_id
INNER JOIN components c ON bcc.component_id = c.id
) brand_query ON brand_names IS NOT NULL
Group by c.name,c.id,t.name,t.id
I got it working with a function:
CREATE FUNCTION brands(int) RETURNS varchar AS $$
SELECT string_agg(b.name, ', ') AS brand_names
FROM brands b
INNER JOIN brands_components
ON b.id = brands_components.brand_id
WHERE brands_components.component_id = $1
$$ LANGUAGE SQL;
SELECT c.name, c.id, t.name AS template_name, t.id AS template_id, brands(c.id), COUNT(p.component_id)
FROM publications p
INNER JOIN components c
ON c.id = p.component_id
INNER JOIN brands_components bc
ON c.id = bc.component_id
AND bc.brand_id IN (1, 2, 3, 4)
INNER JOIN components_templates ct
ON c.id = ct.component_id
INNER JOIN templates t
ON t.id = ct.template_id
GROUP BY 1, 2, 3, 4
Not sure which is preferable, likely it's DineshDB's though.

sql query sum bringing back different results

I have the following two queries below, the Total is coming back different, but I am adding the sums in each of the query the same way. Why is the total coming back different?
select [Total Children] = (SUM(demo.NumberOfPreschoolers) + SUM(demo.NumberOfToddlers) + SUM(demo.NumberOfInfants)),
County = co.Description
from ClassroomDemographics as demo
inner join Classrooms as c on demo.Classroom_Id = c.Id
inner join Sites as s on c.Site_Id = s.Id
inner join Profiles as p on s.Profile_Id = p.Id
inner join Dictionary.Counties as co on p.County_Id = co.Id
where co.Description = 'MyCounty'
Group By co.Description
select [Number Of DLL Children] = SUM(cd.NumberOfLanguageSpeakers),
[Total Children] = (SUM(demo.NumberOfPreschoolers) + SUM(demo.NumberOfToddlers) + SUM(demo.NumberOfInfants)),
County = co.Description
from ClassroomDLL as cd
inner join Classrooms as c on cd.Classroom_Id = c.Id
inner join Sites as s on c.Site_Id = s.Id
inner join Profiles as p on s.Profile_Id = p.Id
inner join Dictionary.Counties as co on p.County_Id = co.Id
inner join ClassroomDemographics as demo on c.Id = demo.Classroom_Id
where co.Description = 'MyCounty'
Group by co.Description
Just a quick glance over the two querties, I would presume that:
inner join ClassroomDemographics as demo on c.Id = demo.Classroom_Id
in the second query is excluding results that are in the first query, therefor the aggregated values will be different.
Your join to the Classrooms table is joining with an extra table in the 2nd query.
Query 1:
from ClassroomDemographics as demo
inner join Classrooms as c on demo.Classroom_Id = c.Id
Query 2:
from ClassroomDLL as cd
inner join Classrooms as c on cd.Classroom_Id = c.Id
...
inner join ClassroomDemographics as demo on c.Id = demo.Classroom_Id
My bet is that the ClassroomDLL table has less data in it, or has rows with a null for one of the join criteria columns, either of which could exclude rows from the results and throw your aggregate totals off.

join tables sql

I have the following tables
Dishes table:
Category table:
t_time table:
To get connect a dish with the ingredients I have the following code which works:
$sql = "SELECT d.name, i.item\n"
. "FROM dish d\n"
. "JOIN dish_ingredients di ON ( d.id = di.dish_id )\n"
. "JOIN ingredients i ON ( di.ingredient_id = i.id )\n"
. "WHERE d.id =1\n"
. "LIMIT 0 , 30";
but now I want to connect the dish with the matching categories/t_time and cuisine . instead of having time= 6 and Category 3 being displayed when I do the query I want it to display 60 and fish instead. What would be the easiest way to do this? a good tutorial might help as well.
just join the tables just like the way you are doing.
SELECT d.name, i.item,
c.category,
e.cuisine,
f.t_time
FROM dish d
INNER JOIN dish_ingredients di ON ( d.id = di.dish_id )
INNER JOIN ingredients i ON ( di.ingredient_id = i.id )
INNER JOIN categories c ON (d.category = c.id)
INNER JOIN cuisine e ON d.cuisine = e.id
INNER JOIN t_time f ON d.time = f.id
WHERE d.id =1
-- LIMIT 0 , 30
If it is possible that a dish can have nullable values on category, cuisine, and t_time, use LEFT JOIN instead of INNER JOIN. Basically, INNER JOIN displays row on which it has at least one match on the table on which you have joined while the LEFT JOIN displays even if it has no rows on the other table.
Try this:
SELECT d.name, i.item, cat.category, t.t_time, cu.cuisine
FROM dish d
INNER JOIN dish_ingredients di ON d.id = di.dish_id
INNER JOIN ingredients i ON di.ingredient_id = i.id
INNER JOIN category cat ON d.category = cat.id
INNER JOIN cuisine cu ON d.cuisine = cu.id
INNER JOIN t_time t ON d.t_time = t.id
WHERE d.id = 1
$sql = "SELECT d.name, i.item, c.category, t.t_time, cs.cuisine\n"
. "FROM dishes d\n"
. "JOIN dish_ingredients di ON ( d.id = di.dish_id )\n"
. "JOIN ingredients i ON ( di.ingredient_id = i.id )\n"
. "LEFT JOIN category c ON ( c.id = d.category )\n"
. "LEFT JOIN t_time t ON ( t.id = d.t_time )\n"
. "LEFT JOIN cuisine cs ON ( cs.id = d.cuisine)\n"
. "WHERE d.id =1\n"
. "LIMIT 0 , 30";
Try to join to the tables with LEFT JOIN, it will show the category,t_time and cuisine if it exists.