🚫 Keine Funktionen in der WHERE-Klausel!
Warum Funktionen auf Spalten den Index unbrauchbar machen – und wie Sie es besser machen
📌 Das Problem – Ein alltäglicher Fall
ÜBERBLICK
Stellen Sie sich vor, Sie suchen alle Bestellungen, die heute aufgegeben wurden. Der naheliegende Code:
Diese Abfrage ist logisch korrekt, aber leider extrem langsam auf einer großen Tabelle. Warum? Weil die Funktion
SELECT * FROM dbo.Bestellungen
WHERE CAST(Bestelldatum AS DATE) = CAST(GETDATE() AS DATE);
CAST(Bestelldatum AS DATE) auf der Spalte den Index auf der Spalte „Bestelldatum“ unbrauchbar macht. SQL Server muss jede einzelne Zeile der Tabelle lesen, die Funktion anwenden und dann den Vergleich durchführen – ein vollständiger Tabellenscan. Genau dieses Problem behandelt dieser Artikel.
💡 Kernaussage: Funktionen auf Tabellenspalten in der WHERE-Klausel sind ein Performance-Killer. Der SQL-Optimierer kann dann keinen Index mehr verwenden, weil der tatsächliche Wert der Spalte erst zur Laufzeit transformiert wird.
🔍 Was bedeutet „sargable“?
INDEX NUTZUNG
Ein Prädikat (die WHERE-Bedingung) heißt sargable, wenn der Optimierer einen Index auf der betreffenden Spalte für eine Index-Suche (Seek) nutzen kann. Dazu muss die Spalte in ihrer nativ gespeicherten Form mit einem Wert verglichen werden – ohne Funktion, ohne Typkonvertierung, ohne Berechnung. Sobald Sie eine Funktion auf die Spalte anwenden, wird die Bedingung nicht sargable. Der Index kann höchstens für einen Scan genutzt werden (Index Scan), was bei großen Datenmengen fast genauso teuer ist wie ein Tabellenscan.
-- Nicht sargable (schlecht)
WHERE YEAR(Bestelldatum) = 2025
WHERE LEFT(Nachname, 3) = 'Sch'
WHERE UPPER(Email) = Diese E-Mail-Adresse ist vor Spambots geschützt! Zur Anzeige muss JavaScript eingeschaltet sein. '
WHERE ISNULL(Rabatt, 0) = 0
-- Sargable (gut)
WHERE Bestelldatum BETWEEN '2025-01-01' AND '2025-12-31'
WHERE Nachname LIKE 'Sch%'
WHERE Email = Diese E-Mail-Adresse ist vor Spambots geschützt! Zur Anzeige muss JavaScript eingeschaltet sein. ' -- bei case-insensitiver Kollation
WHERE Rabatt = 0 OR Rabatt IS NULL
📌 Merksatz: Ein sargables Prädikat erlaubt einen Index Seek. Ein nicht sargables Prädikat erlaubt nur einen Index Scan (oder Table Scan).
⚠️ Häufige nicht sargable Muster
FALLSTRICKE
- Datumsfunktionen:
YEAR(Spalte), MONTH(Spalte), DAY(Spalte), DATEPART(...), CAST(Spalte AS DATE) - String-Funktionen auf der Spalte:
LEFT(Spalte, n), SUBSTRING(Spalte, ...), CHARINDEX(..., Spalte), LEN(Spalte) - Typkonvertierung auf der Spalte:
CAST(Spalte AS INT), CONVERT(..., Spalte), wenn der Datentyp nicht mit dem Vergleichswert übereinstimmt. - Mathematische Operationen:
Spalte * 2 = 100(besserSpalte = 50) - ISNULL oder COALESCE auf der Spalte:
ISNULL(Spalte, 0) = 0 - LIKE mit führendem Wildcard:
Spalte LIKE '%abc'– hier kann auch kein Index genutzt werden (nurLIKE 'abc%'ist sargable).
-- Schlecht: Alle Bestellungen eines Jahres über Funktion
SELECT * FROM Bestellungen
WHERE YEAR(Bestelldatum) = 2024;
-- Besser: Bereichsabfrage (sargable)
SELECT * FROM Bestellungen
WHERE Bestelldatum >= '2024-01-01' AND Bestelldatum < '2025-01-01';
💡 Hinweis: Manche Entwickler glauben, dass
YEAR(Spalte) = 2024 genauso schnell ist wie eine Bereichsabfrage. Das ist ein Irrglaube – nur die Bereichsabfrage nutzt den Index.
✅ Lösung 1: Bereichsabfragen für Datums- und Zahlenbereiche
OPTIMIERUNG
Statt eine Funktion auf die Spalte anzuwenden, formen Sie die Abfrage so um, dass die Spalte selbst mit einem berechneten Wert verglichen wird. Für Datumsangaben bedeutet das:
-- Statt CAST(Bestelldatum AS DATE) = CAST(GETDATE() AS DATE)
WHERE Bestelldatum >= CAST(GETDATE() AS DATE)
AND Bestelldatum < DATEADD(day, 1, CAST(GETDATE() AS DATE));
-- Für ein bestimmtes Jahr:
DECLARE @year INT = 2024;
WHERE Bestelldatum >= '2024-01-01' AND Bestelldatum < '2025-01-01';
💡 Tipp: Berechnen Sie die Bereichsgrenzen einmal in Variablen, damit sie nicht für jede Zeile neu berechnet werden müssen. SQL Server optimiert das aber auch automatisch, wenn Sie Literale oder Konstanten verwenden.
📐 Lösung 2: Persistente berechnete Spalten + Index
FORTGESCHRITTEN
Wenn Sie zwingend eine Funktion auf die Spalte anwenden müssen (z. B.
LEFT(Spalte, 3)), können Sie eine persistente berechnete Spalte einführen, auf die Sie dann einen Index erstellen. Dann wird das Funktionsergebnis vorberechnet und indexiert.
-- Tabelle mit berechneter Spalte (persistent)
CREATE TABLE dbo.Kunden (
KundenID INT PRIMARY KEY,
Nachname NVARCHAR(100),
Nachname_Drei AS LEFT(Nachname, 3) PERSISTED
);
-- Index auf der berechneten Spalte
CREATE INDEX IX_Kunden_Nachname_Drei
ON dbo.Kunden (Nachname_Drei);
-- Abfrage nutzt jetzt den Index
SELECT * FROM dbo.Kunden
WHERE Nachname_Drei = 'Sch';
⚠️ Einschränkung: Berechnete Spalten dürfen keine nicht deterministischen Funktionen enthalten (z. B.
GETDATE()) und müssen deterministisch sein. Zudem verbrauchen sie zusätzlichen Speicherplatz.
🚀 Lösung 3: Index auf Ausdruck (ab SQL Server 2022)
NEU
Seit SQL Server 2022 können Sie Indizes auf beliebige Ausdrücke (nicht nur Spalten) erstellen, ohne eine persistente berechnete Spalte zu benötigen. Das ist die eleganteste Lösung für nicht sargable Prädikate.
-- Index auf Ausdruck (keine separate Spalte nötig)
CREATE INDEX IX_Bestellungen_Year
ON dbo.Bestellungen (YEAR(Bestelldatum));
-- Jetzt funktioniert die YEAR-Abfrage mit Index-Suche
SELECT * FROM dbo.Bestellungen
WHERE YEAR(Bestelldatum) = 2024;
💡 Wichtig: Diese Syntax ist noch relativ neu. Prüfen Sie, ob Ihre SQL Server-Version dies unterstützt. Für ältere Versionen (2019 und älter) müssen Sie auf persistente berechnete Spalten ausweichen.
🔄 Typkonvertierung – Der versteckte Performancekiller
DATA TYPES
Auch implizite oder explizite Typkonvertierungen auf der Spalte zerstören die Sargbarkeit. Besonders häufig: Eine
VARCHAR‑Spalte wird mit einem NVARCHAR-Literale verglichen, oder eine DATE-Spalte mit einem DATETIME-Literale.
-- Problem: Spalte ist VARCHAR, aber Literal ist NVARCHAR (führt zu Konvertierung)
WHERE Spalte = N'Text'; -- N'...' ist NVARCHAR
-- Besser: Einheitliche Datentypen
WHERE Spalte = 'Text';
-- Problem: Vergleich von DATE mit DATETIME
WHERE Bestelldatum = '2025-01-01 00:00:00'; -- implizite Konvertierung
-- Besser: Gleicher Datentyp
WHERE Bestelldatum = '2025-01-01';
🚨 Besonders tückisch: Wenn Sie eine Spalte vom Typ
VARCHAR mit einem numerischen Wert vergleichen (WHERE Spalte = 123), wird die Spalte in einen numerischen Typ konvertiert – wieder kein Index.
⚖️ Ausnahmen von der Regel
BESONDERHEITEN
Nicht jede Funktion in WHERE ist automatisch schlecht. Es gibt Konstellationen, in denen sie keinen oder nur geringen Schaden anrichtet:
- Die Tabelle ist sehr klein (wenige hundert Zeilen). Dann macht ein Scan keinen Unterschied.
- Die Funktion wird auf einer Konstanten ausgeführt (z. B.
WHERE Spalte = UPPER('abc')). Dann wird die Funktion nur einmal berechnet, die Spalte bleibt funktionsfrei. - Der Index kann nicht verwendet werden, weil die Selektivität ohnehin sehr gering ist (z. B. fast alle Zeilen werden ausgewählt).
- Sie verwenden einen Volltextindex (CONTAINS, FREETEXT) – das sind spezielle Funktionen, die extra indiziert werden.
-- Gut: Funktion auf der Konstanten (nicht auf der Spalte)
WHERE Nachname = UPPER('Müller') -- UPPER wird einmal berechnet
-- Schlecht: Funktion auf der Spalte
WHERE UPPER(Nachname) = 'MÜLLER'
💡 Merke: Die linke Seite des Vergleichs (die Spalte) sollte möglichst nackt dastehen.
📊 Ein kleines Experiment – Der Performance-Unterschied
BELEG
Stellen Sie sich eine Tabelle mit 1 Million Zeilen und einem Index auf einer Datumsspalte vor. Die folgende Abfrage mit
CAST benötigt oft 3‑4 Sekunden (Table Scan). Die optimierte Bereichsabfrage läuft in unter 50 Millisekunden (Index Seek) – ein Faktor von 60‑80. Je größer die Tabelle, desto drastischer der Unterschied.
-- Schreibweise A (schlecht) – Laufzeit: mehrere Sekunden
SELECT COUNT(*) FROM dbo.GrosseTabelle
WHERE CAST(ErstellDatum AS DATE) = '2025-01-01';
-- Schreibweise B (gut) – Laufzeit: Millisekunden
SELECT COUNT(*) FROM dbo.GrosseTabelle
WHERE ErstellDatum >= '2025-01-01' AND ErstellDatum < '2025-01-02';
🔍 Selbsttest: Probieren Sie es in Ihrer Umgebung aus. Aktivieren Sie den tatsächlichen Ausführungsplan und sehen Sie den Unterschied zwischen Index Scan (schlecht) und Index Seek (gut).
✅ Best Practices – So vermeiden Sie nicht sargable Prädikate
PRAXIS
- Prüfen Sie WHERE-Klauseln auf Funktionen auf Spalten – besonders
YEAR, MONTH, DAY, DATEPART, CAST, CONVERT, LEFT, SUBSTRING, UPPER, LOWER, ISNULL, COALESCE. - Verwenden Sie Bereichsabfragen für Datums- und Zahlenbereiche statt Funktionen.
- Achten Sie auf einheitliche Datentypen zwischen Spalte und Vergleichswert.
- Für Like-Abfragen vermeiden Sie führende Wildcards (
LIKE '%abc'ist nicht sargable). Nutzen Sie ggf. Volltextsuche. - Nutzen Sie persistente berechnete Spalten oder Index auf Ausdruck (ab SQL 2022), wenn Sie die Funktion nicht umschreiben können.
- Analysieren Sie Ausführungspläne – suchen Sie nach „Index Scan“ statt „Index Seek“ und prüfen Sie die Prädikate.
- Setzen Sie Code-Review-Regeln auf – keine Funktionen auf Spalten in WHERE-Klauseln, außer es gibt gute Gründe.
💡 Abschlussgedanke: Diese Regel ist eine der wirkungsvollsten für die tägliche SQL-Optimierung. Oft können Sie mit kleinen Umschreibungen Abfragen von Minuten auf Millisekunden beschleunigen.