7 Punkte von GN⁺ 2024-09-26 | 2 Kommentare | Auf WhatsApp teilen
  • Eine Sammlung häufig genutzter SQL-Schreibgewohnheiten und Query-Patterns aus der Datenanalyse; mit dem Vorbehalt, dass sie nicht auf alle RDBMS gleichermaßen anwendbar sind
  • Für bessere Lesbarkeit werden führende Kommata, WHERE 1=1, Einrückung, CTEs, Kommentare und USING empfohlen, um Queries leichter lesbar und änderbar zu machen
  • Bei der Datenverarbeitung werden anhand von Beispielen Konstrukte wie Anti-Joins, QUALIFY, GROUP BY ROLLUP und EXCEPT behandelt, die in der Praxis zum Filtern von Ergebnissen, Erzeugen von Summen und Prüfen von Tabellenunterschieden genutzt werden
  • Bei Performance und Korrektheit können NOT IN mit NULL-Werten, implizite Typumwandlungen und Alias-Konflikte bei berechneten Feldern Query-Ergebnisse oder Geschwindigkeit beeinträchtigen
  • Bei komplexen Queries sind grundlegende Gewohnheiten wie das Verständnis der Ausführungsreihenfolge, das Prüfen der Dokumentation, das explizite Angeben der Spaltenherkunft und aussagekräftige Namen für gespeicherte Queries wichtig, um Debugging und Wiederverwendbarkeit zu verbessern

Gewohnheiten für besser lesbares SQL

  • Dieses Repository ist eine Sammlung von SQL-Tipps und -Tricks, die über mehrere Jahre gesammelt wurden. Im Mittelpunkt stehen Dinge, die im Alltag von Datenanalysten nützlich sind, sowie Dinge, die man gern schon beim ersten Schreiben von SQL gewusst hätte
  • Einige Tipps passen möglicherweise nicht zu jedem RDBMS
  • Führende Kommata und führendes AND

    • Für die Trennung von Feldern in der SELECT-Klausel wird empfohlen, statt nachgestellter Kommata führende Kommata zu verwenden
    • So ist klarer zu erkennen, ob es sich um eine neue Spalte oder um umgebrochenen Code handelt
    • Auch bei unterschiedlich langen Zeilen lässt sich leichter erkennen, ob ein Komma fehlt
    • Aus demselben Grund kann man Bedingungen in der WHERE-Klausel ebenfalls ein führendes AND voranstellen
  • Bedingungen mit WHERE 1=1 leichter testen

    • Wenn man in die WHERE-Klausel die Dummy-Bedingung 1=1 einfügt, bricht die Query beim Testen nicht, auch wenn Bedingungen auskommentiert werden
    • Selbst wenn alle Bedingungen auskommentiert werden, bleibt 1=1 übrig und die Query kann weiter ausgeführt werden
  • Einrückung und Formatter

    • Einrückung hilft Kollegen und dem zukünftigen Ich, Queries leichter zu lesen
    • Gibt es Team- oder Unternehmensrichtlinien, sollte man ihnen folgen; andernfalls ist ein eigener passender Stil sinnvoll
    • Man kann den Online-Formatter poorsql oder den Linter sqlfluff verwenden
  • Bei komplexen Queries CTEs in Betracht ziehen

    • Wenn inline views über zwei bis drei Ebenen oder mehr verschachtelt werden, entstehen leicht Queries, die nach einigen Wochen nur schwer wieder zu verstehen sind
    • CTEs werden als Möglichkeit dargestellt, lange Queries besser zu strukturieren und Wiederverwendung sowie Debugging zu erleichtern
  • Kommentare sollten das „Warum“ erklären

    • Nach einiger Zeit kann es schwerfallen, sich zu erinnern, warum eine bestimmte Verarbeitung vorgenommen wurde
    • Kommentare sollten in der Regel eher erklären, warum Code etwas tut, statt wie er funktioniert
    • Das Beispiel versieht eine Bedingung zum Ausschließen von Archiv-Inhalten mit einem Kommentar, weil ein neues CMS das Archive-Videoformat nicht verarbeiten kann
  • Joins auf gleichnamige Spalten mit USING

    • Wenn zwei Tabellen über Spalten mit demselben Namen gejoint werden, lässt sich der Join mit USING einfacher ausdrücken als mit ON
    • USING entfernt die gemeinsame Spalte im Ergebnis als Duplikat und gibt sie nur einmal zurück
    • Wenn bei ON die gemeinsame Spalte nicht explizit angegeben wird, kann ein Fehler ambiguous column name auftreten

