PostgreSQL Antipatterns: „Es kann nur einen geben!“

In SQL beschreiben Sie, was Sie erhalten möchten, und nicht, wie es ausgefĂŒhrt werden soll. Daher nimmt das Problem der Entwicklung von SQL-Abfragen im Stil von „wie es sich anhört, so wird es geschrieben“ seinen Platz ein, zusammen mit der Berechnung von Bedingungen in SQL..

Heute schauen wir uns an einfachen Beispielen an, zu was das im Kontext der Verwendung fĂŒhren kann. GROUP/DISTINCT und LIMIT gemeinsam mit ihnen.

Wenn Sie in der Abfrage geschrieben haben, „verbinde zuerst diese Tabellen und werfe dann alle Duplikate weg, es soll nur ein Exemplar pro SchlĂŒssel ĂŒbrig bleiben“ — genau so wird es funktionieren, auch wenn die Verbindung nicht nötig war.

Und manchmal hat man GlĂŒck und es „funktioniert einfach“, manchmal wirkt es sich negativ auf die Leistung aus, und manchmal fĂŒhrt es zu absolut unerwarteten Effekten aus Sicht des Entwicklers.

PostgreSQL Antipatterns: „Es kann nur einen geben!“
Nun, vielleicht nicht so spektakulÀr, aber


„SĂŒĂŸes Paar“: JOIN + DISTINCT

SELECT DISTINCT
  X.*
FROM
  X
JOIN
  Y
    ON Y.fk = X.pk
WHERE
  Y.bool_condition;

Es ist klar, dass man solche DatensĂ€tze X auswĂ€hlen wollte, fĂŒr die es in Y VerknĂŒpfungen gibt, die mit der ausgefĂŒhrten Bedingung ĂŒbereinstimmen.Sie haben die Anfrage ĂŒber JOIN — haben mehrmals einige pk-Werte erhalten (genau so oft, wie passende EintrĂ€ge in Y vorhanden waren). Wie kann man das beheben? NatĂŒrlich DISTINCT!

Besonders Ă€rgerlich ist es, wenn fĂŒr jeden X-Eintrag mehrere Hundert verwandte Y-EintrĂ€ge gefunden werden, die dann heldenhaft Duplikate entfernt werden


PostgreSQL Antipatterns: „Es kann nur einen geben!“

Wie kann man das beheben? ZunĂ€chst einmal sollte man erkennen, dass die Aufgabe modifiziert werden kann zu „solche X-EintrĂ€ge auszuwĂ€hlen, fĂŒr die es in Y MINDESTENS EINEN verwandten Eintrag gibt, der den Bedingungen entspricht“ — denn aus dem Y-Eintrag benötigen wir nichts.

Verschachteltes EXISTS

SELECT
  *
FROM
  X
WHERE
  EXISTS(
    SELECT
      NULL
    FROM
      Y
    WHERE
      fk = X.pk AND
      bool_condition
    LIMIT 1
  );

Einige Versionen von PostgreSQL verstehen, dass es ausreicht, den ersten gefundenen Eintrag in EXISTS zu finden, Àltere tun das nicht. Deshalb bevorzuge ich es, immer LIMIT 1 innerhalb EXISTS.

LATERAL JOIN

SELECT
  X.*
FROM
  X
, LATERAL (
    SELECT
      Y.*
    FROM
      Y
    WHERE
      fk = X.pk AND
      bool_condition
    LIMIT 1
  ) Y
WHERE
  Y IS DISTINCT FROM NULL;

Diese Variante ermöglicht es auch, gleichzeitig einige Daten aus dem gefundenen verwandten Y-Eintrag zurĂŒckzugeben, falls erforderlich. Eine Ă€hnliche Variante wurde in dem Artikel behandelt „PostgreSQL Antipatterns: Seltene EintrĂ€ge erreichen das Mittelmaß bei JOIN“.

„Warum mehr bezahlen?“: DISTINCT [ON] + LIMIT 1

