一つ前のSQLに関する質問です。リンク内容
以前の質問で以下のSQL文を回答としていただきました。
SQL
1select hotel_id from hotels a where not exists( 2select 1 from tags c where c.tag in ('朝食付き','キャンセル無料') and not exists( 3select 1 from hotels_tags b where c.tags_id = b.tags_id and b.hotel_id = a.hotel_id));
このSQLの挙動が理解できずに困っております。分解するために以下のSQLを実行しました。
SQL
1 select * from hotels a where exists( 2 select 1 from tags c where c.tag in ('朝食付き','キャンセル無料'));
質問❶
しかしtagsテーブルにはhotelテーブルと紐付けできるものがないのでこれだと、inの中にtagsに存在するタグ名(WiFi無料、プール、朝食付きなど)を記入すればtrueとなり全てを取得するということでしょうか。
SQL
1/*NOTを追加*/ 2 select * from hotels a where not exists( 3 select 1 from tags c where c.tag in ('朝食付き','キャンセル無料'));
質問❷
こちらのSQLを実行すると結果は0件になります。in ('朝食付き','キャンセル無料'))でないもの。なのでその他のタグが相当し、
hotelが全件ヒットするかと思いました。なぜ0件になるのでしょうか。
正直
not exists(
select 1 from hotels_tags b where c.tags_id = b.tags_id and b.hotel_id = a.hotel_id));
の部分も理解できていませんが、まずは質問❶と質問❷についてご回答願います。
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。

回答3件
0
ベストアンサー
結論 (長すぎて追記できなくなったため)2018-03-02
not exists を二度使うのはレコードがすべて揃っている場合を検出するため
すべて揃っているのが縦に並ぶレコード(行)だからやりにくいのです。SQLで複数の列の値がすべて not null という条件を調べるのがどれだけ簡単か。また、このために exist を使うなら少なくとも一つレコードがあれば true になり、すべて揃っている場合に含めて余計な場合も検出する。これでは駄目です。
hotel_id | ホテル | タグ |
---|---|---|
1 | 「クラウン リージェンシー ホテル & タワーズ」 | 「朝食付き」<br>「キャンセル無料」 |
5 | 「レッド プラネット セブ」 | <br>「キャンセル無料」 |
8 | 「パーム グラス ホテル」 | <br> |
前回の質問の回答で、すべて揃っている場合の検出に count(*)=2 を使いましたが、検索条件によってはタグの種類と数が異なり 2 とは限りません。タグの種類が n 種類のとき、あるなしのパターンの数は 2 ** n 個。 n が異なっても、すべてのレコードが揃う場合(一種類だけ)を検出できるのは、not existsを二度使う方法 (二重否定/補集合の補集合) です。 not exists を二度使う方法を初めに考えたひとは尊敬に値すると思いませんか。
関係代数の積、商
この質問のケースでは次の演算を想定してSQLを組みたてています。
積: hotels × tags = hotels_tags
商: hotels_tags ÷ tags = hotels
商の意味は資料を読みましょう。商を求めるSQLは一種類ではありません。
投稿2019/03/02 10:09

