SQL | List all all tuples(a, b, c) if there exists another tuple with equal (b,c) - sql

I have three tables where the bold attribute(s) is the primary key
Restaurants(restaurant_ID, name, ...)
resturant_ID, name, ...
1, Macdonalds
2, Hubert
3, Dorsia
... ...
Identifier(restaurant_ID, food_ID)
restaurant_ID, food_ID, ...
1, 1
1, 4
2, 1
2, 7
... ...
Food(food_ID, name, ...)
food_ID food_name
1 Chips
2 Burgers
3 Salmon
... ...
Using postgres I want to list out all restaurants (restaurant_id and name - 1 row per restaurant) that have share the exact same set of foods with at least one other restaurant.
For example, let's say
Restaurant with ID "1" has only associated food_id's 1 and 4 as shown in Identifier
Restaurant with ID "3" has only associated food_id's 4 and 1 as shown in Identifier
Restaurant with ID "7" has only associated food_id's 6 as shown in Identifier
Restaurant with ID "9" has only associated food_id's 6 as shown in Identifier
Then output
Restaurant_id name
1 name1
3 name3
7 ...
9 ...
Any help would be greatly appreciated!
Thank you

Use the aggregate function string_agg() to get the full list of foods for each restaurant:
with cte as (
select restaurant_ID,
string_agg(food_ID::varchar(10),',' order by food_ID) foods
from identifier
group by restaurant_ID
)
select r.*
from Restaurants r inner join cte c
on c.restaurant_ID = r.restaurant_ID
where exists (select 1 from cte where restaurant_ID <> c.restaurant_ID and foods = c.foods)
But I would prefer to group restaurants based on matching foods:
with cte as (
select restaurant_ID,
string_agg(food_ID::varchar(10),',' order by food_ID) foods
from identifier
group by restaurant_ID
)
select string_agg(r.name, ',') restaurants
from Restaurants r inner join cte c
on c.restaurant_ID = r.restaurant_ID
group by foods
having count(*) > 1
See the demo.

Here is a way to get the unique set of resturants having exactly same food items. This uses array_agg() and array_to_string() functions
With cte as
(select T.restaurant_id, array_to_string(array_agg(food_id), ',') as food_list
from
(select *
from Identifier t1
order by restaurant_id, food_id) T
group by T.restaurant_id)
select
concat(r1.name,',',r2.name) as resturant_names,
t1.restaurant_id as restaurant_id1,
r1.name as restaurant_1,
t2.restaurant_id as restaurant_id2,
r2.name as restaurant_2,
t1.food_list as common_food_ids
from cte t1
join cte t2
on t1.restaurant_id < t2.restaurant_id
and t1.food_list = t2.food_list
left join Restaurants r1
on t1.restaurant_id = r1.restaurant_id
left join Restaurants r2
on t2.restaurant_id = r2.restaurant_id;
EDIT : Here is a dB fiddle - https://dbfiddle.uk/?rdbms=postgres_12&fiddle=e2de05edfbe036cc0d81c64d60f0b599 . Also, just for reference, solution to the same problem in Oracle using listagg function - https://dbfiddle.uk/?rdbms=oracle_11.2&fiddle=12785c3d5abbca97be5d44dd45a6da4a
Update : Below query addresses the update output format of the question.
With cte as
(select T.restaurant_id, array_to_string(array_agg(food_id), ',') as food_list
from
(select *
from Identifier t1
order by restaurant_id, food_id) T
group by T.restaurant_id)
select
--concat(r1.name,',',r2.name) as resturant_names,
t1.restaurant_id as restaurant_id,
r1.name as restaurant--,
--t2.restaurant_id as restaurant_id2,
--r2.name as restaurant_2,
--t1.food_list as common_food_ids
from cte t1
join cte t2
on t1.restaurant_id = t2.restaurant_id
and t1.food_list = t2.food_list
left join Restaurants r1
on t1.restaurant_id = r1.restaurant_id
left join Restaurants r2
on t2.restaurant_id = r2.restaurant_id;

As I understand your question, you want all restaurants that have the same list of foods as restaurant 1.
If so, that's a relation division problem. Here is an approach using joins and aggregation:
select r.name
from identifier i1
inner join identifier i2 on i2.food_id = i1.food_id
inner join restaurant r on r.restaurant_id = i2.restaurant_id
where i1.restaurant_id = 1
group by r.restaurant_id
having count(*) = (select count(*) from identifier i3 where i3.restaurant_id = 1)

Related

How to create cases for multiple purposes and just showing the count of unique IDs for multiple cases