Ein weiterer Vorteil solcher Abfrageumwandlungen ist die Möglichkeit, die Auswahl von DatensÀtzen leicht einzuschrÀnken, wenn nur einer oder mehrere davon benötigt werden, wie im folgenden Fall:

SELECT DISTINCT ON(X.pk)
  *
FROM
  X
JOIN
  Y
    ON Y.fk = X.pk
LIMIT 1;

Jetzt lesen wir die Abfrage und versuchen zu verstehen, was der DBMS vorgeschlagen wird:

  • wir verbinden die Tabellen
  • wir machen sie eindeutig nach X.pk
  • aus den verbleibenden DatensĂ€tzen wĂ€hlen wir einen aus

Was haben wir also erhalten? „Irgendeinen Datensatz“ aus den einzigartigen – und wenn wir diesen einen aus den nicht eindeutigen nehmen, Ă€ndert sich das Ergebnis irgendwie? ‚Wenn es keinen Unterschied gibt, warum mehr bezahlen?‘

SELECT
  *
FROM
  (
    SELECT
      *
    FROM
      X
    -- hier können passende Bedingungen eingefĂŒgt werden
    LIMIT 1 -- +1 Limit
  ) X
JOIN
  Y
    ON Y.fk = X.pk
LIMIT 1;

Das gleiche Thema gilt fĂŒr GROUP BY + LIMIT 1.

„Ich muss nur fragen“: implizite GROUP + LIMIT

Ähnliche Dinge treten bei verschiedenen ÜberprĂŒfungen der Nicht-Leerheit von Tabellen oder CTE wĂ€hrend der AusfĂŒhrung der Abfrage auf:

...
CASE
  WHEN (
    SELECT
      count(*)
    FROM
      X
    LIMIT 1
  ) = 0 THEN ...

Aggregatfunktionen (count/min/max/sum/...) funktionieren erfolgreich auf dem gesamten Satz, sogar ohne ausdrĂŒckliche Angabe GROUP BY. Nur das mit LIMIT sind sie nicht sehr kompatibel.

Der Entwickler könnte denken Wenn es dort EintrĂ€ge gibt, brauche ich nicht mehr als LIMIT. Aber das muss nicht sein! Denn fĂŒr die Datenbank ist das:

  • Berechne, was gewĂŒnscht ist fĂŒr alle EintrĂ€ge
  • Gib so viele Zeilen zurĂŒck, wie angefragt

Je nach den Zielbedingungen ist hier eine der Ersetzungen angebracht:

  • (count + LIMIT 1) = 0 findet man NOT EXISTS(LIMIT 1)
  • (count + LIMIT 1) > 0 findet man EXISTS(LIMIT 1)
  • count >= N findet man (SELECT count(*) FROM (... LIMIT N))

„Wie viel in Gramm wiegt man“: DISTINCT + LIMIT

SELECT DISTINCT
  pk
FROM
  X
LIMIT $1

Ein naiver Entwickler könnte fĂ€lschlicherweise annehmen, dass die AusfĂŒhrung der Anfrage stoppt, sobald wir die ersten $1 unterschiedlichen Werte gefunden haben.

In der Zukunft könnte das so funktionieren, dank eines neuen Knotens Index Skip Scan, dessen Implementierung derzeit erarbeitet wird, aber bisher — noch nicht.

ZunĂ€chst werden alle EintrĂ€ge extrahiert, einzigartig gemacht, und nur daraus wird zurĂŒckgegeben, was angefordert wurde. Besonders traurig wird es, wenn wir etwas wie $1 = 4, aber in der Tabelle — Hunderttausende von EintrĂ€gen


Um den Frust zu vermeiden, benutzen wir eine rekursive Abfrage „DISTINCT fĂŒr Arme“ aus der PostgreSQL Wiki:

PostgreSQL Antipatterns: „Es kann nur einen geben!“

Quelle: habr.com

Erwerben Sie zuverlĂ€ssiges Hosting fĂŒr Websites mit DDoS-Schutz, VPS VDS-Server đŸ”„ Kaufen Sie zuverlĂ€ssiges Hosting fĂŒr Websites mit DDoS-Schutz, VPS VDS-Server | ProHoster