Postgres supports the SQL Standard's every.
The query below shows the movie(s) that their genre is exactly Fantasy + Mystery. It won't show the movie if it is Fantasy + Mystery + Drama for example.
Here are the various ways every can be emulated in other RDBMSes.
select m.id, m.title, m.year from movie m join movie_genre mg on m.id = mg.movie_id group by m.id having count(mg.genre) = 2 -- making sure that the movie has exactly two genres -- SQL Standard. Available in Postgres and every(mg.genre in ('Fantasy','Mystery')) -- Specific to Postgres and bool_and(mg.genre in ('Fantasy','Mystery')) -- MySQL can simulate every/bool_and by using BIT_AND. -- Since MySQL's boolean behaves same as C's int, there's no need to explicitly cast the boolean expression to int. -- Hence MySQL don't need to compare bit_and's result to 1 too. -- Following is how it is done in MySQL: -- and bit_and(mg.genre in ('Fantasy','Mystery')) -- Postgres has bit_and, but it's solely for bit manipulation. -- No Postgres users would use bit_and for every/bool_and functionality, Postgres already has very English-like every/bool_and. -- If a user is intent to use bit_and instead of every/bool_and, the user has to cast the boolean expression to integer using ::int. -- And also, bit_and need to be compared with a result: and bit_and((mg.genre in ('Fantasy','Mystery'))::int) = 1 -- Other RDBMSes and count(case when mg.genre in ('Fantasy','Mystery') then mg.genre end) = count(mg.genre) and min(case when mg.genre in ('Fantasy','Mystery') then 1 else 0 end) = 1 and sum(case when mg.genre in ('Fantasy','Mystery') then 0 else 1 end) = 0
Output:
| id | title | year | | --- | ---------------------------------------- | ---- | | 125 | Harry Potter and the Prisoner of Azkaban | 2004 |
DDL:
CREATE TABLE movie ( id INTEGER primary key, title VARCHAR(42), year INTEGER ); INSERT INTO movie (id, title, year) VALUES ('308', 'Coraline', '2009'), ('125', 'Harry Potter and the Prisoner of Azkaban', '2004'), ('204', 'Hugo', '2011'), ('42', 'Hitchiker''s guide to galaxy', '2011'), ('168', 'Lucky', '2011'), ('88', 'Nice', 1969); CREATE TABLE movie_genre ( movie_id INTEGER, genre VARCHAR(8) ); INSERT INTO movie_genre (movie_id, genre) VALUES ('308', 'Fantasy'), ('308', 'Thriller'), ('125', 'Fantasy'), ('125', 'Mystery'), ('204', 'Fantasy'), ('204', 'Mystery'), ('204', 'Drama'), ('308', 'Fantasy'), ('168', 'Fantasy'), ('88', 'Mystery'), ('88', 'Tour');
Postgres-specific
Live test: https://www.db-fiddle.com/f/j1y7WCH2tRcWoE6QMbfRxC/2
select m.id, m.title, m.year from movie m join movie_genre mg on m.id = mg.movie_id group by m.id having count(mg.genre) = 2 -- making sure that the movie has exactly two genres and every(mg.genre in ('Fantasy','Mystery'))
Output:
| id | title | year | | --- | ---------------------------------------- | ---- | | 125 | Harry Potter and the Prisoner of Azkaban | 2004 |
To avoid explicit count (Postgres-specific)
Live test: https://www.db-fiddle.com/f/j1y7WCH2tRcWoE6QMbfRxC/7
select m.id, m.title, m.year from movie m join movie_genre mg on m.id = mg.movie_id group by m.id having array_agg(mg.genre order by mg.genre) = array['Fantasy', 'Mystery']
Output:
| id | title | year | | --- | ---------------------------------------- | ---- | | 125 | Harry Potter and the Prisoner of Azkaban | 2004 |
Note that we need to use order by mg.genre as array comparison is order-dependent. Without order by mg.genre, Harry Potter will not be shown.
Another good read on every: https://blog.jooq.org/2014/12/18/a-true-sql-gem-you-didnt-know-yet-the-every-aggregate-function/
No comments:
Post a Comment