54 Punkte von GN⁺ 2025-10-19 | 5 Kommentare | Auf WhatsApp teilen
  • SQL-Antipatternen erschweren die Wartung von Queries und Datenpipelines und führen zu langsamerer Performance als erwartet
  • Typische Beispiele sind übermäßiger Einsatz von CASE WHEN, Funktionen auf indexierten Spalten, SELECT *, übermäßiger Einsatz von DISTINCT, verschachtelte Views und Subqueries sowie tiefe Abhängigkeitsstrukturen
  • Die meisten Probleme entstehen durch provisorische Lösungen unter Zeit- und Termindruck und schaden langfristig sowohl der Datenzuverlässigkeit als auch der Entwicklungsgeschwindigkeit
  • Als Gegenmaßnahmen braucht es unter anderem klar definierte Joins, den Einsatz von Dimensionstabellen, das Entfernen unnötiger Verschachtelungen und eine regelmäßige Bereinigung von Views
  • SQL sollte nicht als einfaches Skript, sondern als im Team verwalteter Produktionscode behandelt werden; eine auf Lesbarkeit bedachte Anfangsplanung reduziert Nacharbeit

Einführung

  • Heute geht es um einige häufige und besonders folgenreiche SQL-Antipatternen
  • Diese Probleme führen zu einem Teufelskreis aus sinkender Datenzuverlässigkeit und langsamerer Query-Entwicklung
  • Die folgende Liste ist nicht vollständig; für ein tieferes Verständnis empfiehlt sich Bill Karwins Buch

Übermäßig komplexe CASE-WHEN-Ausdrücke

  • In großen Systemen werden CASE-WHEN-Ausdrücke häufig verwendet, um Statuscodes (z. B. 1 = nicht auf Lager) in menschenlesbare Texte umzuwandeln
  • Für die schnelle Entwicklung eines Dashboards oder Reports diese Logik nur in einer einzelnen View unterzubringen, ist langfristig ein Antipattern
  • Das führt zu duplizierter Copy-and-paste-Logik, widersprüchlichen Interpretationen und macht die gesamte Query-Landschaft unübersichtlich
  • Die Lösung ist, eine separate Dimensionstabelle (dimension table) oder eine gemeinsame View zur Umwandlung der Statuscodes zu erstellen, damit die Logik wiederverwendbar wird

Funktionen auf indexierten Spalten verwenden

  • Wenn man wie in WHERE UPPER(name) = 'ABC' eine Funktion auf eine indexierte Spalte anwendet, geht die Effizienz des Indexes verloren
    • In SQL Server und ähnlichen Systemen kann das zu einem unnötigen Full Table Scan führen
  • Die Lösung besteht darin, die funktional transformierte Spalte separat zu indexieren oder den Eingabewert vorab umzuwandeln, um die Query-Bedingung zu vereinfachen

SELECT * in Views verwenden

  • Bei der Entwicklung von Views wirkt SELECT * bequem, aber wenn sich die Struktur (das Schema) ändert, kann die View leicht kaputtgehen
  • Außerdem werden unnötige Spalten mitgezogen, was zu unbeabsichtigten Abhängigkeiten und Performance-Problemen führt; deshalb sollten Spalten explizit ausgewählt werden

Mit DISTINCT Duplikate „lösen“

  • Wenn durch fehlerhafte Joins doppelte Ergebnisse entstehen, ist eine provisorische Behebung mit SELECT DISTINCT problematisch, weil sie Probleme mit der Datenintegrität verdeckt
    • Die eigentliche Ursache sind unvollständige Join-Bedingungen oder Fehler in der Beziehungsdefinition (1:1, 1:N usw.)
    • Die richtige Lösung ist, die Beziehungsdefinition durch robustere Join-Logik zu präzisieren und die Konsistenz der Beziehungen vor Aggregation oder Reporting sicherzustellen

Verschachtelte Views (Excessive View Layer Stacking)

  • Wenn mehrere Teams bestehende Views wiederverwenden und immer neue Views darauf aufbauen, wird die Abhängigkeitskette komplex und die Performance verschlechtert sich drastisch
    • Debugging wird schwieriger, und das Erweitern von Queries fühlt sich bald wie eine „archäologische Ausgrabung“ an
  • Nötig ist eine Strategie, die Transformationslogik regelmäßig zu flatten, und komplexe Berechnungen in klaren Basis-Views oder Tabellen zu materializen

