🗂️Alle 35 Tipps
Filtern nach Kategorie, Dialekt und Niveau – oder einfach suchen. Jede Karte führt direkt zum ausführbaren Beispiel.
Einfügen oder aktualisieren – in einer Anweisung
Eine Preisliste vom Lieferanten enthält bekannte und neue Artikel. Mit SELECT prüfen und danach INSERT oder UPDATE schicken ist umständlich – und bei gleichzeitigen Zugriffen falsch.
Zähler atomar hochzählen
Seitenaufrufe sollen pro Seite gezählt werden. „Wert lesen, +1 rechnen, zurückschreiben“ in der Anwendung verliert bei gleichzeitigen Aufrufen Zählungen (Lost Update).
Nur einfügen, wenn neu – und sehen, was wirklich eingefügt wurde
Newsletter-Anmeldungen kommen mehrfach. Vorhandene Adressen sollen still übersprungen werden, ohne dass der Import abbricht – aber man will wissen, welche Adressen tatsächlich neu waren.
Tabellen abgleichen: Lieferung in den Bestand buchen
Eine Lieferung (Staging-Tabelle) soll in artikel gebucht werden: vorhandene Artikel bekommen mehr Bestand und den neuen Preis, unbekannte werden angelegt.
INSERT OR REPLACE ist kein Upsert
INSERT OR REPLACE (SQLite) bzw. REPLACE INTO (MySQL) sieht aus wie ein Upsert. Tatsächlich wird die kollidierende Zeile gelöscht und eine neue eingefügt.
Race Condition: „erst prüfen, dann einfügen“
Die Anwendung prüft mit SELECT, ob eine E-Mail schon existiert, und fügt sie sonst ein. Kommen zwei Anfragen gleichzeitig, sehen beide „gibt es noch nicht“ – und beide fügen ein.
Preis-Historie mit gültig_von / gültig_bis (SCD Typ 2)
Ein Preis wird überschrieben – und niemand weiß mehr, was der Artikel im Februar gekostet hat. Rechnungen, Reklamationen und Auswertungen brauchen aber den damals gültigen Preis.
Audit-Log: wer hat was geändert – alt und neu als JSON
Für Nachvollziehbarkeit (Revision, DSGVO-Auskunft, Fehlersuche) soll jede Änderung an kunden protokolliert werden – mit altem und neuem Zustand, ohne für jede Spalte eine eigene Log-Spalte zu pflegen.
System-versionierte Tabellen: die Datenbank führt die Historie
Selbstgebaute Historien-Trigger sind Code, der gepflegt, getestet und bei jeder Schemaänderung angepasst werden muss.
Zeitreise-Abfrage: „Wie war der Stand am …?“
Aus einer Historientabelle soll der Stand zu einem Stichtag gelesen werden – genau eine gültige Version je Artikel, auch an Tagen, an denen sich ein Preis ändert.
BEFORE-Trigger zur Validierung
Bestellungen mit Betrag ≤ 0 oder ohne gültigen Status sollen gar nicht erst in die Tabelle gelangen – egal, welche Anwendung schreibt.
Abgeleitete Werte: Generated Column oder Trigger?
Eine Positionssumme (menge × einzelpreis) und die Bestellsumme sollen immer stimmen. Die Anwendung vergisst gern mal, sie mitzupflegen.
INSTEAD OF: Sichten beschreibbar machen
Die Shop-Oberfläche arbeitet mit Bruttopreisen, gespeichert wird netto. Eine Sicht zeigt preis_brutto – aber ein UPDATE auf eine berechnete Spalte ist nicht möglich.
FOR EACH ROW oder FOR EACH STATEMENT?
Ein Preis-Update für die ganze Kategorie „Büro“ ändert drei Zeilen. Wie oft feuert der Trigger – dreimal oder einmal? Bei Massen-Updates mit 100 000 Zeilen ist das der Unterschied zwischen Millisekunden und Minuten.
Kaskaden und Rekursion: Trigger lösen Trigger aus
Wechselt eine Teamleitung die Abteilung, soll das ganze Team folgen – auch die Mitarbeitenden eine Ebene tiefer. Ein Trigger, der dieselbe Tabelle ändert, müsste sich selbst erneut auslösen.
Duplikate finden mit GROUP BY … HAVING
Nach einem Import stehen Kontakte mehrfach in der Tabelle. Welche E-Mail-Adressen kommen öfter als einmal vor – und in welchen Zeilen?
Genau eine Zeile je Gruppe behalten (ROW_NUMBER)
Von jeder mehrfach vorhandenen E-Mail soll nur der neueste Datensatz übrig bleiben. Welche Zeile „gewinnt“, muss eindeutig festgelegt sein.
Ältesten behalten mit EXISTS-Selbstjoin
Nicht jede Datenbank (oder ältere Version) hat Window Functions. Gesucht ist ein Weg, der überall funktioniert.
Unscharfe Duplikate: normalisieren vor dem Vergleich
Anna.Berger@example.org (Großbuchstaben, Leerzeichen am Ende) und anna.berger@example.org sind dieselbe Adresse – ein exakter Vergleich findet sie nicht.
Duplikate verhindern: UNIQUE auf den normalisierten Wert
Aufräumen hilft nur bis zum nächsten Import. Die Datenbank soll neue Dubletten selbst ablehnen – auch in Groß/Klein-Varianten.
Gaps & Islands: Lücken in Nummernfolgen
Rechnungsnummern müssen lückenlos sein – der Steuerprüfer fragt nach 1004, 1007, 1008, 1013 und 1014. Welche zusammenhängenden Bereiche („Inseln“) gibt es, und wo sind die Lücken?
Top-N je Gruppe: die zwei größten Bestellungen pro Kunde
ORDER BY betrag DESC LIMIT 2 liefert die zwei größten Bestellungen insgesamt – gesucht sind aber die zwei größten je Kunde.
Laufende Summen – und die RANGE-Falle
Für eine Umsatzkurve braucht es den kumulierten Umsatz je Tag. Am 03.03. gibt es zwei Bestellungen – und plötzlich stehen dort zwei gleiche Zwischensummen.
Pivot und Unpivot mit CASE-Aggregation
Der Vertrieb will eine Kreuztabelle: Kunden in Zeilen, Monate in Spalten. Und umgekehrt: eine breite Tabelle mit Quartalsspalten soll wieder „lang“ werden.
Rekursive CTE: Organigramm mit Pfad und Ebene
mitarbeiter.chef_id verweist auf die eigene Tabelle. Wer steht wie tief unter der Geschäftsführung, und wie sieht der Weg von oben aus?
Stückliste auflösen: Mengen über Ebenen multiplizieren
Ein Regal besteht aus Seitenteilen, Böden und einem Beschlagset; das Beschlagset wiederum aus Schrauben und Dübeln. Wie viele Schrauben braucht man für 3 Regale?
Datumsreihe erzeugen: Tage ohne Umsatz sichtbar machen
Ein Umsatzdiagramm pro Tag zeigt nur Tage mit Bestellungen – Lücken verschwinden, die Kurve lügt.
Anti-Join: NOT EXISTS statt NOT IN (NULL-Falle)
Newsletter nur an Kunden, die nicht auf der Sperrliste stehen. Mit NOT IN (SELECT email FROM sperrliste) kommt: gar nichts. Warum?
COALESCE und NULLIF: Standardwerte und Division durch null
Eine Kampagne hatte 0 Klicks – die Konversionsrate kaeufe / klicks bricht in PostgreSQL und SQL Server mit „Division durch null“ ab. Und fehlende E-Mails sollen im Bericht als „(keine)“ erscheinen.
Datumsgrenzen: halb-offene Intervalle statt BETWEEN
„Alle Logins im Februar“ mit BETWEEN '2026-02-01' AND '2026-02-28' verliert alle Logins am 28. nach Mitternacht – denn '2026-02-28 18:30' ist größer als '2026-02-28'.
Soft Delete: löschen, ohne zu löschen
Gelöschte Kunden sollen wiederherstellbar bleiben und in Auswertungen der Vergangenheit auftauchen. Aber: Wer sich nach dem Löschen mit derselben E-Mail neu registriert, scheitert am UNIQUE-Index.
Paginierung: OFFSET vs. Keyset (Seek-Methode)
LIMIT 20 OFFSET 100000 ist bequem – aber die Datenbank muss die 100 000 übersprungenen Zeilen trotzdem lesen und verwerfen. Und kommt zwischen zwei Seiten ein neuer Datensatz dazu, verrutscht alles.
Sicheres Batch-Delete: große Mengen in kleinen Portionen
Protokolleinträge vor 2026 sollen weg – bei 50 Millionen Zeilen sperrt ein einziges DELETE die Tabelle minutenlang, bläht Log/WAL auf und lässt Replikate hinterherhinken.
JSON-Spalten abfragen und aufklappen
Bestelldetails (Zahlungsart, Tags) liegen als JSON in einer Spalte. Gesucht: alle Kartenzahlungen und eine Liste „wie oft kommt welcher Tag vor“.
EXPLAIN in 60 Sekunden: SCAN oder SEARCH?
Eine Abfrage ist langsam. Bevor man rät, fragt man die Datenbank, wie sie die Abfrage ausführen will.