Using the OR operator in a nested SQL Statement - sql

I have a database with vendors, what they sell and where they are located.
I need to search for vendors that are based off of a specific locality like a state or for vendors who sell a certain number of products.
An example of this question is:
What are the full names of all vendors who can supply more than one item or are based in Illinois?
This would be easy if I could use two sql queries (but for this problem I cannot).
Assuming there is no joins between tables used, my solution is incorrect but this is what I tried
select
cs.vendor_id, name, count(cs.PRODUCT_ID)
from
grocery.vendor
where
va.state_territory_province = 'Illinois'
group by
(cs.vendor_id)
or /# error found here #/
having
(count(cs.product_id)>1);
ERROR at line 1:
ORA-00933: SQL command not properly ended
If I try each one separately, I get these results
Using Illonois
select
cs.vendor_id, v.name, count(cs.PRODUCT_ID)
from
grocery.vendor v
inner join grocery.vendor_address va
on (v.vendor_id = va.vendor_id)
inner join grocery.can_supply cs
on (v.vendor_id = cs.vendor_id)
where
va.state_territory_province = 'Illinois'
group by
(cs.vendor_id, v.name);
VENDOR_ID NAME COUNT(CS.PRODUCT_ID)
33 Drinks R Us 1
35 Jungle Man 1
34 Poland Spring 1
Using the number of products
select
cs.vendor_id, v.name, count(cs.PRODUCT_ID)
from grocery.vendor v
inner join grocery.vendor_address va
on (v.vendor_id = va.vendor_id)
inner join grocery.can_supply cs
on (v.vendor_id = cs.vendor_id)
group by (cs.vendor_id, v.name)
having(
count(cs.product_ID)>1);
VENDOR_ID NAME COUNT(CS.PRODUCT_ID)
8 Orgo Home Farm 3
17 Wellness 2
21 Wily Wonka 4
27 Camel 3
29 Supplies R Us 5
13 Clean Me Please 5
15 Oral Care Inc 2
31 Cheese Cake Factory 2
37 Crunchy 2
1 Moo Moo Milk Farm 4
4 Haagen Daz 3
26 Beer Inc 4
6 Sailor Bob 3
10 Dawn 2
16 SPAM 2
18 Wonder inc 3
5 Butcher Mat 3
9 Soda Forever 4
14 Wash Shampoo Inc 4
24 Huntz 4
20 Hershey 3
22 Bake Me Inc 5
30 We Make Pizza 2
36 Taste Treat 3
7 Monkey Paradise 6
19 Puff 5
26 rows selected.
Basically I want to merge these two queries into one. Is there a way to nest these together?

I would be inclined to do:
select v.*
from gorcery.vendor v join
grocery.vendor_address va
on v.vendor_id = va.vendor_id
where va.state_territory_province = 'Illinois' or
(select count(*)
from grocery.can_supply cs
where v.vendor_id = cs.vendor_id
) > 1;
Note: this isn't perfect, because a vendor can have multiple addresses. So, I think the better solution is:
select v.*
from gorcery.vendor v
where exists (select 1
from grocery.vendor_address va
where v.vendor_id = va.vendor_id and
va.state_territory_province = 'Illinois'
) and
(select count(*)
from grocery.can_supply cs
where v.vendor_id = cs.vendor_id
) > 1;

You should be able to put both conditions in the HAVING clause:
select v.vendor_id,
v.name,
count(cs.PRODUCT_ID)
from grocery.vendor v
join grocery.vendor_address va
on v.vendor_id = va.vendor_id
join grocery.can_supply cs
on v.vendor_id = cs.vendor_id
group by v.vendor_id, v.name, va.state_territory_province
having va.state_territory_province = 'Illinois'
or count(cs.product_ID) > 1
As pointed out in the comments, I couldn't just use va.state_territory_province in the HAVING clause (silly me), so I added it to the GROUP BY clause. I am assuming that every vendor only has one address.

how about using UNION clause for the two queries?

Related

Trying to only return distinct ID entries from a table where all values of a certain column must be matched