Zu tief verschachtelte Subqueries

  • Tief verschachtelte Subqueries mit mehr als 3 bis 4 Ebenen verschlechtern die Lesbarkeit und erschweren das Debugging
    • Es gibt sogar Fälle mit Subqueries von mehr als 5000 Zeilen
  • Mit CTEs (Common Table Expressions) lassen sich logische Schritte leichter trennen und die Lesbarkeit (readability) der Query verbessern

Fazit

  • SQL wirkt oberflächlich betrachtet einfach, aber mit wachsender Systemgröße nimmt auch die Komplexität zu
  • Die meisten Antipatternen entstehen nicht aus böser Absicht, sondern als Ergebnis von Kompromissen für „schnelle Ergebnisse“ (Zeitdruck, Deadlines, Provisorien)
  • Wenn man SQL wie Code verwaltet (Versionsverwaltung, Code-Reviews, klare Architektur), lassen sich langfristig Produktivität und Zuverlässigkeit zugleich sichern
  • Wer schon in der frühen Planung ein paar Minuten in Klarheit und Konsistenz investiert, reduziert spätere Nacharbeit und Verwirrung erheblich

5 Kommentare

 
aer0700 2025-10-20

Weil gerade keine Zeit ist, löscht man erstmal nur den dringendsten Brand und nimmt sich vor, es später neu zu schreiben — aber wenn sich das aufstaut, endet es in einer schrecklichen Query-Hölle. Ich habe selbst einige davon gebaut. Obwohl man weiß, dass dieses „später“, in dem man sie neu schreibt, niemals kommen wird.

 
firefoxsaiko123 2025-10-20

Uff ...

 
ilikeall 2025-10-20

