Question

I am having trouble figuring out a way around Oracle's lack of support for the HAVING EVERY clause.

I have two tables, Production and Movie, with the following schema:

Production (pid, mid)
Movie(mid, director)

where 'pid' is in integer representing publisher ID, 'mid' is an integer representing movie ID, and director is the name of the movie's director.

My goal is to get a list of publishers (by ID) which have only published movies directed by Peter Jackson or Ben Affleck.

In order to achieve this, I had written the following query:

SELECT *
    FROM Production P, Movie M
    WHERE P.mid = M.mid;
    GROUP BY P.pid
    HAVING EVERY ( M.director IN ('Ben Affleck', 'Peter Jackson') );

But since Oracle doesn't support HAVING EVERY, all I get is the following error:

    HAVING EVERY ( M.director IN ('ben affleck', 'PJ') )
                          *
ERROR at line 5:
ORA-00907: missing right parenthesis

Because the directorship has to apply to every movie produced by the publisher, I don't believe the condition can be moved to the WHERE clause.

Is there any way around this roadblock? Anything that's considered "standard"? Also (and perhaps more importantly) why did Oracle choose not to implement HAVING EVERY?

Was it helpful?

Solution

Try this:

SELECT P.pid
FROM (select distinct Pi.pid, M.Director
      from Production Pi INNER JOIN 
    Movie M ON Pi.mid = M.mid) P
GROUP BY P.pid
HAVING sum(case when P.Director in ('Ben Affleck', 'Peter Jackson') 
           then 1 else 99 end) = 2

Here is a sqlfiddle demo

OTHER TIPS

After thinking about it for a while, I've come up with something that is perhaps a little more readable than what A.B.Cade came up with:

select distinct P.pid
    from Production P
    where P.pid not in (
        -- Get publishers that have produced a movie directed by someone else
        select P1.pid
        from Production P1 INNER JOIN Movie M ON P1.mid = M.mid
        where M.director not in ('Ben Affleck', 'Peter Jackson')
    )

SQLFiddle demo

The difference is that, rather than looking for producers with only the desired directors, we identify all the producers linked to other directors and then omit them.

Based on Dan's own answer, but I've removed the correlated subquery, as it would likely perform very poorly on large datasets:

SELECT DISTINCT P.pid
FROM Production P
LEFT JOIN (
    SELECT P1.pid
    FROM Production P1
    INNER JOIN Movie M ON (P1.mid = M.mid)
    WHERE M.director NOT IN ('Ben Affleck', 'Peter Jackson')
) V ON (P.pid = V.pid)
WHERE v.pid IS NULL;

SQL Fiddle demo

To avoid the magic number 99:

SELECT 
    P.pid
FROM 
(
    SELECT DISTINCT 
        Pi.pid, M.Director
    FROM Production Pi 
    JOIN Movie M ON Pi.mid = M.mid
) P
GROUP BY 
    P.pid
HAVING 
    COUNT(p.Director) = 2 -- The directors should be exactly 2
    AND MIN(CASE WHEN p.Director in ('Ben Affleck', 'Peter Jackson') 
            THEN 1 ELSE 0 END) = 1

Other approaches: http://www.anicehumble.com/2019/04/not-every-rdbms-has-every.html

Note that the double not in approach can't be used. As it will still report the publishers that has one director only that matches Ben Affleck or Peter Jackson.

with production as
(
   select *
   from (values 
      ('DC', 'Batman', 'Ben Affleck'),
      ('DC', 'Robin', 'Peter Jackson'),
      ('Not DC', 'Not Batman', 'Not Ben Affleck'),
      ('Not DC', 'Not Robin', 'Not Peter Jackson'),         
      ('Marvel', 'Avengers', 'Joe Russo'),
      ('WingNut', 'King Kong', 'Peter Jackson'),
      ('Century Fox', 'Deadpool', 'Ben Affleck'),
      ('Century Fox', 'Fantastic 4', 'Peter Jackson'),
      ('Century Fox', 'X-Men', 'Peter Jackson'),
      ('Millenium Fox', 'Scorpion', 'Ben Affleck'),
      ('Millenium Fox', 'Sub-Zero', 'Peter Jackson'),
      ('Millenium Fox', 'Liu Kang', 'Ed Boon')          
   ) as x(publisher, movie, director)
)
select distinct P.publisher
from production P
where P.publisher not in (
      -- Get publishers that have produced a movie directed by someone else
      select P1.publisher
      from production P1
      where P1.director not in ('Ben Affleck', 'Peter Jackson')
    )
;

Wrong output. WingNut should not be included, as it don't have both Ben Affleck and Peter Jackson as directors.

| publisher   |
| ----------- |
| WingNut     |
| Century Fox |
| DC          |

Here's the correct query, every is simulated using min(when true then 1 else 0) = 1

with production as
(
   select *
   from (values 
      ('DC', 'Batman', 'Ben Affleck'),
      ('DC', 'Robin', 'Peter Jackson'),
      ('Not DC', 'Not Batman', 'Not Ben Affleck'),
      ('Not DC', 'Not Robin', 'Not Peter Jackson'),         
      ('Marvel', 'Avengers', 'Joe Russo'),
      ('WingNut', 'King Kong', 'Peter Jackson'),
      ('Century Fox', 'Deadpool', 'Ben Affleck'),
      ('Century Fox', 'Fantastic 4', 'Peter Jackson'),
      ('Century Fox', 'X-Men', 'Peter Jackson'),
      ('Millenium Fox', 'Scorpion', 'Ben Affleck'),
      ('Millenium Fox', 'Sub-Zero', 'Peter Jackson'),
      ('Millenium Fox', 'Liu Kang', 'Ed Boon')          
   ) as x(publisher, movie, director)
)
select P.publisher
from (select distinct publisher, director from production) P
group by
    P.publisher
having
     count(p.Director) = 2 -- The directors should be exactly 2

     and min(case when p.Director in ('Ben Affleck', 'Peter Jackson') 
             then 1 else 0 end) = 1
;

Correct Output, should show DC and Century Fox only. As they are the only publishers that employs both Ben Affleck and Peter Jackson exclusively.

| publisher   |
| ----------- |
| Century Fox |
| DC          |

Live test: https://www.db-fiddle.com/f/aDDw4Pd1DJzs6J5HgbKbdh/4

Using conditional aggregation with COUNT:

SELECT Pi.pid
FROM Production Pi
JOIN Movie M ON Pi.mid = M.mid
GROUP BY Pi.pid
HAVING COUNT(DISTINCT CASE WHEN M.Director IN ('Ben Affleck','Peter Jackson') 
                           THEN M.Director END) = 2;
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top