Reputation: 55
I have two tables that are joined by an association table. The requirement is to find only records in price table that include all of the menu codes in a list and only those in the list. The data (simplified) looks like this
price Table
price_id | site
----------------
1001 | 150
1002 | 151
1003 | 152
1004 | 153
menu Table
menu_id | code
------------------
5001 | 10000
5002 | 10001
5003 | 10002
5004 | 10003
price_menu Table
price_id | menu_id
------------------
1001 | 5002
1002 | 5002
1002 | 5003
1003 | 5002
1003 | 5003
1003 | 5004
Joining price and menu through price_menu, you would get
price joined with price_menu joined with menu
price_id | site | menu_id | code
------------------------------------
1001 | 150 | 5002 | 10001
1002 | 151 | 5002 | 10001
1002 | 151 | 5003 | 10002
1003 | 152 | 5002 | 10001
1003 | 153 | 5003 | 10002
1003 | 153 | 5004 | 10003
So, if I wanted to find prices that are associated with menu.code=10001 and menu.code=10002, and only those codes, I should get the record with price_id=1002 only. The record with price_id=1001 has only one of the two menu.codes and the record with price_id=1003 has and addition menu. Therefore, only record(s) for price_id=1002 should be returned.
I came up with this query. However, the problem is that there could be many menus associated with a price and it would get too large and probably slow as every menu you add to the query you need another subquery.
SELECT * FROM (SELECT *,
(SELECT code AS mycode FROM price AS pc1
INNER JOIN price_menu AS pm1 ON pc1.id=pm1.price_id
JOIN menu AS m1 ON pm1.menu_id=m1.id
WHERE pc1.id=pc.id
AND m1.code = '1571403743330'
) AS data1,
(SELECT code AS mycode FROM price AS pc2
INNER JOIN price_menu AS pm2 ON pc.id=pm2.price_id
JOIN menu AS m2 ON pm2.menu_id=m2.id
WHERE pc2.id=pc.id
AND m2.code = '1571403754395'
) AS data2
FROM price AS pc ) AS outerquery
WHERE data1 = '1571403743330'
AND data2 = '1571403754395'
This will produce one row per price found. However, there is no requirement that it be a single row. Can anyone suggest an alternative that would not require an additional subquery per menu?
Upvotes: 0
Views: 1286
Reputation: 1269603
I would approach this using aggregation and having:
select pm.price_id
from menu m join
price_menu pm
on m.menu_id = pm.menu_id
group by pm.price_id
having count(distinct pm.code) = 2 and
count(distinct case when pm.code in (1001, 1002) then pm.code end)) = 2; -- number of things you are looking for
Note: If there are no duplicate codes for a given price id, then use count()
rather than count(distinct)
.
In Postgres, you can use arrays for this information:
select pm.price_id
from menu m join
price_menu pm
on m.menu_id = pm.menu_id
group by pm.price_id
having count(distinct pm.code) = array_length(array[1, 2]) and
count(distinct case when pm.code = any(array[1, 2]) then pm.code end)) = array_length(array[1, 2]);
This makes it easier to pass the array in as a parameter.
Upvotes: 0
Reputation: 147146
Using your simplified example, you could achieve what you want with this query, which checks that the number of menu codes for a price (COUNT(m.code)
) is 2 and that they are 10001
and 10002
:
SELECT p.price_id, p.site
FROM price p
JOIN price_menu pm ON pm.price_id = p.price_id
JOIN menu m ON m.menu_id = pm.menu_id
GROUP BY p.price_id, p.site
HAVING SUM(CASE WHEN m.code IN (10001, 10002) THEN 1 END) = COUNT(m.code)
AND COUNT(m.code) = 2
Output:
price_id site
1002 151
Upvotes: 1