Dies ist ein Fall von relationaler Teilung. Ich habe das Tag hinzugefügt.
Indizes
Angenommen eine PK- oder UNIQUE-Einschränkung für USER_PROPERTY_MAP(property_value_id, user_id)
- Spalten in dieser Reihenfolge, um meine Abfragen schnell zu machen. Verwandte:
- Ist ein zusammengesetzter Index auch gut für Abfragen im ersten Feld?
Sie sollten auch einen Index für PROPERTY_VALUE(value, property_name_id, id)
haben . Wieder Spalten in dieser Reihenfolge. Fügen Sie die letzte Spalte id
hinzu nur wenn Sie Index-Only-Scans daraus bekommen.
Für eine bestimmte Anzahl von Eigenschaften
Es gibt viele Möglichkeiten, es zu lösen. Dies sollte einer der einfachsten und schnellsten für genau zwei sein Eigenschaften:
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'
Tabelle PROPERTY_NAME
wird nicht besucht , da Sie anscheinend Eigenschaftsnamen gemäß Ihrer Beispielabfrage bereits in IDs aufgelöst haben. Andernfalls könnten Sie PROPERTY_NAME
einen Join hinzufügen in jeder Unterabfrage.
Unter dieser verwandten Frage haben wir ein Arsenal an Techniken zusammengestellt:
- So filtern Sie SQL-Ergebnisse in einer Has-Viele-Durch-Beziehung
Für eine unbekannte Anzahl von Eigenschaften
@Mike und @Valera haben sehr nützliche Fragen in ihren jeweiligen Antworten. Um dies noch dynamisch zu machen :
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);
Nur Zeilen aus den VALUES
hinzufügen/entfernen Ausdruck. Oder entfernen Sie den WITH
-Klausel und JOIN
für keine Eigenschaftsfilter überhaupt.
Das Problem bei dieser Klasse von Abfragen (Zählen aller Teilübereinstimmungen) ist Leistung . Meine erste Abfrage ist weniger dynamisch, aber in der Regel erheblich schneller. (Einfach mit EXPLAIN ANALYZE
testen .) Speziell für größere Tabellen und eine wachsende Anzahl von Eigenschaften.
Das Beste aus beiden Welten?
Diese Lösung mit rekursivem CTE sollte ein guter Kompromiss sein:schnell und dynamisch:
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 hier
Das Handbuch über rekursive CTEs.
Die zusätzliche Komplexität zahlt sich nicht für kleine Tabellen aus, bei denen der zusätzliche Aufwand den Nutzen überwiegt oder der Unterschied von vornherein vernachlässigbar ist. Aber es skaliert viel besser und ist "Zähl"-Techniken mit wachsenden Tabellen und einer wachsenden Anzahl von Eigenschaftsfiltern zunehmend überlegen.
Zähltechniken müssen alle besuchen Zeilen in user_property_map
für alle gegebenen Eigenschaftsfilter, während diese Abfrage (wie auch die 1. Abfrage) irrelevante Benutzer frühzeitig eliminieren kann.
Optimierung der Leistung
Mit aktuellen Tabellenstatistiken (sinnvolle Einstellungen, autovacuum
läuft), hat Postgres Wissen über "häufigste Werte" in jeder Spalte und ordnet Joins in der ersten Abfrage neu zuerst die selektivsten Eigenschaftsfilter auszuwerten (oder zumindest nicht die am wenigsten selektiv). Bis zu einem bestimmten Limit:join_collapse_limit
. Verwandte:
- Postgresql join_collapse_limit und Zeit für die Abfrageplanung
- Warum verlangsamt eine geringfügige Änderung des Suchbegriffs die Abfrage so sehr?
Dieser „deus-ex-machina“-Eingriff ist mit der 3. Abfrage nicht möglich (rekursiver CTE). Um die Leistung (möglicherweise sehr) zu verbessern, müssen Sie zuerst selbst selektivere Filter platzieren. Aber selbst bei der Worst-Case-Ordnung übertrifft es immer noch das Zählen von Abfragen.
Verwandte:
- Statistikziele in PostgreSQL prüfen
Viel mehr blutige Details:
- Teilweiser PostgreSQL-Index wird nicht verwendet, wenn er für eine Tabelle mit vorhandenen Daten erstellt wird
Weitere Erklärungen im Handbuch:
- Vom Planer verwendete Statistiken