Nützliche Konstrukte für die Datenverarbeitung

  • Mit Anti-Joins Zeilen finden, die in einer anderen Tabelle fehlen

    • Ein Anti-Join wird verwendet, um Zeilen zurückzugeben, die in einer Tabelle vorhanden sind, aber in einer anderen keine Entsprechung haben
    • Das Beispiel behandelt den Fall, nur die video_id von nicht archivierten Inhalten abzurufen
    • Es gibt mehrere Implementierungsvarianten
    • Nach einem LEFT JOIN nur Zeilen filtern, bei denen der Schlüssel der gematchten Tabelle NULL ist
    • NOT IN mit einer Subquery verwenden
    • NOT EXISTS mit einer korrelierten Subquery verwenden
    • NOT IN wird nicht empfohlen, da es wegen NULL-Werten möglicherweise nicht wie beabsichtigt funktioniert
  • Ergebnisse von Window Functions mit QUALIFY filtern

    • QUALIFY ermöglicht es, Query-Ergebnisse anhand der Ergebnisse von Window Functions zu filtern
    • Dadurch kann ohne inline view gefiltert und die Anzahl der Codezeilen reduziert werden
    • Das Beispiel wählt mit DENSE_RANK() die Top-10-Märkte je Produkt aus und filtert anschließend mit QUALIFY
    • Als Einschränkung gilt, dass QUALIFY offenbar nur in großen Data Warehouses wie Snowflake, Amazon Redshift und Google BigQuery verfügbar ist
  • GROUP BY und ORDER BY anhand der Spaltenposition

    • Statt Spaltennamen kann man die Spaltenposition verwenden, etwa GROUP BY 1 oder ORDER BY 2
    • Für temporäre oder einmalige Queries kann das nützlich sein
    • Für Produktionscode wird empfohlen, immer direkt auf Spaltennamen zu verweisen
  • Summen mit GROUP BY ROLLUP erzeugen

    • GROUP BY ROLLUP kann genutzt werden, um Zwischensummen und Gesamtsummen zu erzeugen
    • Das Beispiel berechnet Gehaltssummen nach Abteilung und erzeugt zugleich eine Zeile mit der gesamten Gehaltssumme
    • Die Transact-SQL-Dokumentation erklärt, dass ROLLUP Gruppen für Kombinationen von Spaltenausdrücken erzeugt und die Anzahl der Gruppen von rechts nach links reduziert, um Zwischen- und Gesamtsummen zu bilden
    • Mit COALESCE kann die Summenzeile als Total angezeigt werden
    • Man muss auf die Sortierspalte achten, damit die Summenzeile am Ende des Ergebnisses erscheint
  • Unterschiede zwischen zwei Ergebnismengen mit EXCEPT finden

    • EXCEPT gibt Zeilen zurück, die im Ergebnis der ersten Query enthalten sind, aber nicht im Ergebnis der zweiten
    • Mit EXCEPT zusammen mit UNION ALL kann geprüft werden, ob zwei Tabellen dieselben Daten enthalten
    • Wenn keine Zeilen zurückgegeben werden, sind die beiden Tabellen identisch
    • Wenn Zeilen zurückgegeben werden, sind diese Zeilen die Ursache der Unterschiede