So the Situation is that I have 3 Tables, 2 of which are "helper" tables while the other one is the main table I'm trying to get distinct IDs out of:
Main Table dbo.recipes has columns ID, Name and some others such as:
ID NAME
5 Veggie Cassola
6 Mozzarella Penne
7 Wiener Schnitzel with Fries
8 Grilled Salmon with Rice
9 Greek style Salad
The helpers are dbo.stock:
ID_USER ID_INGREDIENT
1 225
1 585
1 607
1 643
1 763
1 874
1 937
1 959
1 960
2 225
2 246
2 331
2 363
2 511
2 585
and dbo.content:
ID_INGREDIENT ID_RECIPE
98 5
196 5
333 5
607 5
608 5
613 5
627 5
643 5
763 5
874 5
951 5
956 5
225 6
585 6
607 6
Basically the dbo.stock is the inventory of ingredients that the user has hence user id and ingredient id.
The dbo.content is the ingredients needed to make a certain dish.
What I want to query is ONLY recipes that the user actually has the ingredients for, so that means that all the recipes which have ALL their ingredients matched (to a certain user) should be returned. The code that I have at the moment for my procedure is as follows:
SELECT * FROM [dbo].[recipe]
WHERE [recipe].[id] NOT IN
(SELECT DISTINCT [content].[id_recipe] FROM [dbo].[content]
WHERE [content].[id_ingredient] NOT IN
(SELECT [stock].[id_ingredient] FROM [dbo].[stock]
WHERE [stock].[id_user] = #userID))
which works, but I doubt this is the best way to achieve this. Is there a better way to reach the same?
MS SQL Server Express 2019
Basically, you want to find all the recipes where there isn't an ingredient in content that is not in stock. It's not the way you think about it in English, but it leads to this if you think about it that way in SQL:
DECLARE #userID int = 1;
SELECT ID, NAME
FROM dbo.recipe AS r
WHERE NOT EXISTS
(
SELECT id_ingredient FROM dbo.content WHERE id_recipe = r.ID
EXCEPT
SELECT id_ingredient FROM dbo.stock WHERE id_user = #userID
);
However, this query is more along the lines of yours, just without the expensive DISTINCTs found in both of the above plans (EXCEPT is sneaky like that), so is probably the best option:
DECLARE #userID int = 1;
SELECT ID, NAME
FROM dbo.recipe AS r
WHERE NOT EXISTS
(
SELECT 1 FROM dbo.content AS c
WHERE id_recipe = r.ID AND NOT EXISTS
(
SELECT 1 FROM dbo.stock
WHERE id_ingredient = c.id_ingredient
AND id_user = #userID
)
);
Example db<>fiddle
This is a classic Relational Division With Remainder question.
#AaronBertrand has given you a couple good solutions. Here is another one that is often used.
DECLARE #userID int = 1;
SELECT
r.Id,
r.Name
FROM dbo.recipe AS r
JOIN dbo.content AS c ON c.id_recipe = r.ID
LEFT JOIN dbo.stock AS s ON s.id_ingredient = c.id_ingredient
AND s.id_user = #userID
GROUP BY
r.Id,
r.Name
HAVING COUNT(*) = COUNT(s.id_ingredient);
This will join everything together (left-joining the stock), group by the recipe and return only those groupings that have the same number of rows as there are non-null stock rows. In other words, every content must match, and there must be at least one content.
There is a semantic difference: if you wanted also all recipes which have no `content, you can change it slightly.
DECLARE #userID int = 1;
SELECT
r.Id,
r.Name
FROM dbo.recipe AS r
LEFT JOIN dbo.content AS c ON c.id_recipe = r.ID
LEFT JOIN dbo.stock AS s ON s.id_ingredient = c.id_ingredient
AND s.id_user = #userID
GROUP BY
r.Id,
r.Name
HAVING COUNT(c.id_recipe) = COUNT(s.id_ingredient);
db<>fiddle

Using two Count() functions without changing the results

SELECT CARRIER.CARRIERID, PLAN.PLANID, PLNDESCRIPTION,
COUNT(MEMBER.PLANID) AS MBRCNT
FROM CARRIER
LEFT OUTER JOIN PLAN
ON CARRIER.CARRIERID = PLAN.CARRIERID
LEFT OUTER JOIN MEMBER
ON PLAN.PLANID = MEMBER.PLANID
GROUP BY CARRIER.CARRIERID, PLAN.PLANID, PLNDESCRIPTION
ORDER BY CARRIER.CARRIERID;
results in
CARR PLANID PLNDESCRIPTION MBRCNT
---- ---------- ----------------------------------- ----------
ANTH 4 Single SuperMed 6
ANTH 5 2-Party SuperMed 4
ANTH 6 Family SuperMed 7
BCBS 1 Single Basic Medical 9
BCBS 2 2-Party Basic Medical 15
BCBS 3 Family Basic Medical 11
DLT 7 Single Dental Only 6
DLT 8 Family Dental Only 0
MM 10 Single SuperMed with Dental 5
MM 11 2-Party SuperMed with Dental 0
MM 12 Family SuperMed with Dental 2
NWD 9 Life Only 2
PHC 0
and
SELECT CARRIER.CARRIERID, COUNT(PLAN.CARRIERID)
FROM CARRIER
LEFT OUTER JOIN PLAN
ON CARRIER.CARRIERID = PLAN.CARRIERID
LEFT OUTER JOIN MEMBER
ON PLAN.PLANID = MEMBER.PLANID
GROUP BY CARRIER.CARRIERID
ORDER BY CARRIER.CARRIERID;
result in
CARR COUNT(PLAN.CARRIERID)
---- ---------------------------------------
ANTH 17
BCBS 35
DLT 7
MM 8
NWD 2
PHC 0
How can I combine these to get all of the rows next to each other?
I think you just want analytic functions:
SELECT c.CARRIERID, p.PLANID, p.PLNDESCRIPTION,
COUNT(m.PLANID) AS MBRCNT,
SUM(COUNT(m.PLANID)) OVER (PARTITION BY c.CARRIERID) as CNT_2
FROM CARRIER c LEFT JOIN
PLAN p
ON c.CARRIERID = p.CARRIERID LEFT JOIN
MEMBER m
ON p.PLANID = m.PLANID
GROUP BY c.CARRIERID, p.PLANID, p.PLNDESCRIPTION
ORDER BY c.CARRIERID;
One option is to full outer join them together
with firstCount as (
SELECT CARRIER.CARRIERID, PLAN.PLANID, PLNDESCRIPTION,
COUNT(MEMBER.PLANID) AS MBRCNT
FROM CARRIER
LEFT OUTER JOIN PLAN
ON CARRIER.CARRIERID = PLAN.CARRIERID
LEFT OUTER JOIN MEMBER
ON PLAN.PLANID = MEMBER.PLANID
GROUP BY CARRIER.CARRIERID, PLAN.PLANID, PLNDESCRIPTION
ORDER BY CARRIER.CARRIERID
)
secondCount as (
SELECT CARRIER.CARRIERID, COUNT(PLAN.CARRIERID) as count
FROM CARRIER
LEFT OUTER JOIN PLAN
ON CARRIER.CARRIERID = PLAN.CARRIERID
LEFT OUTER JOIN MEMBER
ON PLAN.PLANID = MEMBER.PLANID
GROUP BY CARRIER.CARRIERID
ORDER BY CARRIER.CARRIERID
)
select coalesce(firstCount.carrierId, secondCount.carrierId) as carrierId,
firstCount.PLANID,
firstCount.PLNDESCRIPTION,
firstCount.MBRCNT,
secondCount.Count
from firstCount
full outer join secondCount on firstCount.CarrierId = secondCount.CarrierId

Group by with two columns

I am trying to write a query using group by in sub query ,I referred lot of blogs but could not get all the values.
I have three tables and below is the structure of those tables.
Pet_Seller_Master
ps_id ps_name city_id
2 abc 1
3 xyz 2
4 fer 4
5 bbb 1
City_Master
city_id city_name
1 Bangalore
2 COIMBATORE
4 MYSORE
Api_Entry
api_id ps_id otp
1 2 yes
2 3
3 2 yes
4 3 yes
5 4
6 5 yes
7 5 yes
8 5 yes
Query is to get number of sellers, no of pet sellers with zero otp, no of pet sellers with 1 otp, no of pet sellers with 2 otp,no of pet sellers with otp>2 for the particular city and within date range.
Through Below query I am able to get city , psp , and zero otp
select cm.city_name,
count(ps.ps_id) as PSP,
((select count(ps1.ps_id)
FROM ps_master ps1
WHERE ps1.city = cm.city_id)-
(SELECT count(distinct ps1.ps_id)
from ps_master ps1
INNER JOIN api_entry ae ON ps1.ps_id = ae.ps_id and otp!=''
WHERE ps1.city = cm.city_id and date(timestamp) >= curdate() - INTERVAL DAYOFWEEK(curdate())+6 DAY AND date(timestamp) < curdate())) as zero_psp
from ps_master ps INNER JOIN city_master cm ON ps.city = cm.city_id and cm.city_type = 'IN HOUSE PNS'
group by city_id
Please tell me the solution to solve this query.
Thanks in advance
It's not hard to do and you were on a right track. Here is what I would use:
select c.city_name, a.otp, p.ps_name, COUNT(*) nbr
from Api_Entry a
inner join Pet_Seller_Master p on p.ps_id=a.ps_id
inner join City_Master c on p.city_id=c.city_id
group by c.city_name, a.otp, p.ps_name
Now, if you want to get the number of sellers with zero otp, you just apply where clause:
where otp <> 'yes'
If you want to get the number of pet sellers with otp>2, then you just use subquery:
select *
from (
select c.city_name, a.otp, p.ps_name, COUNT(*) nbr
from #tempA a
inner join #tempP p on p.ps_id=a.ps_id
inner join #tempC c on p.city_id=c.city_id
group by c.city_name, a.otp, p.ps_name
) g
where nbr > 2

Inner join on two tables, with clause

with one as
(Select sno = ROW_NUMBER()OVER (order by complaint_id), Complaint_Id, Complaint.ComplaintType_id, Complaint.complaintProfileId, Complaint.Description,
Complaint.Email, Complaint.PriorityLevel_id, Complaint.Date_Complained, Complaint.Status, Complaint.AdminComments, Complaint.Phone, Complaint.Evidence
from Complaints Complaint )
The result of this query is (not the entire result)
sno complaintProfileId
1 14
2 15
3 15
4 14
5 14
6 13
The second subquery:
two as
(SELECT Complaint.complaintProfileId,
CASE
WHEN MMB_Name IS NOT NULL THEN MMB_Name
WHEN UPPMembership.profile_id IS NOT NULL THEN 'UPP'
ELSE 'Not found'
END as Name
FROM Complaints Complaint
LEFT JOIN MMBMembership
ON MMBMembership.profile_id = Complaint.complaintProfileId
left JOIN MMB_BusinessProfiles mmbProfiles
ON mmbProfiles.MMB_id = MMBMembership.MMB_id
LEFT JOIN UPPMembership
ON UPPMembership.profile_id = Complaint.complaintProfileId)
complaintProfileId Name
14 UPP
15 Marlon
15 Marlon
14 UPP
14 UPP
13 Rodolfo
So this is where I am having trouble with
select one.*, two.Name
from one join two
on one.complaintProfileId = two.complaintProfileId
This query returns 36 records. id 14 is being returned 9times and 15-6times and so on..
I am doing a inner join but still not sure
Thanks
Sun
You need to join on a unique key. Each '14' on the left side is being joined to each of the three '14's on the right side. (3x3=9)

SQL issue: Calculating percentages and using multiple joins to the same table

Here's an obfuscated version of something I've been trying to do at work. Say I have been given this month's data for customers in my shop - how much they've spent split by the food type:
CUSTOMER FOOD_TYPE FOOD_TYPE_VALUE
1 SWEET 52.6
1 SAVOURY 31.0
1 DAIRY 45.8
1 DRINKS 12.1
2 SWEET 15.1
2 SAVOURY 44.1
2 DRINKS 23.4
3 SWEET 95.7
3 SAVOURY 20.0
3 DAIRY 10.8
3 DRINKS 57.1
It has been decided that Customer 3 is our ideal customer as he fits the demographic profile, and we want to track month by month, how everybody's distribution of shopping preferences differs from his.
I can get the percentage allocation for each food type for each customer with:
SELECT
c1.customer,
c1.food_type,
100 * c1.food_type_value / sum(c2.food_type_value)
FROM
mytable c1 INNER JOIN mytable c2
ON c1.customer = c2.customer
group by c1.customer, c1.food_type, c1.food_type_value
But I am having trouble constructing a query that will give me a further column with the matching percentage values for my ideal customer. ie:
CUSTOMER FOOD_TYPE FOOD_TYPE_PERC IDEAL_PERC
1 SWEET 37 52
1 SAVOURY 22 11
1 DAIRY 32 6
1 DRINKS 9 31
Any tips on how I can achieve this without too much mess?
Join it on the subset of the customer table that contains your ideal customer:
SELECT
c1.customer,
c1.food_type,
100 * c1.food_type_value / sum(c2.food_type_value),
c3.FOOD_TYPE_VALUE / sum(c2.food_type_value) as IDEAL_PERC
FROM
mytable c1 INNER JOIN mytable c2
ON c1.customer = c2.customer
INNER JOIN (
SELECT FOOD_TYPE, FOOD_TYPE_VALUE
FROM mytable
WHERE customer = 3) c3
ON c2.FOOD_TYPE = c3.FOOD_TYPE
group by c1.customer, c1.food_type, c1.food_type_value, c3.FOOD_TYPE_VALUE
Your comment to my answer suggests you need to divide by the FOOD_TYPE_VALUE of the ideal customer, so do this:
SELECT
c1.customer,
c1.food_type,
100 * c1.food_type_value / sum(c2.food_type_value),
c3.IDEAL_PERC
FROM
mytable c1 INNER JOIN mytable c2
ON c1.customer = c2.customer
INNER JOIN (
SELECT s1.FOOD_TYPE,
100 * s1.FOOD_TYPE_VALUE / sum(s2.FOOD_TYPE_VALUE) IDEAL_PERC
FROM mytable s1
INNER JOIN mytable s2
on s1.customer = s2.customer
WHERE s1.customer = 3
GROUP BY s1.FOOD_TYPE, s1.FOOD_TYPE_VALUE) c3
ON c2.FOOD_TYPE = c3.FOOD_TYPE
group by c1.customer, c1.food_type, c1.food_type_value, c3.IDEAL_PERC