Find all recipes with just vegan products - sql

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;

Related

JOIN two SELECT without UNION

It needs to download the ID, name and surname of those who have registered by the deadline and have an identity card (with the relevant conditions) or passport (with the relevant conditions). ID card and passport are two separate tables.
I have made the SQL queries in UNION format and it works:
select distinct p.id, p.name, p.surname from persons.person p
join persons.documents d on d.person_id = p.id
join persons.id_card idd on d.id_card_id = idd.id
join persons.id_card_to_registration ir on idd.id = ir.id_card
join registrations.registration r on ir.registration_id = r.id
where p.created_at >= '2022-01-01'
and p.created_at <= '2022-03-30'
and p.registration_id = r.id
and ir.status in (0,5)
UNION
select distinct p.id, p.name, p.surname from persons.person p
join persons.documents d on d.person_id = p.id
join persons.passport pass on d.passport_id = pass.id
join persons.passport_country pc on pc.id = pass_country_id
join persons.passport_to_registration pr on pass.id = pr.passport_id
join registrations.registration r on pr.registration_id = r.id
where p.created_at >= '2022-01-01'
and p.created_at <= '2022-03-30'
and p.registration_id = r.id
and pc.zone in (0,1) or (pc.zone is null and pass.safe = true);
I would now like to do this SQL in one query without union and unfortunately it doesn't work for me - I tried to do it like this:
select distinct p.id, p.name, p.surname from persons.person p
join persons.documents d on d.person_id = p.id
left join persons.id_card idd on d.id_card_id = idd.id
left join persons.id_card_to_registration ir on idd.id = ir.id_card
left join persons.passport pass on d.passport_id = pass.id
left join persons.passport_country pc on pc.id = pass_country_id
left join persons.passport_to_registration pr on pass.id = pr.passport_id
join registrations.registration r on ir.registration_id = r.id
where p.created_at >= '2022-01-01'
and p.created_at <= '2022-03-30'
and p.registration_id = r.id
and (ir.status in (0,5) or ir.status is null)
and pc.zone in (0,1) or (pc.zone is null and pass.safe = true)
And it doesn't return any records to me. I would like some advice on what error I have made. And is it possible to create such a query without union?
The SQL in clause may help to filter by each condition. This may be the way to avoid the union clause, please let me know if this works for you:
select distinct p.id, p.name, p.surname from persons.person p
where p.created_at >= '2022-01-01'
and p.created_at <= '2022-03-30'
and ((p.id in
(select distinct p2.id from persons.person p2
d.person_id from persons.documents d on d.person_id = p2.id
join persons.id_card idd on d.id_card_id = idd.id
join persons.id_card_to_registration ir on idd.id = ir.id_card
join registrations.registration r on ir.registration_id = r.id
where p2.registration_id = r.id
and ir.status in (0,5)
)
) or (
(p.id in
(select distinct p3.id from persons.person p3
d.person_id from persons.documents d on d.person_id = p3.id
join persons.passport pass on d.passport_id = pass.id
join persons.passport_country pc on pc.id = pass_country_id
join persons.passport_to_registration pr on pass.id = pr.passport_id
join registrations.registration r on pr.registration_id = r.id
where p3.registration_id = r.id
and pc.zone in (0,1) or (pc.zone is null and pass.safe = true);
)
)
))
I think you'll have better performance if you can convert the query to use EXISTS.
SELECT DISTINCT p.id, p.name, p.surname
FROM persons.person p
WHERE p.created_at >= '2022-01-01'
AND p.created_at <= '2022-03-30'
AND(EXISTS (SELECT *
FROM persons.documents d
JOIN persons.id_card idd
ON d.id_card_id = idd.id
JOIN persons.id_card_to_registration ir
ON idd.id = ir.id_card
JOIN registrations.registration r
ON ir.registration_id = r.id
WHERE p.id = d.person_id
AND p.registration_id = r.id
AND ir.status IN (0,5))
OR EXISTS (SELECT *
FROM persons.documents d
JOIN persons.passport pass
ON d.passport_id = pass.id
JOIN persons.passport_country pc
ON pc.id = pass_country_id
JOIN persons.passport_to_registration pr
ON pass.id = pr.passport_id
JOIN registrations.registration r
ON pr.registration_id = r.id
WHERE p.id = d.person_id
AND p.registration_id = r.id
AND pc.zone IN (0,1)
OR (pc.zone IS NULL
AND pass.safe = TRUE)))

How to join queries with a subquery?

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'
)

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.

