How to select all dishes containing a list of ingredients - sql

I am looking to build a query which would return the dishes that contain a list of ingredients but I can't figure it out. The dish returned must contain at least the list of ingredients requested.
Ingredients
============
ID
IngredientName
IngredientAmount
P_ID
Dishes
==========
ID
DishName
DishIngredients
==========
ID
DishID
IngredientID
So far I built the following query, but it returns all meals that contain at least 1 of the ingredients in the list, not only those which contain all ingredients in the list.
The list of ingredients to match is a string converted to a table because I want to use it as an argument in a stored procedure.
DECLARE #IdIngredients nvarchar(1024) = '4174, 4028'
DECLARE #Ingredients TABLE (IdIngredient int)
INSERT #Ingredients (IdIngredient)
SELECT Convert(int, value) FROM STRING_SPLIT(#IdIngredients, ',')
SELECT DISTINCT D.Id
FROM Dishes D
INNER JOIN DishIngredients DI ON DI.DishID = D.ID
WHERE IngredientID IN (SELECT IdIngredient FROM #Ingredients)
Any help would be welcome. I am sure there is a way to manage it either with a join or by counting matching ingredients, but I can't figure it out.
While writing this post, I maybe found a solution but I still need to test it:
DECLARE #IdIngredients nvarchar(1024) = '4174, 4028'
DECLARE #Ingredients TABLE (IdIngredient int)
INSERT #Ingredients (IdIngredient)
SELECT Convert(int, value) FROM STRING_SPLIT(#IdIngredients, ',')
SELECT DISTINCT D.Id
FROM Dishes D
INNER JOIN DishIngredients DI ON DI.DishID = D.ID
WHERE IngredientID IN (SELECT IdIngredient FROM #Ingredients)
GROUP BY D.Id
HAVING COUNT(D.Id) = (SELECT COUNT(*) FROM #Ingredients)

A double NOT EXISTS should do the trick:
SELECT D.*
FROM Dishes As D
WHERE Not Exists
(
SELECT 1
FROM #Ingredients As I
WHERE Not Exists
(
SELECT 1
FROM DishIngredients As DI
WHERE DI.DishID = D.Id
And DI.IngedientId = I.IdIngredient
)
)
Or, if you'd prefer, a NOT EXISTS with an EXCEPT:
SELECT D.*
FROM Dishes As D
WHERE Not Exists
(
SELECT IdIngredient
FROM #Ingredients
EXCEPT
SELECT IngredientId
FROM DishIngredients As DI
WHERE DI.DishID = D.Id
)
EXCEPT and INTERSECT (Transact-SQL) - SQL Server | Microsoft Docs

So far I buit following query but it returns all meals that contain at least 1 of the ingredients in the list but not only those which contain all ingredients in the list.
When you split the ingredients list, you can gather how many ingredients there are. Let it be X.
Now if you join the dishes with the recipes,
SELECT D.*, COUNT(*) AS N
FROM Dishes As D
JOIN DishIngredients AS DI ON (DI.DishID = D.ID)
JOIN Ingredients AS I ON (I.ID = DI.IngredientID)
WHERE Ingredients.IngredientName IN (#IngredientNameList)
GROUP BY D.ID
it will report, for each dish, how many ingredients there are from the supplied ingredient list.
The dishes you want are those HAVING N = X.
(If you have the ingredients IDs, even better, you can save yourself the JOIN with Ingredients and use IngredientID instead).

Related

Count total number of rows where this.row is related with rows in another table

I have two simple tables, parents and children. I am trying to count the number of parents who have at least one child.
create table People(
id integer unique,
name varchar(120),
primary key (id)
);
create table children(
id integer unique,
name varchar(120),
parentId integer,
primary key(id),
foreign key (parentId) references People(id)
);
This is the code I tried but it gives me the total number of children instead:
select count(*)
from (people p join children ch on ch.parentid = p.id)
having count(ch.id) > 0;
I am trying to count the number of parents who have at least one children.
This should be as simple as:
SELECT COUNT(*)
FROM people p
WHERE EXISTS (SELECT 1 FROM children c WHERE c.parentid = p.id)
Using EXISTS is usually the most efficient way to check that something, well, exists.
You're close. You just need to make the check for children on a per-parent basis:
SELECT COUNT(*) AS parents_with_children
FROM (SELECT p.name, COUNT(c.id) AS num_children
FROM people p
JOIN children c ON c.parentid = p.id
GROUP BY p.name
HAVING COUNT(c.id) > 0) p
Demo on dbfiddle
SELECT COUNT(*),p.*
FROM People p JOIN children c ON c.parnetId=p.id
WHERE NOT c.parnetId IS NULL
GROUP BY (p.id)
(no need for having since it only joins existing children anyways)
select count(p.*)
from people p inner join children ch
on ch.parentid = p.id
You could try something like this,
SELECT COUNT(DISTINCT children.parentid)
FROM People
INNER JOIN children
ON children.parentid = people.id;
With EXISTS:
select count(distinct p.id) counter from people p
where exists (
select 1 from children
where parentid = p.id
)
or even better:
select count(distinct parentid) counter
from children
because all the info you need is in the table children, so just count the distinct values in column parentid

SQL Logic or Query to find what is missing

Hi all I need your help on the following logic. Currently I have a table that has 300 records, that are related but on this new tables I have the columns called them country, POS so for each combination of country + POs I should have 1 record of table A.
but the situation is that when I am checking the last table someone only inserted some records of table A into table b, and now I have to find what are the missing combination.
could you guide me on the logic that I should use for this, any question please let me know.
Example
Table A name Categories
Milk
Hot Sauces
Meat
Table B
Category POS Country
Milk DF Mexico
Meat DF Mexico
Hot Sauces DF Mexico
Milk CC Canada
Like you can see Canada still missing 2 categories but this table have all Americas countries so let say I have 20 countries. So 20 multiple by 300 categories I should have 6000 distinct records or more because each country have different quantities of POS, right, but someone only inserted let say 3600 records so now I have to find what combination are missed.
If you Don't have a country table you can derive one by selecting DISTINCT Country from your TableB. Then cross join that with Categories for a Cartesian Join (all possible combinations) between Countries and Categories.
SELECT countries.country, c.Category
FROM
(SELECT DISTINCT Country
FROM
#TableB) as countries
CROSS JOIN #Categories c
LEFT JOIN #TableB b
ON countries.Country = b.Country
AND c.Category = b.Cateogry
WHERE
b.Cateogry IS NULL
If you actually need All Possible Combinations of POS and Country and Categories. In this case it sounds like POS is more like a store than a point of sale but same concept. Just derive a POS table if you don't have one and cross join it with the cross join of countries and categories.
SELECT
countries.country, c.Category, pos.POS
FROM
(SELECT DISTINCT Country
FROM
#TableB) as countries
CROSS JOIN #Categories c
CROSS JOIN (SELECT DISTINCT POS
FROM
#TableB) as pos
LEFT JOIN #TableB b
ON countries.Country = b.Country
AND c.Category = b.Cateogry
AND pos.POS = b.POS
WHERE
b.Cateogry IS NULL
But I would guess that not every store is in every country so you probably want to constrain the POS combiantions to POS's that are available in a particular country. Again you can derive the table if you don't have one this time include Country and do an inner join between the derived country table and it.
SELECT
countries.country, c.Category, pos.POS
FROM
(SELECT DISTINCT Country
FROM
#TableB) as countries
CROSS JOIN #Categories c
INNER JOIN (SELECT DISTINCT Country, POS
FROM
#TableB) as pos
ON countries.Country = pos.Country
LEFT JOIN #TableB b
ON countries.Country = b.Country
AND c.Category = b.Cateogry
AND pos.POS = b.POS
WHERE
b.Cateogry IS NULL
test data used:
DECLARE #Categories AS TABLE (Category VARCHAR(25))
DECLARE #TableB AS TABLE (Cateogry VARCHAR(25),POS CHAR(2), Country VARCHAR(25))
INSERT INTO #Categories VALUES ('Milk'),('Hot Sauces'),('Meat')
INSERT INTO #TableB VALUES ('Milk','DF','Mexico'),('Meat','DF','Mexico'),('Hot Sauces','DF','Mexico'),('Milk','CC','Canada'),('Milk','XX','Canada')
Hi, You can use below logic to get missing data,
SELECT column_name FROM tableA WHERE column_name NOT IN
(SELECT column_name FROM tableB)
Change the required column names and table names in the query. Use same column names in all three places

SELECT Statement in CASE

Please don't downgrade this as it is bit complex for me to explain. I'm working on data migration so some of the structures look weird because it was designed by someone like that.
For ex, I have a table Person with PersonID and PersonName as columns. I have duplicates in the table.
I have Details table where I have PersonName stored in a column. This PersonName may or may not exist in the Person table. I need to retrieve PersonID from the matching records otherwise put some hardcode value in PersonID.
I can't write below query because PersonName is duplicated in Person Table, this join doubles the rows if there is a matching record due to join.
SELECT d.Fields, PersonID
FROM Details d
JOIN Person p ON d.PersonName = p.PersonName
The below query works but I don't know how to replace "NULL" with some value I want in place of NULL
SELECT d.Fields, (SELECT TOP 1 PersonID FROM Person where PersonName = d.PersonName )
FROM Details d
So, there are some PersonNames in the Details table which are not existent in Person table. How do I write CASE WHEN in this case?
I tried below but it didn't work
SELECT d.Fields,
CASE WHEN (SELECT TOP 1 PersonID
FROM Person
WHERE PersonName = d.PersonName) = null
THEN 123
ELSE (SELECT TOP 1 PersonID
FROM Person
WHERE PersonName = d.PersonName) END Name
FROM Details d
This query is still showing the same output as 2nd query. Please advise me on this. Let me know, if I'm unclear anywhere. Thanks
well.. I figured I can put ISNULL on top of SELECT to make it work.
SELECT d.Fields,
ISNULL(SELECT TOP 1 p.PersonID
FROM Person p where p.PersonName = d.PersonName, 124) id
FROM Details d
A simple left outer join to pull back all persons with an optional match on the details table should work with a case statement to get your desired result.
SELECT
*
FROM
(
SELECT
Instance=ROW_NUMBER() OVER (PARTITION BY PersonName),
PersonID=CASE WHEN d.PersonName IS NULL THEN 'XXXX' ELSE p.PersonID END,
d.Fields
FROM
Person p
LEFT OUTER JOIN Details d on d.PersonName=p.PersonName
)AS X
WHERE
Instance=1
Ooh goody, a chance to use two LEFT JOINs. The first will list the IDs where they exist, and insert a default otherwise; the second will eliminate the duplicates.
SELECT d.Fields, ISNULL(p1.PersonID, 123)
FROM Details d
LEFT JOIN Person p1 ON d.PersonName = p1.PersonName
LEFT JOIN Person p2 ON p2.PersonName = p1.PersonName
AND p2.PersonID < p1.PersonID
WHERE p2.PersonID IS NULL
You could use common table expressions to build up the missing datasets, i.e. your complete Person table, then join that to your Detail table as follows;
declare #n int;
-- set your default PersonID here;
set #n = 123;
-- Make sure previous SQL statement is terminated with semilcolon for with clause to parse successfully.
-- First build our unique list of names from table Detail.
with cteUniqueDetailPerson
(
[PersonName]
)
as
(
select distinct [PersonName]
from [Details]
)
-- Second get unique Person entries and record the most recent PersonID value as the active Person.
, cteUniquePersonPerson
(
[PersonID]
, [PersonName]
)
as
(
select
max([PersonID]) -- if you wanted the original Person record instead of the last, change this to min.
, [PersonName]
from [Person]
group by [PersonName]
)
-- Third join unique datasets to get the PersonID when there is a match, otherwise use our default id #n.
-- NB, this would also include records when a Person exists with no Detail rows (they are filtered out with the final inner join)
, cteSudoPerson
(
[PersonID]
, [PersonName]
)
as
(
select
coalesce(upp.[PersonID],#n) as [PersonID]
coalesce(upp.[PersonName],udp.[PersonName]) as [PersonName]
from cteUniquePersonPerson upp
full outer join cteUniqueDetailPerson udp
on udp.[PersonName] = p.[PersonName]
)
-- Fourth, join detail to the sudo person table that includes either the original ID or our default ID.
select
d.[Fields]
, sp.[PersonID]
from [Details] d
inner join cteSudoPerson sp
on sp.[PersonName] = d.[PersonName];

How to select members of a special kind of relationship in sql

I have 3 table as follow :
s(s# int,sname nchar(10))
p(p# int,pname nchar(10))
sp(s# int,p# int)
table "s" is table of suppliers and "s#" is primary key of it.also table "p" is table of products and "p#" is primary key on it."s#" and "p#" are foreign key in table "sp".
now my question is "How can I select name of suppliers from table "s" which producing all of products in table "p"...
SELECT p.*, s.sname FROM s, sp, p WHERE s.s# = sp.s# AND sp.p# = p.p#;
This statement will output all products with all their suppliers.
Now we group my suppliers, and count how many products they provide:
SELECT s.sname, count(*) FROM s, sp, p WHERE s.s# = sp.s# AND sp.p# = p.p# GROUP BY s.s#;
Now we know exacly, how many products each supplier provides. And we also know, how many products are in the productstable:
SELECT count(*) FROM p;
If you compare these values, you get your desired result:
SELECT amounts.name FROM
( SELECT s.sname AS name, count(*) AS offers
FROM s, sp, p
WHERE s.s# = sp.s# AND sp.p# = p.p#
GROUP BY s.s# ) amounts, -- this is a temp. tablename
( SELECT count(*) AS avaiable FROM p ) countTbl
WHERE amounts.offers = countTbl.avaiable;
Notice, that I didn't test the query. But you should get an idea on how to solve this problem.
It might also be possible to write this query more efficient, but this one can be understood easily.
There are two ways to do this, the first I thought of was to invert the logic.
Rather than attempting to find every entry of P let's just look for any that don't exist, then exclude those entries from S:
SELECT *
FROM S
WHERE
NOT EXISTS (
SELECT *
FROM P
LEFT JOIN SP
ON P.P# = SP.P#
AND SP.S# = S.S#
WHERE
SP.P# IS NULL
)

Select persons which have overlap with other table

I didn't even know how to come up with a good title, so I hope I can describe my problem in a right way :)
So I have a person table, and it has a N:N relationship with keywords via the table PersonKeywords.
Then I also have a Search table, and it also has a N:N relationship with keywords via the table SearchKeywords.
Now the person can have a relationship with keyword A and B, and the search record can have a relationship with the keywords A and C.
Now I want the person in my resultset, because it has at least one (in this 'A') of the keywords the search record has.
I also want the person who has 'A', the one with 'C', the one with 'A' and 'C', but not the one with only B.
So it's a match on two lists, but I don't know where to start to create such a statement...
So you have three people...
declare #persons table (id int identity(1,1), name varchar(10))
insert #persons (name) values ('Babs'),('Ken'),('Neville'),('Sue')
Babs has A and B, Ken has A and C, Neville has B only and Sue has C only
declare #personkeywords table (personid int, keyword varchar(5))
insert #personkeywords values (1,'a'),(1,'b'),(2,'a'),(2,'c'),(3,'b'),(4,'c')
The search is for A or C
declare #searchkeywords table (searchid int, keyword varchar(5))
insert #searchkeywords values (1,'a'),(1,'c')
So...
select distinct persons.*
from #persons persons
inner join #personkeywords personkeywords on persons.id = personkeywords.personid
inner join #searchkeywords searchkeywords on personkeywords.keyword = searchkeywords.keyword
where
searchkeywords.searchid = 1
Gives
1 Babs
2 Ken
4 Sue
Although I don't have very much information to work with, the following should at least help you...
SELECT s.SearchID, k.Keyword, p.PersonID, p.Name
FROM Search s
INNER JOIN SearchKeywords sk ON s.SearchID = sk.SearchID
INNER JOIN Keywords k ON sk.KeywordID = k.KeywordID
LEFT OUTER JOIN PersonKeywords pk ON k.KeywordID = pk.KeywordID
LEFT OUTER JOIN Person p ON pk.PersonID = p.PersonID
WHERE k.Keyword = 'mykeyword'
GROUP BY s.SearchID, k.Keyword, p.PersonID, p.Name