これは、関係分割の場合です。タグを追加しました。
インデックス
USER_PROPERTY_MAP(property_value_id, user_id)
にPKまたはUNIQUE制約があると仮定します -クエリを高速化するためのこの順序の列。関連:
- 複合インデックスは、最初のフィールドのクエリにも適していますか?
PROPERTY_VALUE(value, property_name_id, id)
にもインデックスが必要です 。繰り返しますが、この順序で列を作成します。最後の列を追加しますid
インデックスのみのスキャンを取得した場合のみ。
指定された数のプロパティに対して
それを解決する方法はたくさんあります。これは、正確に2つの場合、最も単純で最速の1つである必要があります。 プロパティ:
SELECT u.*
FROM users u
JOIN user_property_map up1 ON up1.user_id = u.id
JOIN user_property_map up2 USING (user_id)
WHERE up1.property_value_id =
(SELECT id FROM property_value WHERE property_name_id = 1 AND value = '101')
AND up2.property_value_id =
(SELECT id FROM property_value WHERE property_name_id = 2 AND value = '102')
-- AND u.user_name = 'user1' -- more filters?
-- AND u.city = 'city1'
テーブルPROPERTY_NAME
にアクセスしていません 、クエリの例によると、プロパティ名はすでにIDに解決されているようです。それ以外の場合は、PROPERTY_NAME
に結合を追加できます 各サブクエリで。
この関連する質問の下で、技術の武器を集めました:
- SQLの結果をフィルタリングしてhas-many-through関係にする方法
不明な数の物件の場合
@Mikeと@Valeraには、それぞれの回答に非常に役立つクエリがあります。これをさらに動的にするため :
WITH input(property_name_id, value) AS (
VALUES -- provide n rows with input parameters here
(1, '101')
, (2, '102')
-- more?
)
SELECT *
FROM users u
JOIN (
SELECT up.user_id AS id
FROM input
JOIN property_value pv USING (property_name_id, value)
JOIN user_property_map up ON up.property_value_id = pv.id
GROUP BY 1
HAVING count(*) = (SELECT count(*) FROM input)
) sub USING (id);
VALUES
からのみ行を追加/削除します 表現。または、WITH
を削除します 句とJOIN
プロパティフィルタなし まったく。
問題 このクラスのクエリ(すべての部分一致をカウント)では、パフォーマンスです。 。私の最初のクエリはそれほど動的ではありませんが、通常はかなり高速です。 (EXPLAIN ANALYZE
でテストするだけです 。)特に大きなテーブルと増え続けるプロパティの場合。
両方の長所は?
再帰CTEを使用するこのソリューションは、適切な妥協点となるはずです。高速および 動的:
WITH RECURSIVE input AS (
SELECT count(*) OVER () AS ct
, row_number() OVER () AS rn
, *
FROM (
VALUES -- provide n rows with input parameters here
(1, '101')
, (2, '102')
-- more?
) i (property_name_id, value)
)
, rcte AS (
SELECT i.ct, i.rn, up.user_id AS id
FROM input i
JOIN property_value pv USING (property_name_id, value)
JOIN user_property_map up ON up.property_value_id = pv.id
WHERE i.rn = 1
UNION ALL
SELECT i.ct, i.rn, up.user_id
FROM rcte r
JOIN input i ON i.rn = r.rn + 1
JOIN property_value pv USING (property_name_id, value)
JOIN user_property_map up ON up.property_value_id = pv.id
AND up.user_id = r.id
)
SELECT u.*
FROM rcte r
JOIN users u USING (id)
WHERE r.ct = r.rn; -- has all matches
ここにdbfiddle
再帰的CTEに関するマニュアル。
追加された複雑さは、追加のオーバーヘッドが利点を上回っている、または最初から違いが無視できる小さなテーブルには影響しません。ただし、拡張性ははるかに高く、テーブルが増え、プロパティフィルターの数が増える「カウント」手法よりもますます優れています。
カウントテクニックはすべてにアクセスする必要があります user_property_map
の行 指定されたすべてのプロパティフィルターに対して、このクエリ(および最初のクエリ)は無関係なユーザーを早期に排除できます。
パフォーマンスの最適化
現在のテーブル統計(合理的な設定、autovacuum
実行中)、Postgresは「最も一般的な値」に関する知識を持っています 各列で、最初のクエリの結合を並べ替えます 最も選択的なプロパティフィルターを最初に評価します(または少なくとも最も選択性の低いフィルターではありません)。特定の制限まで:join_collapse_limit
。関連:
- Postgresqljoin_collapse_limitとクエリプランニングの時間
- 検索語を少し変更すると、クエリが大幅に遅くなるのはなぜですか?
この「deus-ex-machina」介入は、3番目のクエリでは不可能です。 (再帰CTE)。パフォーマンスを向上させるには(おそらく多くの場合)、最初に自分でより選択的なフィルターを配置する必要があります。ただし、最悪の場合の順序でも、クエリのカウントを上回ります。
関連:
- PostgreSQLで統計ターゲットを確認する
非常に厄介な詳細:
- 既存のデータを含むテーブルで作成された場合、PostgreSQLの部分インデックスは使用されません
マニュアルの詳細説明:
- プランナーが使用する統計