The sql tables are as follows: likes with columns drinker and beer, sells with columns bar and beer and frequents with columns drinker and bar.
I have previously answered the following statement:
"Drinkers who frequent bars which serve some beer they like"
with the SQL Query:
SELECT DISTINCT y.drinker
FROM likes a, sells, frequents y
WHERE a.beer = sells.beer
AND sells.bar = y.bar
AND y.drinker = a.drinker;
Now, I am trying to modify the query listed above to answer a similar, yet different statement:
"Drinkers who only frequent bars which serve beers they like"
The modification, logically, is to include only those who frequent bars that serves beers they like and to eliminate those that visit any bar that doesn't serve a beer they like.
I am having the hardest time modifying the above query to satisfy the second statement.
My thought process was to perhaps do a double negation, such as: Get the list of drinkers who do not exist in the following group: drinkers who do not frequent bars that don't sell beers they like. But the correct implementation is escaping me.
Here's is my best attempt at solving this query, but I am well aware it is not correct, for this query still returns drinkers who frequent some bars (not only) that serves beer they like:
SELECT distinct x.drinker
FROM frequents x
WHEREexists (SELECT*
FROM frequents y, sells z, likes u
WHERE x.drinker=y.drinker
AND y.bar = z.bar and z.beer = u.beer
AND y.drinker = u.drinker);
Any help would be great, thank you for any insight you can provide.
Drinkers who frequently exactly the number of bars as those they frequent which sell their favorites:
select drinker
from frequents
group by drinker
having count(bar) = (
select count(distinct f.bar)
from
sells s
inner join likes l on l.beer = s.beer
inner join frequents f on f.bar = s.bar and f.drinker = l.drinker
where f.drinker = frequents.drinker
)
The inner count will be lower when you can't match up all three of the relationship triangle. (Notice how each of the tests for beer, bar, and drink each appear once in the inner query.) If you wanted to look for the opposite set of people you'd just change the equality test to greater than.
EDIT:
I was thinking about alternate ways of approaching this. Interestingly this is one where you might find a right outer join to be useful (rather than the parentheses around the joined tables.)
The frequents
table is still the focus of the query below and the logic is driven by the idea of finding out which frequented bars are also bars where a like/favorite is available for sale. Because of the outer join the "frequents" side will never be null but the "likes-sells" side will have nulls only when a pair can't be matched for that frequent. The final test in the having
clause is redundant but I wanted to show the symmetry in being able to match the counts on both the columns of `frequents' even though only one is strictly necessary.
select f.drinker
from
frequents f left outer join (
likes l inner join sells s on s.beer = l.beer
) on f.drinker = l.drinker and f.bar = s.bar
group by f.drinker
having count(f.drinker) = count(l.drinker) and count(f.bar) = count(s.bar)
I think this is a valid solution...
The subquery is used to filter out drinkers who frequent a bar that has a 0 count of beers they like.
select distinct drinker
from frequents
where drinker not in (
select f.drinker
from frequents f
join sells s on f.bar = s.bar
left join likes l on l.drinker = f.drinker and l.beer = s.beer
group by f.drinker, f.bar
having count(l.drinker) = 0
);
Sample SQL Fiddle
I built a little fiddle for you and came up with what you already suggested: a query that is based on a negative selection of drinkers that go to places where their beer is NOT served:
SELECT DISTINCT fdrinker FROM frequents f WHERE NOT EXISTS (
SELECT 1 FROM frequents WHERE NOT EXISTS
(SELECT 1 FROM sells WHERE sbar=fbar AND
sbeer IN (SELECT lbeer FROM likes WHERE ldrinker=fdrinker) )
AND fdrinker=f.fdrinker )
The innermost sub-select lists all the "liked" drinks of a particular drinker. The next outer sub-select level will list this bar from the sells
table if it sells at least one liked beer. The next level up then checks the frequented bars against on the existance of any bars that "fail to deliver". And if no such "failure" occurs the top level select will present the drinkers that will only frequent bars where at least one of their preferred beers is served.
E.g.:
SELECT a.drinker
FROM likes a
LEFT
JOIN ( SELECT DISTINCT f.drinker
FROM frequents f
LEFT
JOIN sells s
ON s.bar = f.bar
JOIN likes l
ON l.drinker = f.drinker
LEFT
JOIN sells x
ON x.bar = f.bar
AND x.beer = l.beer
WHERE x.beer IS NULL
) b
ON b.drinker = a.drinker
WHERE b.drinker IS NULL;