次のクエリがあります。
SELECT id,
email,
first_name as "firstName",
last_name as "lastName",
is_active as "isActive",
password,
access,
CASE
WHEN access < 3 THEN (
SELECT
CASE WHEN count(*) = 1 THEN true ELSE false END
FROM user_rating_entity ure
WHERE ure.user_id = u.id
AND ure.rating_entity_id = :re_id
)
ELSE true
END as "isResponsible"
FROM users u
WHERE u.id = :id
access > 3
の場合、フィールド「isResponsible」を直接trueに設定し、サブクエリを実行しないでください。 >=
および<
ににアクセスする両方のケースで説明分析を使用しましたが、同じ出力が得られます。
どうしてこんなことに?
ここでクエリプランを読み取るには、3つの重要な部分があります。
サンプルデータを提供しなかったので、いくつか作成しましょう。
_CREATE TABLE foo AS
SELECT x FROM generate_series(1,100) AS x;
_
そして、実行可能な範囲外で、サブクエリを使用して基本的なクエリを実行してみましょう。
_EXPLAIN ANALYZE
SELECT
x,
(CASE WHEN x>200 THEN (SELECT sum(x) FROM foo) END)
FROM foo;
_
計画は、ケースが付随しているが実行されなかったことを示します。
_ Seq Scan on foo (cost=2.26..4.51 rows=100 width=4) (actual time=0.017..0.047 rows=100 loops=1)
InitPlan 1 (returns $0)
-> Aggregate (cost=2.25..2.26 rows=1 width=4) (never executed)
-> Seq Scan on foo foo_1 (cost=0.00..2.00 rows=100 width=4) (never executed)
Planning time: 0.101 ms
Execution time: 0.118 ms
(6 rows)
_
Aggregate
行の(never execution)で確認できます。ただし、CASE WHEN x>20 THEN (SELECT sum(x) FROM foo
のように設定すると、さらに多くのことがわかります
_ Seq Scan on foo (cost=2.26..4.51 rows=100 width=4) (actual time=0.020..0.095 rows=100 loops=1)
InitPlan 1 (returns $0)
-> Aggregate (cost=2.25..2.26 rows=1 width=4) (actual time=0.043..0.043 rows=1 loops=1)
-> Seq Scan on foo foo_1 (cost=0.00..2.00 rows=100 width=4) (actual time=0.006..0.019 rows=100 loops=1)
Planning time: 0.092 ms
Execution time: 0.158 ms
(6 rows)
_
ここでは、Aggregateが_loops=1
_時間ループしていることがわかります。 PostgreSQLは、これが相関サブクエリではなく、単なるリテラルに(本質的に)リテラルに変換されることを認識しています。それが相関していることを確認しましょう。
_EXPLAIN ANALYZE
SELECT
x,
(CASE WHEN x>20 THEN (SELECT sum(f2.x)+f1.x FROM foo AS f2) END)
FROM foo AS f1;
_
今あなたはこの計画を見るでしょう
_ Seq Scan on foo f1 (cost=0.00..228.50 rows=100 width=4) (actual time=0.020..3.210 rows=100 loops=1)
SubPlan 1
-> Aggregate (cost=2.25..2.26 rows=1 width=4) (actual time=0.038..0.038 rows=1 loops=80)
-> Seq Scan on foo f2 (cost=0.00..2.00 rows=100 width=4) (actual time=0.005..0.017 rows=100 loops=80)
Planning time: 0.104 ms
Execution time: 3.272 ms
_
ここで重要なのは、集計に_loops=80
_があり、それ自体に_loops=80
_シーケンススキャンが必要であることです。
これはすべて一般的ですが、サンプルデータやクエリプランなしで提供できるのはこれだけです。
Evanはすでに指摘しています _(never executed)
_の出力で_EXPLAIN ANALYZE
_を見落とした可能性があります。
最新のPostgresでクエリを記述するためのよりクリーンで用途の広い方法は、LATERAL
サブクエリを使用することです(必ずしも高速ではありません)。
_SELECT id, email
, first_name AS "firstName"
, last_name AS "lastName"
, is_active AS "isActive"
, password, access
, COALESCE(ure.resp, true) AS "isResponsible"
FROM users u
LEFT JOIN LATERAL (
SELECT (count(*) = 1) AS resp
FROM user_rating_entity
WHERE user_id = u.id -- lateral reference
AND rating_entity_id = :re_id
) ure ON u.access < 3
WHERE u.id = :id;
_
コメントしたように 、 COALESCE()
は、特定の CASE
式のよりエレガントな代替品です。
しかし、あなたはcount(*)
は決してnull
ではないと述べましたか?次に、なぜCOALESCE()
?
count(*)
自体は決してnull
(「行なし」の場合は0)ではありませんが、_LEFT JOIN
_は、結合条件が満たされない場合でもnull
を生成します。そして、それがここでの要点です。Postgresは、結合条件_u.access < 3
_が満たされていない外側の行をカウントしません。元のクエリに従って、null
を取得し、true
に折りたたみます。
Evan's answer でわかるように、count(*)
は、条件を満たすusers
のすべての行に対して_user_rating_entity
_の順次スキャンをトリガーします。小さなテーブルや非常に少数のユーザーには問題ありませんが、大きなテーブルには問題があります。
一致するインデックスを使用すると、インデックススキャンを実行できるため、大幅に高速化できます。
_CREATE INDEX foo ON user_rating_entity (user_id, rating_entity_id)
_
また、カウントごとに数行以上ある場合は、より高速なクエリ手法があります。しかし、それがこの質問の範囲を広げています...
関連:
または:
_SELECT ...
, CASE WHEN ure.ct <> 1 THEN false ELSE true END AS "isResponsible"
-- COALESCE(NOT ure.ct <> 1, true) -- equivalent
-- ure.ct = 1 -- NOT equivalent, misses NULL case
FROM users u
LEFT JOIN LATERAL (
SELECT count(*) AS ct
FROM ...
) ure ON u.access < 3
WHERE u.id = :id;
_
同じ結果。