Patterns, die Performance und Korrektheit beeinträchtigen

  • Bei Spalten, die NULL enthalten können, ist NOT EXISTS besser als NOT IN

    • Wenn die Vergleichsspalte NULL zulässt, kann NOT IN in der Regel langsamer sein als NOT EXISTS
    • Dieses Phänomen trat in Snowflake auf; das PostgreSQL-Wiki schreibt in Don’t Do This, dass NOT IN (SELECT ...) nicht gut optimiert wird
    • NOT IN funktioniert nicht wie beabsichtigt, wenn die Vergleichswerte NULL enthalten
    • Dass eine Spalte NULL zulässt, bedeutet nicht, dass tatsächlich NULL-Werte vorhanden sind. Wenn man jedoch mit Tabellen arbeitet, die man nicht ändern kann, kann NOT EXISTS helfen, die Geschwindigkeit zu verbessern
  • Implizite Typumwandlungen können verlangsamen oder fehlschlagen

    • Wenn in einer Bedingung ein Wert mit einem anderen Datentyp als die Spalte verwendet wird, kann die Datenbank eine implizite Typumwandlung versuchen
    • Das Beispiel behandelt den Fall, dass die String-Spalte video_id mit der Integer-Zahl 200050 verglichen wird
    • Sich auf implizite Typumwandlungen zu verlassen, kann Probleme verursachen
    • Wenn Werte vorhanden sind, die sich nicht umwandeln lassen, kann ein Fehler auftreten
    • Die zusätzliche Arbeit, jeden Wert in den angegebenen Typ umzuwandeln, kann Queries verlangsamen
    • Man sollte denselben Datentyp wie die Spalte verwenden oder, um Fehler zu vermeiden, Funktionen wie Snowflakes TRY_TO_NUMBER nutzen
    • Die Auswirkungen auf die Geschwindigkeit hängen von der Größe des verarbeiteten Datensatzes ab

Häufige Fehler

  • NOT IN und NULL

    • NOT IN funktioniert nicht, wenn die Vergleichswerte NULL enthalten
    • Da NULL für Unknown steht, kann die SQL-Engine nicht verifizieren, dass der geprüfte Wert nicht in der Liste enthalten ist
    • In diesem Fall ist die Verwendung von NOT EXISTS eine Alternative
  • Alias-Konflikte bei berechneten Feldern

    • Wenn ein berechnetes Feld denselben Namen wie eine vorhandene Spalte erhält, kann unerwartetes Verhalten entstehen
    • Die GROUP BY-Dokumentation von Snowflake schreibt, dass bei einem Namen in der GROUP BY-Klausel, der sowohl zu einem Spaltennamen als auch zu einem Alias passt, der Spaltenname verwendet wird
    • Wenn im Beispiel mit LEFT(product, 1) AS product ein Alias erzeugt und GROUP BY product verwendet wird, wird nicht nach dem ersten Buchstaben, sondern nach der ursprünglichen Spalte product gruppiert, sodass drei Zeilen zurückgegeben werden
    • Es gibt zwei Lösungen
    • Einen eindeutigen Alias wie product_letter verwenden
    • Den Ausdruck explizit angeben, etwa GROUP BY LEFT(product, 1)
    • Auch bei Window Functions können Alias-Probleme auftreten
    • Im Beispiel wird der Revenue von Robot per CASE auf 0 gesetzt, aber dies wird erst nach der Ausführung der Window Function angewendet, wodurch das Ranking anders ausfällt als erwartet
    • Wo möglich, sollte man eindeutige Aliasse verwenden oder den berechneten Ausdruck direkt in das ORDER BY der Window Function schreiben
  • Angeben, zu welcher Tabelle eine Spalte gehört

    • In komplexen Queries mit mehreren Joins sollte es möglich sein, Probleme mit Werten bis zur Ursprungstabelle zurückzuverfolgen
    • Wenn zwei Tabellen denselben Spaltennamen teilen und die Spaltenzugehörigkeit nicht angegeben wird, kann das RDBMS einen Fehler ausgeben
    • Das Beispiel macht die Herkunft der Spalten durch Tabellenaliase wie vc.video_id und metadata.season eindeutig