退会済みユーザー
総合スコア0
0
exists/not exists の意味
exists/not exists で条件判定を行います。以下の例(select-1、select-2、select-3、select-4)を使って説明します。
SQL
1select-1 where exists (select-2) 2select-3 where not exists (select-4)
SQLをifを使って書き換えます。この構文は存在しませんがイメージしやすいでしょう。
exists
SQL
1if exists(select-2の結果、レコードが1件以上存在する(true)) 2 select-1を実行し結果を得る 3else 4 空のテーブル
not exists
SQL
1if not exists(select-4の結果、レコードが1件も存在しない(true)) 2 select-3を実行し結果を得る 3else 4 空のテーブル
回答
質問1
SQLを書き換えると以下のようになり 、条件がtrue(レコードが存在する)なので、hotelsテーブルが選択される。
SQL
1if exists(select 1 from tags c where c.tag in ('朝食付き','キャンセル無料')) 2 select * from hotels a 3else 4 空のテーブル
質問2
SQLを書き換えると以下のようになり 、条件がfalse(レコードが存在する)なので、空のテーブルが選択される。
SQL
1if not exists(select 1 from tags c where c.tag in ('朝食付き','キャンセル無料')) 2 select * from hotels a 3else 4 空のテーブル
上の例は条件判定が1度しか行われませんが、以下のように相関副問い合わせがある場合は、レコード1件ごとに条件判定が行われます。
not exists(
select 1 from hotels_tags b where c.tags_id = b.tags_id and b.hotel_id = a.hotel_id));
説明資料
SQL全体でやりたいのは、Coddの関係演算(代数)の「商」です。まず資料を読んで考えてください。「商」は「積」の反対演算です。hotels_tags ÷ tgasをやりたいのです。not existsを2回使う意味は、補集合の補集合二重否定をとるためですが、今はわからなくてよいです。
相関副問合せをもつ exists / not exists (ここから、2018-03-02)
くどいかもしれませんが、動作を知りたいという要望に応えます。このあと、まとめとして、関係演算の積、商の解説、さらに、すべてを検索するのになぜexistsを使用するかを解説する予定ですが、そちらで、これについて別の質問をたてたほうがよいかも。(疲れたので休憩します)
相関副問合せにおける exists/not exist の動作
exists/not existの動作を if 構文に直して考えます。外側の検索で読まれたレコード1件ずつを判定の対象とします。
SQL
1<外側の検索で読まれたレコード> -- <-- 1件が判定の対象 2if exists|not exists(<外側の検索で読まれたレコード>の項目の値を使うselectの結果) 3 <外側の検索で読まれたレコード>を採用する -- <-- 上の exists/ not existsがtrueの場合 4else 5 <外側の検索で読まれたレコード>を採用しない
SQL動作確認:(関係演算の除算)ホテルとタグの組がすべて存在するホテルを検索
explain を使ってSQLを調べます。(JSON 形式の出力を行う exlain format=json <SQL> ... もあります。こちらは詳細が表示されます)
SQL
1> explain select hotel_id from hotels a where not exists( 2 -> select 1 from tags c where c.tag in ('朝食付き','キャンセル無料') and not exists( 3 -> select 1 from hotels_tags b where c.tags_id = b.tags_id and b.hotel_id = a.hotel_id)); 4+------+--------------------+-------+----------------+---------------+----------+---------+------+------+--------------------------+ 5| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | 6+------+--------------------+-------+----------------+---------------+----------+---------+------+------+--------------------------+ 7| 1 | PRIMARY | a | index | NULL | hotel_id | 6 | NULL | 12 | Using where; Using index | 8| 2 | DEPENDENT SUBQUERY | c | range | tag | tag | 302 | NULL | 2 | Using where; Using index | 9| 3 | DEPENDENT SUBQUERY | b | index_subquery | hotel_id | hotel_id | 4 | func | 8 | Using index; Using where | 10+------+--------------------+-------+----------------+---------------+----------+---------+------+------+--------------------------+ 113 rows in set (0.001 sec)
explainからわかるのは select の入れ子です。select_type の意味は次のとおり。
- PRIMARY 主検索
- DEPENDENCY SUBQUERY 外側の検索結果を使用する検索(外側の検索が先に実行される)
上の検索 id.1,id.2,id.3 はすべて「選択」であり「結合」はありません。依存関係は、id.1 <- id.2 <- id.3。以下の疑似コードで動作を示します。
pseudo
1hotels a を全件検索 <-- id.1 2 hotelsのレコードごとに以下を実行 3 tags c のレコード'朝食付き','キャンセル無料'を検索する。 <-- id.2 4 各レコード(2,'朝食付き'),(3,'キャンセル無料')のそれぞれに以下を実行する 5 hotels_tags b のレコードを検索する。(<ホテルid>,<タグid=2>)と(<ホテルid>,<タグid=3>)の2回 <-- id.3
ここまでで、SQLの動作の概要を理解してください。
SQL: ホテルごとの動作の確認
ホテルと検索対象のタグの存在有無の組み合わせを調べます。検索で選択されるホテルは、すべてのタグを持つ「クラウン リージェンシー ホテル & タワーズ」だけです。これらの3つのホテルを確認用データとして使います。
hotel_id | ホテル | 「朝食付き」(tags_id:2) | 「キャンセル無料」(tags_id:3) |
---|---|---|---|
1 | クラウン リージェンシー ホテル & タワーズ | ○ | ○ |
5 | レッド プラネット セブ | ー | ○ |
8 | パーム グラス ホテル | ー | ー |
まず、うえのSQLの検索条件に hotel_id を追加、1ホテルごとの検索の和に変形します。
SQL
1select hotel_id from hotels a where hotel_id = 1 and not exists( ... 2union 3... 4union 5select hotel_id from hotels a where hotel_id = 5 and not exists( ... 6union 7... 8union 9select hotel_id from hotels a where hotel_id = 8 and not exists( ... 10union 11...
確認してください-1
上の個別SQLの hotel_id を、1, 5, 8 のそれぞれで実行して動作を確認してください。
SQL(id.2-1): 必要なタグを検索
タグの検索は全てのホテルで同じです。次の副問合せ(id.3)で使用する tags_id、2, 3 を取得します。すべてのホテルで使用するタグの定数レコードが二件。
SQL
1select c.tags_id from tags c where c.tag in ('朝食付き','キャンセル無料')
SQL(id.3): ホテル、タグの組の検索
SQL(id.2-1)の各定数( tags_id: 2, 3 )を使って以下の問い合わせを実行します。not existsが二回実行されます。
SQL
1not exists(select 1 from hotels_tags b where c.tags_id = b.tags_id and b.hotel_id = a.hotel_id)
確認してください-2
hotel_idが、1、 5、 8、tags_idが、2、3 の組み合わせで以下の動作を確認してください。MySQLの結果は、trueが 1、falseが 0 になります。
組み合わせは 6 とおりです。指定する(ホテル、タグ)の組が存在すれば false、存在しなければ trueになります。
SQL
1select not exists(select 1 from hotels_tags b where <タグid(2,3のいずれか)> = b.tags_id and b.hotel_id = <ホテルのid(1,5,8のいずれか)>);
SQL(id.2-2): ホテル、タグ検索(id.3)の結果をタグ検索にまとめる
各ホテルでtags_id 2,3 の検索を実行したので、UNIONを使って結果をまとめます。select 1 の部分は、わかりやすくするためつぎのように置き換えておきます。
- select '朝食付きなし' from ...
- select 'キャンセル無料なし' from ...
SQL
1select '朝食付きなし' from tags c where not exists(select 1 from hotels_tags b where 2 = b.tags_id and b.hotel_id = <ホテルのid>) 2union 3select 'キャンセル無料なし' from tags c where not exists(select 1 from hotels_tags b where 3 = b.tags_id and b.hotel_id = <ホテルのid>);
確認してください-3
hotel_idが、1、5、8 のそれぞれで、上のまとめ検索SQL(id.2-2)を実行してください。確認するのは次のことです。
- (ホテル、タグ)が存在しなければ、まとめ検索でレコードが存在する。
- (ホテル、タグ)が2つ揃っているなら、まとめ検索のレコードは存在しない。
SQL(id.1-2): ホテル、タグ検索の結果をタグ検索にまとめたものSQL(id.2-2)に not exists を適用する。
最後のホテルの判定処理は、ホテル、タグ検索の結果をタグ検索にまとめたものを使って、ホテルごとになされます。
SQL
1select hotel_id from hotels a where a.hotel_id = <ホテルのid> and not exists( 2select '朝食付きなし' from tags c where not exists(select 1 from hotels_tags b where 2 = b.tags_id and b.hotel_id = <ホテルのid>) 3union 4select 'キャンセル無料なし' from tags c where not exists(select 1 from hotels_tags b where 3 = b.tags_id and b.hotel_id = <ホテルのid>) 5);
確認してください-4
hotel_idが、1、5、8 のそれぞれで、上のまとめ検索を実行してください。確認するのは次のことです。
- (ホテル、タグ)がひとつでも存在しなければ、ホテルが選択されない。
- (ホテル、タグ)が2つ揃っているときに限りホテルが選択される。
以上が動作の詳細です。プログラマーはこの動作を無意識のうちに(直観的に)考えながらSQLを組み立てています。
確認してください sql 一覧
確認してください-1
SQL
1select hotel_id from hotels a where hotel_id = 1 and not exists( 2 select 1 from tags c where c.tag in ('朝食付き','キャンセル無料') and not exists( 3 select 1 from hotels_tags b where c.tags_id = b.tags_id and b.hotel_id = a.hotel_id)); 4select hotel_id from hotels a where hotel_id = 5 and not exists( 5 select 1 from tags c where c.tag in ('朝食付き','キャンセル無料') and not exists( 6 select 1 from hotels_tags b where c.tags_id = b.tags_id and b.hotel_id = a.hotel_id)); 7select hotel_id from hotels a where hotel_id = 8 and not exists( 8 select 1 from tags c where c.tag in ('朝食付き','キャンセル無料') and not exists( 9 select 1 from hotels_tags b where c.tags_id = b.tags_id and b.hotel_id = a.hotel_id));
確認してください-2
SQL
1select not exists(select 1 from hotels_tags b where 2 = b.tags_id and b.hotel_id = 1); 2select not exists(select 1 from hotels_tags b where 3 = b.tags_id and b.hotel_id = 1); 3select not exists(select 1 from hotels_tags b where 2 = b.tags_id and b.hotel_id = 5); 4select not exists(select 1 from hotels_tags b where 3 = b.tags_id and b.hotel_id = 5); 5select not exists(select 1 from hotels_tags b where 2 = b.tags_id and b.hotel_id = 8); 6select not exists(select 1 from hotels_tags b where 3 = b.tags_id and b.hotel_id = 8);
確認してください-3
SQL
1select '朝食付きなし' from tags c where not exists(select 1 from hotels_tags b where 2 = b.tags_id and b.hotel_id = 1) 2union 3select 'キャンセル無料なし' from tags c where not exists(select 1 from hotels_tags b where 3 = b.tags_id and b.hotel_id = 1); 4select '朝食付きなし' from tags c where not exists(select 1 from hotels_tags b where 2 = b.tags_id and b.hotel_id = 5) 5union 6select 'キャンセル無料なし' from tags c where not exists(select 1 from hotels_tags b where 3 = b.tags_id and b.hotel_id = 5); 7select '朝食付きなし' from tags c where not exists(select 1 from hotels_tags b where 2 = b.tags_id and b.hotel_id = 8) 8union 9select 'キャンセル無料なし' from tags c where not exists(select 1 from hotels_tags b where 3 = b.tags_id and b.hotel_id = 8);
確認してください-4
SQL
1select hotel_id from hotels a where a.hotel_id = 1 and not exists( 2select '朝食付きなし' from tags c where not exists(select 1 from hotels_tags b where 2 = b.tags_id and b.hotel_id = 1) 3union 4select 'キャンセル無料なし' from tags c where not exists(select 1 from hotels_tags b where 3 = b.tags_id and b.hotel_id = 1) 5); 6select hotel_id from hotels a where a.hotel_id = 5 and not exists( 7select '朝食付きなし' from tags c where not exists(select 1 from hotels_tags b where 2 = b.tags_id and b.hotel_id = 5) 8union 9select 'キャンセル無料なし' from tags c where not exists(select 1 from hotels_tags b where 3 = b.tags_id and b.hotel_id = 5) 10); 11select hotel_id from hotels a where a.hotel_id = 8 and not exists( 12select '朝食付きなし' from tags c where not exists(select 1 from hotels_tags b where 2 = b.tags_id and b.hotel_id = 8) 13union 14select 'キャンセル無料なし' from tags c where not exists(select 1 from hotels_tags b where 3 = b.tags_id and b.hotel_id = 8) 15);
投稿2019/03/01 12:57
編集2019/03/02 05:31
退会済みユーザー
総合スコア0
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。

退会済みユーザー
2019/03/01 15:14

退会済みユーザー
2019/03/01 16:17

退会済みユーザー
2019/03/01 16:55

退会済みユーザー
2019/03/02 05:54

退会済みユーザー
2019/03/02 10:13

退会済みユーザー
2019/03/02 19:24

0
解説
SQLは集合を扱う言語です。このSQLは集合理論で言うところの補集合を利用したものです。
NOTによって補集合を取得します。
「複数のtagを全て含まないもの」の補集合が「複数のtagを全て持つもの」となります。
「複数のtagを全て含まないもの」部分のSQLが以下です。
SQL
1select 1 from tags 2where tag in ('朝食付き','キャンセル無料') 3 and not exists( 4 select 1 from hotels_tags 5 where tags_id=tags.tags_id 6 )
上記では、全て含まないものと、'朝食付き','キャンセル無料'の何れかを含むものが含まれます。
not existsにしているのに、'朝食付き','キャンセル無料'の何れかを含むものが含まれるのはおかしいと思うかもしれませんが、'朝食付き'のみは'キャンセル無料'を含んでいないのでTrueで、'キャンセル無料'のみは'朝食付き'を含んでいないのでこれもTrueとなります。
'朝食付き'または'キャンセル無料'の何れも含むものはFalseとなるのです。
「'朝食付き'と'キャンセル無料'」という集合をhotels_tagsが含まないものという事で、ここが理解する為のポイントです。
その動作は、以下のSQLのhotel_idを変えてみると分かり易いでしょう。
SQL
1select * from tags 2where tag in ('朝食付き','キャンセル無料') 3 and not exists( 4 select 1 from hotels_tags 5 where tags_id=tags.tags_id and hotel_id=1 6 )
上記で「複数のtagを全て含まないもの」が得られるので、それをNotにすることで補集合である「複数のtagを全て持つもの」となります。
追記(質問への回答)
SQL
1select * from hotels 2where exists( 3 select 1 from tags where tag in ('朝食付き','キャンセル無料') 4 )
質問❶
tagsテーブルにはhotelテーブルと紐付けできるものがないのでこれだと、
inの中にtagsに存在するタグ名(WiFi無料、プール、朝食付きなど)を記入すればtrueとなり
全てを取得するということでしょうか。
hotelsとtagsに相関はありません。
単に、tagに '朝食付き'または'キャンセル無料'というtagがあれば、hotelsは全件抽出されます。
SQL
1select * from hotels 2where not exists( 3 select 1 from tags where tag in ('朝食付き','キャンセル無料') 4 )
質問❷
こちらのSQLを実行すると結果は0件になります。in ('朝食付き','キャンセル無料'))でないもの。
なのでその他のタグが相当し、hotelが全件ヒットするかと思いました。
なぜ0件になるのでしょうか。
こちらは質問①の補集合ですね。
因みに配列の扱えるDBMSだと補集合とするような事をしなくても、非常に簡潔で高速な問合せが可能です。
postgres でhotelsにtags int[] でtagを配列で持つ場合、以下で同じ結果が得られます。
SQL
1select * from hotels where tags @> array[2,3]::int[]
hotels_tagsと同内容を得る場合は、unnestで配列を行に展開します。
SQL
1select hotel_id, unnest(tags) as tags_id from hotels
DBMSの選定も重要な要素だと思いませんか。
投稿2019/03/02 02:30
編集2019/03/02 23:43総合スコア25430
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。

あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。