SQL Beginner Query Logic With 3 Tables

2019-08-11 05:13发布

问题:

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.

回答1:

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)


回答2:

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



回答3:

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.



回答4:

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;