プロファイル用とプロファイルの雇用状況用の 2 つのテーブルがあります。2 つのテーブルには 1 対 1 の関係があります。1 つのプロファイルに雇用状況がない場合があります。テーブル スキーマは次のとおりです (わかりやすくするために、無関係な列は削除されています)。
create type employment_status as enum ('claimed', 'approved', 'denied');
create table if not exists profiles
(
id bigserial not null
constraint profiles_pkey
primary key
);
create table if not exists employments
(
id bigserial not null
constraint employments_pkey
primary key,
status employment_status not null,
profile_id bigint not null
constraint fk_rails_d95865cd58
references profiles
on delete cascade
);
create unique index if not exists index_employments_on_profile_id
on employments (profile_id);
これらの表を使用して、すべての失業者のプロファイルをリストするように求められました。失業者プロファイルは、雇用記録を持たないプロファイル、または「承認済み」以外のステータスで雇用されているプロファイルとして定義されます。
私の最初の暫定的なクエリは次のクエリでした。
SELECT * FROM "profiles"
LEFT JOIN employments ON employments.profile_id = profiles.id
WHERE employments.status != 'approved'
ここでの前提は、すべてのプロファイルがそれぞれの雇用とともにリストされることであり、その後、where 条件でそれらをフィルタリングできます。雇用記録のないプロファイルは、雇用状況がnull
であるため、条件によってフィルタリングされます。ただし、このクエリは、雇用されていないプロファイルを返しませんでした。
いくつかの調査の後、この投稿を見つけ、それが機能しない理由を説明し、クエリを変換しました。
SELECT *
FROM profiles
LEFT JOIN employments ON profiles.id = employments.profile_id and employments.status != 'approved';
これは実際に機能しました。しかし、私の ORM はわずかに異なるクエリを生成し、うまくいきませんでした。
SELECT profiles.* FROM "profiles"
LEFT JOIN employments ON employments.profile_id = profiles.id AND employments.status != 'approved'
唯一の違いはselect句です。このわずかな違いがなぜこのような違いを生むのかを理解しようとしました。3 つのクエリすべてについて説明と分析を実行しました。
EXPLAIN ANALYZE SELECT * FROM "profiles"
LEFT JOIN employments ON employments.profile_id = profiles.id
WHERE employments.status != 'approved'
Hash Join (cost=14.28..37.13 rows=846 width=452) (actual time=0.025..0.027 rows=2 loops=1)
Hash Cond: (e.profile_id = profiles.id)
-> Seq Scan on employments e (cost=0.00..20.62 rows=846 width=68) (actual time=0.008..0.009 rows=2 loops=1)
Filter: (status <> ''approved''::employment_status)
Rows Removed by Filter: 1
-> Hash (cost=11.90..11.90 rows=190 width=384) (actual time=0.007..0.007 rows=8 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 12kB
-> Seq Scan on profiles (cost=0.00..11.90 rows=190 width=384) (actual time=0.003..0.004 rows=8 loops=1)
Planning Time: 0.111 ms
Execution Time: 0.053 ms
EXPLAIN ANALYZE SELECT *
FROM profiles
LEFT JOIN employments ON profiles.id = employments.profile_id and employments.status != 'approved';
Hash Right Join (cost=14.28..37.13 rows=846 width=452) (actual time=0.036..0.042 rows=8 loops=1)
Hash Cond: (employments.profile_id = profiles.id)
-> Seq Scan on employments (cost=0.00..20.62 rows=846 width=68) (actual time=0.005..0.005 rows=2 loops=1)
Filter: (status <> ''approved''::employment_status)
Rows Removed by Filter: 1
-> Hash (cost=11.90..11.90 rows=190 width=384) (actual time=0.015..0.015 rows=8 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 12kB
-> Seq Scan on profiles (cost=0.00..11.90 rows=190 width=384) (actual time=0.010..0.011 rows=8 loops=1)
Planning Time: 0.106 ms
Execution Time: 0.108 ms
EXPLAIN ANALYZE SELECT profiles.* FROM "profiles"
LEFT JOIN employments ON employments.profile_id = profiles.id AND employments.status != 'approved'
Seq Scan on profiles (cost=0.00..11.90 rows=190 width=384) (actual time=0.006..0.007 rows=8 loops=1)
Planning Time: 0.063 ms
Execution Time: 0.016 ms
最初と 2 番目のクエリ プランは、一方のハッシュ結合と他方の右ハッシュ結合を除いてほとんど同じですが、最後のクエリは結合または where 条件さえ実行しません。
私はうまくいった4番目のクエリを思いつきました:
EXPLAIN ANALYZE SELECT profiles.* FROM profiles
LEFT JOIN employments ON employments.profile_id = profiles.id
WHERE (employments.id IS NULL OR employments.status != 'approved')
Hash Right Join (cost=14.28..35.02 rows=846 width=384) (actual time=0.021..0.026 rows=7 loops=1)
Hash Cond: (employments.profile_id = profiles.id)
Filter: ((employments.id IS NULL) OR (employments.status <> ''approved''::employment_status))
Rows Removed by Filter: 1
-> Seq Scan on employments (cost=0.00..18.50 rows=850 width=20) (actual time=0.002..0.003 rows=3 loops=1)
-> Hash (cost=11.90..11.90 rows=190 width=384) (actual time=0.011..0.011 rows=8 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 12kB
-> Seq Scan on profiles (cost=0.00..11.90 rows=190 width=384) (actual time=0.007..0.008 rows=8 loops=1)
Planning Time: 0.104 ms
Execution Time: 0.049 ms
この件に関する私の質問は次のとおりです。
- 構造は同じなのに、2 番目と 3 番目のクエリのクエリ プランが異なるのはなぜですか?
- 同じ構造なのに、最初と 4 番目のクエリのクエリ プランが異なるのはなぜですか?
- Postgres が 3 番目のクエリの join と where 条件を完全に無視するのはなぜですか?
編集:
次のサンプル データでは、予想されるクエリは 2 と 3 を返すはずです。
insert into profiles values (1);
insert into profiles values (2);
insert into profiles values (3);
insert into employments (profile_id, status) values (1, 'approved');
insert into employments (profile_id, status) values (2, 'denied');