PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

Dieselbe Spalte mehrmals in WHERE-Klausel verwenden

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