Ausführungsreihenfolge, Dokumentation und gespeicherte Namen

  • SQL-Ausführungsreihenfolge verstehen

    • Als einer der wichtigsten Ratschläge für SQL-Lernende wird genannt, die Ausführungsreihenfolge der Klauseln zu verstehen
    • Wer die Ausführungsreihenfolge kennt, schreibt Queries möglicherweise deutlich anders
    • Als Referenz wird A beginner’s guide to the true order of SQL operations genannt
  • Dokumentation vollständig lesen

    • Es gibt einen Fall, in dem in Snowflake GREATEST() verwendet wurde, um aus mehreren Datumsspalten das neueste Datum zurückzugeben
    • GREATEST() gibt NULL zurück, wenn eines der Argumente NULL ist
    • Hätte man die Dokumentation weitergelesen, hätte man statt COALESCE(GREATEST(...), ...) GREATEST_IGNORE_NULLS() verwenden können
    • In vielen Fällen dauert das Überfliegen der Dokumentation weniger als eine Minute und kann den Aufwand reduzieren, die Ursache für unerwartetes Verhalten zu finden
  • Gespeicherte Queries aussagekräftig benennen

    • Um zu vermeiden, dass man Queries, die erneut ausgeführt oder referenziert werden müssen, nicht wiederfindet, sollte man sie mit aussagekräftigen Namen speichern
    • Der gespeicherte Name enthält üblicherweise das Thema der Query, den Ausführungsmonat und den Namen der anfragenden Person
    • Das Beispiel hat das Format Lapsed users analysis - 2023-09-01 - Olivia Roberts

2 Kommentare

 
hiyama 2024-09-26

Die führenden Kommas sind in diesem Beitrag alle als nachgestellte Kommas geschrieben. Im Original stehen sie führend.

-- Good:  
SELECT   
timeslot_date  
, timeslot_channel   
, overnight_fta_share  
, IFF(DATEDIFF(DAY, timeslot_date, CURRENT_DATE()) > 7, -- First argument of IFF.  
	LAG(overnight_fta_share, 1) OVER (PARTITION BY timeslot_date, timeslot_channel ORDER BY timeslot_activity), -- Second argument of IFF.  
		NULL) AS C7_fta_share -- Third argument of IFF.  
, IFF(DATEDIFF(DAY, timeslot_date, CURRENT_DATE()) >= 29,   
		LAG(overnight_fta_share, 2) OVER (PARTITION BY timeslot_date, timeslot_channel ORDER BY timeslot_activity),   
			NULL) AS C28_fta_share  
FROM timeslot_data  
;  
 