„Die meisten Probleme sind provisorische Notlösungen aufgrund von Zeit- und Termindruck“
Schnief..

 
GN⁺ 2025-10-19
Hacker-News-Kommentar
  • Wenn ich in einer Query DISTINCT sehe, vermute ich, dass der Verfasser das Datenmodell oder die Mengenlehre nicht richtig verstanden hat – oder vielleicht beides nicht
    • Manchmal ist DISTINCT auch ein Signal für ein übermäßig normalisiertes Schema. Zum Beispiel halte ich es nicht für nötig, extra eine Tabelle wie addresses_cities anzulegen, nur um zu verhindern, dass derselbe Stadtname mehrfach gespeichert wird
    • Meine Erfahrung ist fast genau dieselbe. Kürzlich hatte ich aber einen Fall, in dem sich die Performance stark verbessert hat, als ich DISTINCT in eine CTE eingefügt habe, obwohl alle Joins korrekt waren. Wenn die Eindeutigkeit der Datensätze garantiert ist, scheint der Query Planner zu optimieren
    • Ich habe einmal LIMIT 1 zu einer Query hinzugefügt, weil ich erwartet hatte, dass höchstens ein Ergebnis zurückkommt, und bekam ähnliches Feedback, dass das keine gute Praxis sei. In großen Tabellen neigt die DB aber dazu, selbst dann weiter die ganze Tabelle zu durchsuchen, wenn der gewünschte Datensatz schon gefunden wurde (sqlite, mysql, postgresql)
    • Ich frage mich, wie man erkennen kann, ob man DISTINCT in einer Query wie SELECT x FROM t gefahrlos weglassen kann. Selbst wenn man im Schema von t sieht, dass x eine PRIMARY- oder UNIQUE-Constraint hat, kann jemand die UNIQUE-Constraint kurz darauf entfernen. Dann entstehen Duplikate, und man rätselt, warum. SQL ist keine Set-, sondern eine Bag-Sprache. Wenn zur Laufzeit rel t und Attribut x gefunden werden, werden sie einfach zurückgegeben. Es können Duplikate entstehen, und sogar Typen können sich ändern. Wenn man also ein Set will, muss man DISTINCT ausdrücklich angeben. Der Query Planner wird zur Laufzeit bei UNIQUE oder PRIMARY ohnehin keine Deduplizierung ausführen
    • In Cypher ist es eher umgekehrt. Wenn man mit neo4j komplexe Daten verarbeitet, landen doppelte Knoten sehr leicht im Ergebnis, sodass DISTINCT unverzichtbar ist. Vor allem bei Beziehungen variabler Länge wird es ohne DISTINCT langsam und duplikatreich
  • Ich habe ein zweiteiliges Tutorial mit etwa 9000 Zeichen darüber geschrieben, wie man korrekte Query-Strukturen auch ohne DISTINCT entwirft
    https://kb.databasedesignbook.com/posts/systematic-design-of-join-queries/
    • Guter Artikel. Habe ich direkt als Lesezeichen gespeichert. Und dann gemerkt, dass das tatsächlich auch ein Buch ist
  • Eines der seltener erwähnten Themen sind Queries, die nach „nicht Vorhandenem“ suchen. Zum Beispiel sind != oder NOT IN (...) in den meisten Fällen ineffizient (es sei denn, andere Bedingungen haben die Ergebnismenge bereits stark eingegrenzt). Und es ist wichtig zu verstehen, wie die DB mit null umgeht. Ob null und leere Zeichenkette gleich sind, ob null == null gilt – das kann je nach DB unterschiedlich sein
    • Was null-Verarbeitung und Indizierung angeht: Die von mir verwendeten DBs indexieren null-Werte nicht, daher laufen Queries wie WHERE col IS NULL ineffizient, selbst wenn es einen Index auf col gibt. Wenn es wirklich nötig ist, empfehle ich, eine char(1)- oder bit-Spalte anzulegen, die markiert, ob col null ist, und genau dieses Feld zu indexieren
    • Du hast gesagt, dass != oder NOT IN (...) fast immer ineffizient sind – ich frage mich, warum. Wenn die rechte Seite konstant ist, sollte das doch ein Hash-Table-Lookup sein und im Allgemeinen effizient laufen. Gibt es eine effizientere Alternative?
  • Ich glaube nicht, dass alle hier genannten „Anti-Patterns“ wirklich Anti-Patterns sind. Dass Query-Bedingungen nicht zu den Indizes passen, ist letztlich ein Problem mangelnden Verständnisses der Indexprinzipien. Und viele der hier genannten Punkte hängen eher mit dem Datenbankschema als mit SQL selbst zusammen. Wenn DISTINCT nötig ist, könnte das ein Zeichen dafür sein, dass das Primärschlüssel-Design nicht stimmt. Wenn man zu viele Views übereinander stapelt, ist am Ende das Design der Basistabellen das Problem. Gutes DB-Modellierung verhindert all diese Probleme im Vorfeld
  • Diese „Anti-Patterns“ sind in Wirklichkeit oft nur einfache Workarounds für die Grenzen – oder das Nicht-Design – der SQL-Sprache. Ich entwickle eine neue Sprache, die auf SQL-Datenbanken läuft, und möchte für jedes dieser Probleme bessere Alternativen schaffen. Sie ist noch unvollständig und schlecht dokumentiert, aber falls Interesse besteht, hätte ich gern Feedback auf https://lutra-lang.org
    • Der Ausdruck „SQL-Datenbank“ ist mehrdeutig. SQL wird nicht nur in relationalen, sondern auch in nichtrelationalen DBs implementiert. Experten kennen die Probleme von SQL schon seit Langem, und es gab mit Chris Date und Hugh Darwens Tutorial D bereits Alternativen. Trotzdem konnten sich diese wegen der über Jahrzehnte gewachsenen SQL-Codebasis und Tooling-Landschaft nicht durchsetzen. Ich habe SQL zu verdanken, dass ich jahrzehntelang Jobsicherheit und konstante Einnahmen hatte; also ja, eine bessere Sprache wäre wünschenswert, aber ich sehe die Lage auch positiv
    • Das Projekt sieht gut aus. Wenn es ausgereifter wird, werde ich es definitiv weiterverfolgen
  • Das größte Anti-Pattern ist, SQL nicht als echte Programmiersprache zu betrachten, sondern nur als einfache Query-Sprache. Ich empfehle, den Code konsistent einzurücken und logisch zusammengehörige Teile zu gruppieren. Subqueries sollte man möglichst in CTEs umwandeln. Auch wirkungsvolle Kommentare sind wichtig. Mein Stil hier: https://bentilly.blogspot.com/2011/02/sql-formatting-style.html
    • Ich finde, solche Diskussionen über Codestil sind ohne ein passendes Linter-Tool fast bedeutungslos
  • Der größte Hebel, um meine Queries schneller zu machen und weniger Serverressourcen zu verbrauchen, war, sie sargabler zu machen
    https://en.wikipedia.org/wiki/Sargable
    https://www.brentozar.com/blitzcache/non-sargable-predicates/
    • Ich frage mich, in welchen Communities das Wort „sargable“ tatsächlich benutzt wird. Ich arbeite seit über 20 Jahren mit SQL, habe es aber in Manuals, auf Stack Overflow oder HN fast nie gesehen. Mich würde interessieren, ob das vor allem in bestimmten DBs, Firmen oder Open-Source-Communities gebräuchlich ist
    • Bei der Suche nach der Herkunft von sargable war diese StackOverflow-Antwort hilfreich: https://dba.stackexchange.com/a/217983
      Das Wort sargable ist ein Portmanteau aus „Search ARGument ABLE“
  • Viele Probleme durch den übermäßigen Einsatz von CASE WHEN lassen sich lösen, indem man die Logik in einer UDF (User Defined Function) zentralisiert
    Eine Funktion auf einer indexierten Spalte ist ein Signal dafür, dass die Query nicht sargable ist
    Statt DISTINCT zu überstrapazieren, kann bei durch Joins verursachtem Fan-out eine Query nützlich sein, die entsprechend der Tabellen-Granularität dedupliziert, etwa mit
    ROW_NUMBER() OVER (PARTITION BY <grain> ORDER BY <deterministic sort>) = 1
    
    Manche DBs unterstützen QUALIFY, wodurch die Query deutlich sauberer wird
    sargable erklärt
    QUALIFY in Redshift
    • Nicht-sargable Probleme lassen sich mit einem Expression Index leicht lösen. Zumindest in sqlite sehe ich das so
  • Es gibt auch Situationen, in denen verschachtelte Views tatsächlich nötig sind. In unserer POS-Software verwenden wir viele verschachtelte Views, um ein Backbone-View zu bauen, das Transaktionen auf einen Blick sauber darstellt. Andernfalls müsste man für jede einzelne Tabelle unterschiedliche where-Klauseln schreiben und Dinge wie void/Rückgaben/Stornierungen jedes Mal erneut behandeln; bei Änderungen müsste man dann Dutzende Views/Procedures anpassen. In unserem Fall sind verschachtelte Views deutlich praktischer
  • Das Problem mit Funktionen auf indexierten Spalten braucht eine klarere Erklärung. Wenn eine Funktion auf eine indizierte Spalte angewendet wird, geht die Effizienz des Index verloren, und praktisch entsteht ein Full Scan, was langsam ist. Ich habe das am eigenen Leib erfahren und gelernt
    • Dazu gibt es eine bekannte Dokumentation: https://use-the-index-luke.com/sql/where-clause/obfuscation
    • Die vorgeschlagene Lösung (z. B. einen Index auf UPPER(name) anzulegen) ist zumindest in MS SQL Server nicht optimal. Ich weiß nicht, wie gut andere DBs das unterstützen, aber eine bessere Lösung ist, stattdessen direkt eine berechnete Spalte mit case-insensitiver COLLATE anzulegen
      ALTER TABLE example ADD name_ci AS name COLLATE SQL_Latin1_General_CI_AS;
      
      (je nach Geschmack anpassen)
    • Im zugehörigen Blog gibt es einen Tippfehler. In der ersten Zeile muss es großgeschrieben sein. Wenn ein Index bereits auf die durch die Funktion transformierten Daten gelegt wurde, passiert bei der Query kein Full Scan. In diesem Beispiel wäre es natürlich besser, von vornherein eine case-insensitive Collation zu verwenden, aber grundsätzlich ist die Idee stimmig
    • „Ich habe das am eigenen Leib erfahren und gelernt“ klingt wie das Motto von SQL-Entwicklern. Wie auch immer: SQL hat sich über lange Zeit ziemlich stabil entwickelt, daher ist es dauerhaft nützlich, solche Fallstricke früh zu kennen
 
ahwjdekf 2025-10-21

Das Wichtigste fehlt noch.

  • ein ORM verwenden