I wrote a CTE which helps determine a flag based off a certain client's ID and what kind of client they are. I am looking to test the counts of the Flags, and the counts are totaling out amazingly! However, I am looking to add additional columns to the very last section of my code to show the amount of IDs who have belonged in all 3 cases, or both PPP and R, PPP and RR, RR and PPP, or RR and R. Is there a way I could do this? I know SUM won't work. I'm thinking using CASE or an IF, however I am a novice to SQL and am unsure what to do.
WITH ids AS (
SELECT DISTINCT LOWER(r.entry_id) AS ID
FROM id_user AS r
UNION
SELECT DISTINCT LOWER(identifiervalue) AS ID
FROM account AS a
)
,PPP as (
SELECT DISTINCT
LOWER(accountid) as "ID"
FROM ppp
WHERE (date >= '2022-11-21')
)
,R as (
SELECT DISTINCT
LOWER(account_id) as "ID"
FROM user
)
, RR as (
SELECT DISTINCT
LOWER(id) AS "ID"
FROM program_member
)
, Joint as (
SELECT
r.ID
,CASE WHEN p.ID IS NULL THEN 0 ELSE 1 END AS "PPP Flag"
,CASE WHEN r.ID IS NULL THEN 0 ELSE 1 END AS "R Flag"
,CASE WHEN rr.raid IS NULL THEN 0 ELSE 1 END AS "RR Flag"
FROM ids i
LEFT JOIN PPP ppp ON i.RAID = ppp.RAID
LEFT JOIN R r ON i.RAID = r.RAID
LEFT JOIN RR rr on i.RAID = rr.RAID
----TESTING COUNTS
SELECT
COUNT(ID) AS "ID Count"
,sum("PPP Flag") AS "PPP Users"
,sum("R") AS "R Accounts"
,sum("RR Flag") AS "RR Users"
FROM Joint
if all you are trying to do is get distinct counts, and how many are within each of those given flag categories, dont try to repeat then join. They are either in the PPP, User or Program_Member table. Just get that
select
UnionCnt.IDAndRaidCnt,
P.PPPCnt,
R.RCnt,
RR.RRCnt
from
-- this outer query returns only 1 record
( select
count(*) IDAndRaidCnt
from
-- this inner query returns all distinct based on the UNION result
(SELECT DISTINCT
LOWER(r.entry_id) ID
FROM
id_user r
UNION
SELECT DISTINCT
LOWER(identifiervalue) ID
FROM
account a)
) UnionCnt
JOIN
(SELECT
count( DISTINCT LOWER(accountid)) PPPCnt
FROM
ppp
WHERE
date >= '2022-11-21' ) P
-- above will only return a single record anyhow
on 1=1
JOIN
(SELECT
count( DISTINCT LOWER(account_id) RCnt
FROM
user ) R
-- also returns single row with total qualifying distinct ID count
on 1=1
JOIN
(SELECT
count( DISTINCT LOWER(id)) RRCnt
FROM
program_member ) RR
-- same here, single record
on 1=1

How to get all rows from one table which have all relations?

I have 3 tables:
companies (id, name)
union_products (id, name)
products (id, company_id, union_product_id, price_per_one_product)
I need to get all companies which have products with union_product_id in (1,2) and total price of products (per company) is less than 100.
What I am trying to do now:
select * from "companies" where exists
(
select id from "products"
where "companies"."id" = "products"."company_id"
and "union_product_id" in (1, 2)
group by id
having COUNT(distinct union_product_id) = 2 AND SUM(price_per_one_product) < 100
)
The problem I stuck with is that I'm getting 0 rows from the query above, but it works if I'll change COUNT(distinct union_product_id) = 2 to 1.
DB fiddle: https://www.db-fiddle.com/f/iRjfzJe2MTmnwEcDXuJoxn/0
Try to join the three tables as the following:
SELECT C.id, C.name FROM
products P JOIN union_products U
ON P.union_product_id=U.id
JOIN companies C
ON P.company_id=C.id
WHERE P.union_product_id IN (1, 2)
GROUP BY C.id, C.name
HAVING COUNT(DISTINCT P.union_product_id) = 2 AND
SUM(P.price_for_one_product) < 100
ORDER BY C.id
See a demo.
SELECT c.name FROM "companies" c
JOIN "products" p ON c.id = p.company_id
WHERE union_product_id IN (1, 2) AND price_for_one_product < 100
GROUP BY c.name
HAVING COUNT(DISTINCT p.name) =2
This would provide you all the company(s) name(s) which has provides both union_product_id 1 and 2 and the price_for_one_product/ price_per_one_product is less than 100.
Note: You might need to change price_for_one_product with price_per_one_product, as in question you have used price_per_one_product but db-fiddle link table defination has used price_for_one_product.

SQL Query: Count "id" occurrences in two tables

I have these 3 tables and I am trying to count, how many "hints" and "quizzes" are there for specific town id.
db_town
id
town
1
New York
db_hint
id
town_id
hint
1
1
test
db_quiz
id
town_id
quiz
1
1
quiz 1
2
1
quiz 2
I am using this statement, but it does not work :(
SELECT count(q.id),count(h.id) FROM `db_town` t LEFT JOIN `db_quiz` q ON t.id = q.town_id LEFT JOIN `db_hint` h ON t.id = h.town_id WHERE t.id = 1 GROUP BY t.id
and it produces this result:
count(q.id)
count(h.id)
2
2
Do I need to use two statements? Or is it possible to query it in a single SQL statement? I am using MariaDB.
You can use union all and aggregation:
select town_id, sum(is_hint), sum(is_quiz)
from ((select town_id, 1 as is_hint, 0 as is_quiz
from hints
) union all
(select town_id, 0, 1
from quizzes
)
) t
group by town_id;
Alternatively, you can use correlated subqueries:
select t.*,
(select count(*) from hints h where h.town_id = t.id),
(select count(*) from quizzes q where q.town_id = t.id)
from towns t;
Two things to look out for:
JOINs are likely to multiply rows and throw off the counts.
Getting 0 values if a town has no hints or quizzes.
You can use COUNT (DISTINCT) if both the hint id and the quiz id are unique.
SELECT
count(distinct q.id),count(distinct h.id)
FROM `db_town` t
LEFT JOIN `db_quiz` q ON t.id = q.town_id
LEFT JOIN `db_hint` h ON t.id = h.town_id
WHERE t.id = 1 GROUP BY t.id

How to group results by count of relationships

Given tables, Profiles, and Memberships where a profile has many memberships, how do I query profiles based on the number of memberships?
For example I want to get the number of profiles with 2 memberships. I can get the number of profiles for each membership with:
SELECT "memberships"."profile_id", COUNT("profiles"."id") AS "membership_count"
FROM "profiles"
INNER JOIN "memberships" on "profiles"."id" = "memberships"."profile_id"
GROUP BY "memberships"."profile_id"
That returns results like
profile_id | membership_count
_____________________________
1 2
2 5
3 2
...
But how do I group and sum the counts to get the query to return results like:
n | profiles_with_n_memberships
_____________________________
1 36
2 28
3 29
...
Or even just a query for a single value of n that would return
profiles_with_2_memberships
___________________________
28
I don't have your sample data, but I just recreated the scenario here with a single table : Demo
You could LEFT JOIN the counts with generate_series() and get zeroes for missing count of n memberships. If you don't want zeros, just use the second query.
Query1
WITH c
AS (
SELECT profile_id
,count(*) ct
FROM Table1
GROUP BY profile_id
)
,m
AS (
SELECT MAX(ct) AS max_ct
FROM c
)
SELECT n
,COUNT(c.profile_id)
FROM m
CROSS JOIN generate_series(1, m.max_ct) AS i(n)
LEFT JOIN c ON c.ct = i.n
GROUP BY n
ORDER BY n;
Query2
WITH c
AS (
SELECT profile_id
,count(*) ct
FROM Table1
GROUP BY profile_id
)
SELECT ct
,COUNT(*)
FROM c
GROUP BY ct
ORDER BY ct;

Help with query

I'm trying to make a query that looks at a single table to see if a student is in a team called CMHT and in a medic team - if they are I don't want to see the result.
I only want see the record if they're only in CMHT or medic, not both.
Would the right direction be using sub query to filter it out? I've done a search on NOT IN but how could you get to see check if its in more then 2 teams are not?
Student Team ref
1 CMHT 1
1 Medic 2
2 Medic 3 this would be in the result
3 CMHT 5 this would be in the result
So far I've done the following code would I need use a sub query or do a self join and filter it that way?
SELECT Table1.Student, Table1.Team, Table1.refnumber
FROM Table1
WHERE (((Table1.Team) In ('Medics','CMHT'))
This is Mark Byers's answer with a HAVING clause instead of a subquery:
SELECT Student, Team, ref
FROM Table1
GROUP BY Student
HAVING COUNT(Student) = 1
SELECT *
FROM students
WHERE NOT EXISTS
(
SELECT NULL
FROM students si
WHERE si.student = s.student
AND si.team = 'CMHT'
)
OR NOT EXISTS
(
SELECT NULL
FROM students si
WHERE si.student = s.student
AND si.team = 'Medic'
)
SELECT a.*
FROM Table1 a
INNER JOIN
( SELECT Student, COUNT(*) FROM Table1
GROUP BY Student
HAVING COUNT(*) = 1)b
ON (a.Student = b.Student)
how could you get to see check if its in 2 or more teams?
You can count the number of teams per student and then filter only those you want to see:
SELECT student FROM
(
SELECT student, COUNT(*) AS cnt
FROM Table1
GROUP BY student
) T1
WHERE cnt = 1
You can do it with outer join
select COALESCE(t1.Student, t2.Student) as Student,
COALESCE(t1.Team, t2.Team) as Team,
COALESCE(t1.ref, t2.ref) as ref
from
(select * from Student where Team = 'CMHT') t1
outer join
(select * from Student where Team = 'Medic') t2
on t1.Student = t2.Student
where
t1.Student is null or
t2.Student is null;