GN⁺ 2024-09-26
Hacker-News-Kommentare
  • Mein zusätzlicher Tipp wäre folgender: Lernt den DB-Server richtig kennen und prüft Ausführungspläne regelmäßig. Es können überraschende Ergebnisse herauskommen, daher sollte man anpassen und erneut prüfen.
    In der Regel ist EXISTS schneller als IN, und NOT EXISTS verhält sich bei der Behandlung von NULL anders als EXCEPT. Statt nach einem Tabellen-Join Zeilen mit etwas wie DISTINCT herauszufiltern, kann es oft deutlich schneller sein, Subquery-Spalten in der SELECT-Liste zu verwenden. Das kann sogar dann gelten, wenn man mehr als 10 Werte aus derselben Tabelle holt, und auch dann, wenn der DB-Server Lateral Joins unterstützt. Die Subquery darf allerdings höchstens eine Zeile zurückgeben.
    Abfragen, die nicht einmalig sind, sollten keinen Full Table Scan auslösen. Der Table Scan von heute kann der Ausfall von morgen sein, daher sollte man Indizes hinzufügen. Merkt euch auch, dass die GROUP BY-Klausel oft darüber entscheidet, ob ein Index genutzt wird.
    Wenn man nach einem Ausdruck filtern muss, etwa wenn geprüft werden soll, ob ein Teilstring einem bestimmten Wert entspricht, kann man eine berechnete Spalte hinzufügen und darauf einen Index legen. Manche DBs unterstützen auch direkt Ausdrucksindizes. Wenn man statt OR UNION ALL verwendet, wird es bei komplexen Abfragen oder mehreren OR-Bedingungen oft deutlich schneller.
    Wenn die DB die Filterreihenfolge nicht intelligent genug festlegt, kann es auch hilfreich sein, eine Subquery zu JOINen, um die Reihenfolge zu erzwingen.

    • Am nützlichsten ist es, das DBMS selbst zu lernen. Jede DB hat ihre Eigenheiten bei Performance und Isolation Levels, und auch die Zusatzfunktionen unterscheiden sich, daher kommt man nicht daran vorbei.
      Interessant bei Postgres war für mich, dass sich INSERT (SELECT ...)-Vorgänge nahezu linear beschleunigen lassen, wenn man sie passend zur Zahl der CPU-Kerne manuell shardet; möglicherweise gilt das auch für andere DBs. Das funktionierte sogar mit ungefähr 10 Joins. Man schaut sich zuerst mit EXPLAIN den innersten oder äußersten Join an und führt dann für jeden Zeilenbereich eine separate parallele Abfrage aus (id >= start AND id < end). Ich habe das vor 6 Jahren in einem Projekt aus merkwürdigen Gründen sehr oft gemacht. Postgres 10+ hat Parallelität hinzugefügt, aber soweit ich weiß, ist sie noch nicht so weit entwickelt.
    • Ich weiß nicht genau, was damit gemeint ist, in der SELECT-Liste Subquery-„Spalten“ zu verwenden.
      Wenn ich zum Beispiel SELECT column1, (SELECT column2, column3, ... FROM table_b WHERE table_a.id = table_b.a_id) FROM table_a ausführe, bekomme ich erwartungsgemäß „subquery must return only one column“. Ist gemeint, mehrere Spalten als Record-/Composite-Typ zurückzugeben?
      Mir war auch nicht sofort klar, warum die GROUP BY-Klausel oft die Indexnutzung bestimmt; für Interessierte erklärt dieser Artikel das Schritt für Schritt gut: https://www.brentozar.com/archive/2015/06/indexing-for-group...
    • Stimme zu. Man sollte EXPLAIN verwenden und lernen, es mit dem bevorzugten Tool zu interpretieren. Außerdem sollte man Abfragen überwachen.
      In einem früheren Startup habe ich PgHero installiert, und das war wirklich eine große Hilfe bei Performance-Optimierung und Priorisierung.
    • Selbst gut entworfene Abfragen verhalten sich oft anders als erwartet. Typische Gründe sind nicht aktualisierte Spaltenstatistiken oder fragmentierte Daten in großen Tabellen. Ein Beispiel dafür sind zufällige Primary-Key-Inserts.
    • Der Aussage „Abfragen, die nicht einmalig sind, sollten keinen Full Table Scan machen. Der Table Scan von heute kann der Ausfall von morgen sein“ stimme ich nicht zu.
      Es gibt Abfragen, bei denen ein Full Table Scan die effizienteste Zugriffsstrategie ist. Das gilt meist für analytische oder aggregierende Abfragen über die ganze Tabelle, und manchmal ist ein Table Scan sogar besser, wenn nur 50 % aller Zeilen geholt werden.
      Ich sehe auch nicht, wie ein schreibgeschützter Table Scan zu einem Ausfall führen soll. Er blockiert keinen konkurrierenden Zugriff. Der Nachteil ist nur die höhere I/O-Last; wenn der Server das nicht verkraftet, ist er ohnehin gravierend unterdimensioniert.
  • Die 3 Beispiele im Abschnitt „Lesbarkeit“ wirken seltsam. Die ersten beiden opfern buchstäblich Lesbarkeit, um das Schreiben zu erleichtern, und das letzte ist selbst mit Einrückung kaum zu retten, so schwer lesbar und monströs ist es.

    • Das Format mit führenden Kommas hat neben der Lesbarkeit noch weitere Vorteile. In einem Versionsverwaltungssystem zum Beispiel führt das Format eine Zeile pro Argument + führendes Komma dazu, dass Änderungen an Argumenten nur als einzeilige Diffs erscheinen.
      Ich denke, Entwickler schauen sich Commit-Historien fast genauso oft an wie den eigentlichen Quellcode.
    • Ich finde das Aussehen der ersten beiden Konventionen auch nicht besonders schön, aber es sind Konventionen, die Leute, die tatsächlich SQL schreiben, in der Praxis wirklich verwenden. Ich verstehe auch, warum sie existieren.
      Ich sehe sie inzwischen oft genug, dass sie mich kaum noch stören.
    • Als Alternative kann man SQL auch komplett verwüsten, als hätte ein Dreijähriger gerade MSPaint entdeckt, dann auf den „beautifier“-Button drücken und früh Mittagessen gehen.
    • Ich weiß nicht, warum du das für schlechter hältst.
      Ich sehe daran kein Problem.
      Falsch wirkt daran auch nichts.
    • Wer teilt in einem SELECT-Block die Spalten auf einzelne Zeilen auf und lässt dann trotzdem 150 Zeichen lange Zeilen stehen? Das ist eine kaputte Definition von Lesbarkeit. Von den Kommas will ich gar nicht erst anfangen.
      In Code Reviews schaut sich niemand lange Zeilen wirklich gründlich an. Das war das größte Problem von AngularJS. Merges wurden falsch durchgeführt und alles ging kaputt, weil einem ungefähr ab Spalte 90 die Augen zufallen. Ich war in mehr als einem halben Dutzend Teams mit Code Reviews, und es war immer dasselbe. Selbst wenn man sich dieses Problems sehr bewusst ist und versucht, es zu vermeiden, mache ich solche Fehler immer noch ungefähr halb so oft wie andere.
      Schreibt es etwas aufgeteilter. Besonders dann, wenn ihr anderen Beispiele zeigt.
  • Tipps für den Umgang mit komplexen Stored Procedures:

    1. Zu Beginn der Prozedur die permanente Tabelle sofort in eine temporäre Tabelle kopieren und nur die benötigten Zeilen auswählen/einschränken/filtern
    2. In der Mitte die temporäre Tabelle nach Bedarf bearbeiten
    3. Am Ende die permanente Tabelle innerhalb einer Transaktion aktualisieren. Wenn ein Fehler erkannt wird, die Transaktion sofort zurückrollen und die Prozedur beenden. Wenn man diese drei Schritte befolgt, verbessert sich die Nebenläufigkeit, und man kann die Prozedur neu starten, ohne Datenreste manuell aufräumen zu müssen
    4. Beim Umgang mit Remote-Tabellen ist äußerste Vorsicht geboten. Da sich Remote-Tabellen nicht innerhalb des aktuellen RDBMS befinden, ist die Wahrscheinlichkeit groß, dass Statistiken oder Indizes dieses RDBMS kaum genutzt werden können. In vielen Fällen ist es schneller, die komplette Remote-Tabelle in eine temporäre Tabelle zu dumpen/kopieren und dann damit zu arbeiten. Das Maximum, das man von einer Remote-Tabelle erwarten kann, ist meist die Ausführung einer WHERE-Klausel. Wenn man JOINs oder komplexe Operationen versucht, kommt es wahrscheinlich zu Timeouts
    5. Ausführungspläne sind leicht misszuverstehen. In manchen Fällen kann der Ausführungsplan auf zeilenweise Verarbeitung zurückfallen und die Performance zum Stillstand bringen. Oft ist es besser, komplexe Stored Procedures in kleine Schritte mit temporären Tabellen aufzuteilen
    6. Um zu sehen, was das RDBMS tatsächlich macht, sollte man immer den Ausführungsplan prüfen
    • Ich habe schon einmal die Anwendung von Punkt 5 in einem Fall zurückgenommen, in dem sie nicht wirklich nötig war, und dadurch die Query-Performance stark verbessert. Manchmal ist es deutlich ineffizienter, eine Query in mehrere kleine Queries aufzuteilen, als dem Query-Optimierer die gesamte Query zu geben und ihn den optimalen Weg finden zu lassen
      Wenn man Punkt 5 ohne Punkt 6 anwendet, merkt man womöglich gar nicht, dass man etwas Nichtoptimales tut. Mein Rat ist, vorzeitige Optimierung zu vermeiden, zunächst auf die intuitivste Weise zu schreiben und nur bei Bedarf zu optimieren. Am wichtigsten ist, SQL nicht prozedural zu schreiben. Man beschreibt die gewünschten Daten, statt der Engine vorzuschreiben, wie sie sie holen soll
    • Ich mag es nicht, massenhaft temporäre Tabellen verwenden zu müssen, aber ich stoße oft auf Queries, die niemals fertig würden, wenn ich sie dem Query Planner überlasse. Wie bei Compilern werden auch die Fähigkeiten des Query Planners stark überschätzt
      Andererseits versieht Microsoft das Thema ständig mit Warnungen, als ob der Query Planner es am besten wüsste und man nicht versuchen sollte, daran zu tunen
    • Diese Regeln können für die DB eines bestimmten Vendors vollkommen zutreffen, aber andere Datenbanken können ganz andere Prioritäten, Eigenschaften und Trade-offs haben
      Auch die Version der DB kann Einfluss haben
    • Punkt 1 bis 3 sind in Ordnung, wenn man garantieren kann, dass die Datenmenge vernünftig ist. Wenn die Daten für die Hardware jedoch zu groß werden, kann das Kopieren großer Datensätze und das anschließende Aktualisieren großer Datensätze erheblichen Overhead verursachen
  • Ich mag keine Entwicklung „für alle Fälle“. Das gilt für Interfaces genauso wie für Platzhalter wie where 1=1
    Mach es, wenn es gebraucht wird. Tu es nicht nur, weil man es vielleicht irgendwann in der Zukunft brauchen könnte. Produktionscode ist kein Ort, um Entwicklungshilfen stehen zu lassen. Während der Entwicklung kannst du machen, was du willst, aber in Produktionscode sind Lesbarkeit und klare Intention viel wichtiger

    • Schreibst du Verweise auf Tabellen- und Spaltennamen immer vollständig qualifiziert? Ich habe oft erlebt, dass das die Lesbarkeit um Größenordnungen verbessert, aber es wird schnell sehr wortreich und ist beim Schreiben unglaublich mühsam und langweilig
  • Noch etwas zu „Anti-Joins“. Wenn man nur prüfen will, ob in einer anderen großen Tabelle oder Unterabfrage Zeilen existieren, die eine Bedingung erfüllen, sollte man EXISTS statt IN oder LEFT JOIN verwenden
    EXISTS liefert wahr, sobald ein passender Eintrag gefunden wird. Bei LEFT JOIN und IN sammelt die Engine vor der Auswertung alle Ergebnisse

    • Das fand ich etwas verwirrend. In allen Fällen, die ich getestet habe, erzeugte (NOT) EXISTS einen besseren oder denselben Ausführungsplan wie (LEFT) JOIN oder (NOT) IN
      Außerdem ist die Absicht klarer
  • Zum Thema „Code kommentieren“ wird zumindest bei MSSQL oft empfohlen, in Kommentaren /**/ statt -- zu verwenden. Das liegt daran, dass Funktionen wie der Query Store Queries oft ohne Zeilenumbrüche speichern; wenn man die Query von dort holt, muss man dann alles manuell korrigieren, statt direkt den Formatter der IDE zu verwenden

    • Das klingt wie ein Bug im Query Store
    • Kann man nach XML casten? Ich verwende das bei OBJECT_DEFINITION
      select name,cast((select OBJECT_DEFINITION(object_id) for xml path('')) as xml) from sys.procedures
      Da Zeilenumbrüche erhalten bleiben, kann das das Aufräumen erleichtern. Allerdings werden andere XML-Zeichen zerstört, so wie > zu > wird. Eine weitere Option wäre die Verwendung von VARBINARY und irgendetwas, das es wieder zurückwandelt
  • Alle regen sich über den Komma-Vorschlag auf, aber halten 1=1 in der WHERE-Klausel für eine gute Idee? Wenn ich das in einem Code-Review sehe, weiß ich nicht, was ich von der Person halten soll

    • Man kann es aus demselben Grund rechtfertigen wie ein nachgestelltes Komma: Änderungen an der WHERE-Anweisung wirken sich nicht auf andere Zeilen aus, wodurch Code-Reviews einfacher werden
      Aber wenn der Grund wie hier das spätere Hinzufügen dynamischer Bedingungen ist, würde man dort, wo ich arbeite, ganz sicher gefeuert werden
  • Kann jemand allgemeine Richtlinien dazu teilen, wo man die Grenze ziehen sollte zwischen Leistungssteigerungen durch DB-Konfiguration – also einem Ansatz, der fast schon einem „Kauf“ gleichkommt – und einem „Build“-Ansatz, bei dem man Dinge faktisch manuell implementiert? Nach meiner begrenzten Erfahrung landet diese Arbeit oft bei App-Entwicklern, weil fähige DBAs deutlich besser bezahlt werden und anderswo arbeiten. Wie oben gesagt: Es ist wichtig, die DB zu verstehen
    Ein typisches Beispiel sind Daten, die sich im Laufe der Zeit in großen Mengen ansammeln und bei denen auf die neuesten Daten am häufigsten zugegriffen wird. Ein DBA kann den Zugriff mit Partitionierung oder partiellen Indizes schnell halten, während ein App-Entwickler Datensätze im Hintergrund in separate Archivtabellen verschieben und dabei weiterhin Dinge wie die endgültige Suche über den gesamten Datenbestand unterstützen kann. Es fühlt sich auch so an, als könnten Tools die anfängliche Arbeit, eine Tabelle zum richtigen Zeitpunkt in mehrere aufzuteilen, ziemlich stark automatisieren, etwa wenn man durch fehlende Funktionen einer Cloud-DB eingeschränkt ist
    Eine weitere Verwaltungsoption ist, große Blobs/Dateien komplett in einer separaten Datenbank oder im Dateisystem zu speichern, um andere Storage-Einstellungen zu nutzen. Auch das ist etwas, das die DB übernehmen kann oder das man manuell behandelt
    Im Extremfall könnte man sogar so weit gehen, Indizes selbst zu implementieren. Man hat eine riesige Tabelle mit einem einzigen autoinkrementierenden Primärschlüssel und unzähligen Spalten und erstellt dann separat eine Tabelle mit dieser ID und einigen durchsuchbaren Spalten. Das könnte bis hin zu Full-Text-Suche oder Vektoren gehen
    Ein nützlicher Tipp für die manuelle Umsetzung des Materialized-View-Patterns in MSSQL 2016+ ist die kombinierte Nutzung von Partition Switching. Das wird unter https://github.com/cajuncoding/SqlBulkHelpers?tab=readme-ov-... gut beschrieben und implementiert. Es war eine kleine Bibliothek, die ich zufällig gefunden habe, kommerziell aber als besonders nützlich empfand, obwohl sie im Ranking weit unten stand und nur wenige Sterne hatte; sie konzentriert sich auf Bulk Inserts in MSSQL mit .NET. Ich halte das für ein gutes Beispiel dafür, die Grenze zwischen Buy und Build durch die Automatisierung von Partition Switching sinnvoll zu ziehen

  • Was fehlt: Man sollte aufhören, SELECT * zu verwenden. Mit ziemlicher Sicherheit braucht man nicht die volle Tabellenbreite, und so erhöht man nur die Datenmenge, die gefiltert und übertragen werden muss, und verhindert außerdem das schöne Feature Semi Join

    • Menschen, die SQL verwenden, lassen sich grob in zwei Gruppen einteilen: Analysten und Entwickler
      Wenn man Entwickler ist, dann ja. SELECT * hat Fallstricke, und man sollte fast immer Spalten explizit angeben oder stattdessen einen Query Builder verwenden, der das für einen übernimmt
      Wenn man aber Analyst ist, ist das Leben kurz, und manchmal möchte man nicht jede einzelne Spalte eintippen. Dann ist SELECT * auch okay
  • Vielleicht etwas off-topic, aber ist es akzeptabel, wenn ein Maintainer einen Pull Request einfach schließt, ohne irgendwelche Kommentare oder Diskussion?
    Ich frage aus der Perspektive von jemandem, der gelegentlich zu dem Repository beigetragen hat oder beitragen wollte
    Beispiel: https://github.com/ben-n93/SQL-tips-and-tricks/pulls?q=is%3A...