How to select top when already selected fields

Just wanted to ask how to add a 'select top 1 *' when I've already selected fields from a list? I seen examples in other codes but don't quite get it. Thought will be easier if see it in a code I constructed.
Below is an example of a query I have:
select frp.ProductPersonID,frp.FlightSeatId, frp.PlusMealId, per.TitleID, per.surname, per.FirstName, per.PersonTypeId, tor.PersonID, tor.Reference
from package pk
inner join product p on p.packageid = pk.packageid
inner join productperson pp on pp.productid = p.productid
inner join person per on per.personid = pp.personid
left join flightlogicalseat fls on fls.productpersonid = pp.productpersonid
inner join TourOperatorReference tor on tor.PersonID = per.PersonId
inner join FlightReservationPassenger frp on frp.ProductPersonID = pp.ProductPersonId
where pk.Reference LIKE '%'
and ProductTypeId =1
Simply try to use TOP keyword like this:
select TOP 1 frp.ProductPersonID,frp.FlightSeatId, frp.PlusMealId, per.TitleID,
You can just wrap your existing query in new query:
SELECT TOP 1 * FROM
(select frp.ProductPersonID,frp.FlightSeatId, frp.PlusMealId, per.TitleID, per.surname, per.FirstName, per.PersonTypeId, tor.PersonID, tor.Reference
from package pk
inner join product p on p.packageid = pk.packageid
inner join productperson pp on pp.productid = p.productid
inner join person per on per.personid = pp.personid
left join flightlogicalseat fls on fls.productpersonid = pp.productpersonid
inner join TourOperatorReference tor on tor.PersonID = per.PersonId
inner join FlightReservationPassenger frp on frp.ProductPersonID = pp.ProductPersonId
where pk.Reference LIKE '%'
and ProductTypeId =1) t

Sql join 1 instance

I require some help with my very shaky sql skills.
Say I have the following select statement:
SELECT DISTINCT
p.ProjectId,
p.Title,
i.Name,
p.StartDate,
p.EndDate,
ped.ProjectEthicsDocumentId,
st.Description AS StatusText
FROM
dbo.Project p
inner join dbo.WorkflowHistory w ON p.ProjectId = w.ProjectId
left join dbo.ProjectInstitution pi ON pi.ProjectId = p.ProjectId
left join dbo.Institution i ON i.InstitutionId = pi.InstitutionId
left join dbo.ProjectEthicsDocument ped on p.ProjectId = ped.ProjectId
left join dbo.Status st ON p.StatusId = st.StatusId
This will return all the projects and other relevant details from the relevant tables. Now, say I have 2 institutions for 'Project A'. This statement will return 2 rows for 'Project A', one for each institution. How do I set it so that it only returns the first row of each project it finds? I want one instance of every project with say the first institution found.
The easiest way is probably with the row_number() function:
select *
from (SELECT DISTINCT p.ProjectId, p.Title, i.Name, p.StartDate,p.EndDate,
ped.ProjectEthicsDocumentId, st.Description AS StatusText,
row_number() over (partition by p.ProjectId order by i.InstitutionId) as seqnum
FROM dbo.Project p
inner join dbo.WorkflowHistory w ON p.ProjectId = w.ProjectId
left join dbo.ProjectInstitution pi ON pi.ProjectId = p.ProjectId
left join dbo.Institution i ON i.InstitutionId = pi.InstitutionId
left join dbo.ProjectEthicsDocument ped on p.ProjectId = ped.ProjectId
left join dbo.Status st ON p.StatusId = st.StatusId
) p
where seqnum = 1;
You can move selecting institution name to a subquery. This way you it doesn't affect how other tables are joined.
SELECT DISTINCT
p.ProjectId,
p.Title,
(SELECT TOP 1 i.Name FROM dbo.Institution i
INNER JOIN dbo.ProjectInstitution pi ON i.InstitutionId = pi.InstitutionId
WHERE pi.ProjectId = p.ProjectId) AS Name,
p.StartDate,
p.EndDate,
ped.ProjectEthicsDocumentId,
st.Description AS StatusText
FROM
dbo.Project p
inner join dbo.WorkflowHistory w ON p.ProjectId = w.ProjectId
left join dbo.ProjectEthicsDocument ped on p.ProjectId = ped.ProjectId
left join dbo.Status st ON p.StatusId = st.StatusId
you could use
;with cte as
(
<your select statement> `,`
Row_number() over(partition by <column that has 2 records> order by ProjectId) as rn
)
--then do this
select * from cte where rn=1