T-SQL Ninja #37

COALESCE (und ein wenig ISNULL)

Was tut COALESCE?

Angenommen ihr habt in eurer Datenbank mehrere Spalten einer Tabelle, die alle NULL-Werte enthalten dürfen. Nun möchtet ihr den Wert der ersten Spalte, wenn dieser aber nicht vorhanden ist, dann den Wert der zweiten Spalte, wenn dieser nicht vorhanden ist, dann denn Wert der dritten Spalte und so weiter… Es gibt nun immer wieder Stimmen, die behaupten, dass COALESCE eigentlich wie ISNULL ist, nur eben anders als ISNULL dem ANSI-Standard entspricht. Doch es gibt noch weitere Unterschiede zwischen beiden Funktionen, die ihr heute kennenlernen sollt.

Wie unterscheidet sich COALESCE von ISNULL?

Der Hauptunterschied zwischen COALESCE und ISNULL ist, dass ihr bei ISNULL den Rückgabewert schon von vorneherein kennt, er ist nämlich der Datentyp der Spalte, die ihr als erstes Argument übergebt. Sehen wir uns das im Beispiel an. Dafür erzeugen wir zunächst eine Tabelle mit einer Spalte in die wir einen NULL-Wert einfügen:

DROP TABLE IF EXISTS #Ninja
CREATE TABLE #Ninja 
(
    NinjaHandle varchar(15) null
)
INSERT INTO #Ninja (NinjaHandle) 
VALUES (NULL)

Nun arbeiten wir mit dieser NULL-Zeile. Ersetzen wir den NULL-Wert zunächst mittels ISNULL:

SELECT 
    ISNULL(NinjaHandle, 'T-SQL Ninja') 
FROM #Ninja

Wie nicht anders zu erwarten steht in der Ergebnismenge der Wert „T-SQL Ninja“. Doch nun modifizieren wir die Abfrage leicht:

SELECT 
    ISNULL(NinjaHandle, 'Brilliantly querying T-SQL Ninja') 
FROM #Ninja

Nun erhalten wir zu unserer Überraschung nur noch den Wert „Brilliantly que“. Warum ist das so? Weil ISNULL den einzufügenden Wert als Wert von NinjaHandle, also als varchar der Länge 15 zurückgibt. Ist unser String nun länger als 15 Zeichen, so schneidet ISNULL ab und gibt uns nur die ersten 15 Zeichen zurück.

Anders ist das, wenn wir COALESCE verwenden. Auch hier erhalten wir bei der ersten Abfrage den Wert „T-SQL Ninja“ zurück:

SELECT 
    COALESCE(NinjaHandle, 'T-SQL Ninja')
FROM #Ninja

Anders ist das bei der zweiten Abfrage:

SELECT 
    COALESCE(NinjaHandle, 'Brilliantly querying T-SQL Ninja') 
FROM #Ninja

Hier bekommen wir die vollständige Zeichenkette zurückgeliefert, unabhängig vom Typ von NinjaHandle. Das heißt, während ISNULL uns eine Typensicherheit garantiert, kann COALESCE das nur bedingt leisten. Das bedeutet, wenn eure Anwendung auf eine Typensicherheit in der Rückgabe angewiesen ist, dann seid ihr mit ISNULL immer auf der sicheren Seite, mit COALESCE kann euch hingegen auch eine böse Überraschung blühen.

Sehen wir uns ein weiteres Beispiel an, das dieses Problem verdeutlicht. Dafür modifizieren wir zuerst die Ninja-Tabelle und fügen eine weitere Spalte hinzu:

ALTER TABLE #Ninja 
ADD NinjaSkillLevel INT NULL

Nun möchten wir mit dieser Spalte rechnen und sie als Modifikator eines Wertes verwenden, dafür teilen wir den Wert durch 2 wenn kein NinjaSkillLevel vorhanden ist, sonst durch das Skill-Level. In SQL gegossen sehen die beiden Varianten, die wir haben um das zu tun wie folgt aus:

SELECT
     5/ISNULL(NinjaSkillLevel, 2.00)
    ,5/COALESCE(NinjaSkillLevel, 2.00)
FROM #Ninja

Was nun geschieht ist, dass durch die Typensicherheit von ISNULL, die 2.00 als Integer-Wert 2 zurückgegeben wird, was zur Folge hat, dass die 5 durch die ganzzahlige 2 geteilt wird, hier liefert der SQL Server dann den ganzzahligen Wert zurück, also erhalten wir als Rückgabewert die 2. Im Fall von COALESCE hat die 2.00, die der Funktion übergeben wird aber eine höhere Genauigkeit als die Spalte NinjaSkillLevel, hier entscheidet sich der SQL Server also, den NinjaSkillLevel auf den Datentyp von 2.00 zu konvertieren, also eine implizite Konvertierung vorzunehmen. Dadurch teilen wir 5 durch eine Gleitkommazahl, was in einer Gleitkommazahl resultiert, wir erhalten hier also den korrekten Wert von 2.5 (mit einigen nachgestellten Nullen). Und tatsächlich finden wir auch im Ausführungsplan eine implizite Typenumwandlung:

Hier sehen wir zweierlei: einerseits, dass COALESCE intern in CASE-Statements umgewandelt wird, andererseits, dass der SQL Server implizit zum Datentypen numeric(12,2) umwandelt.

Doch neben der Gefahr eines unerwarteten Rückgabetypen hat die COALESCE-Funktion natürlich auch Vorteile. Einer der Vorteile ist, dass man ihr auch mehrere Spalten übergeben kann, so lange diese Typenkompatibel sind. Ein weiterer Vorteil ist, dass es auch möglich ist, wenn alle Spalten NULL-Werte enthalten, einen NULL-Wert zurück zu geben. Ein Seiteneffekt dieser Tatsache ist, dass wenn ihr eine abgeleitete Spalte mit ISNULL definiert, diese Spalte als Primärschlüssel verwendet werden kann, da ISNULL nicht NULL sein kann und der Rückgabewert für den SQL Server als Primärschlüssel in Frage kommt. Anders ist das bei COALESCE, der Rückgabewert hier kann theoretisch NULL sein, auch wenn ihr das mit einem konstanten Wert als letztem Argument faktisch unterbinden könnt. Deshalb schlägt der Versuch fehl, einen Primärschlüssel auf einer mit COALESCE abgeleiteten Spalte zu definieren.

Ihr solltet aus dem heutigen Dojo nicht die Nachricht mitnehmen, dass COALESCE nicht typensicher und damit böse ist. Vielmehr solltet ihr euch im Klaren darüber sein, dass hier unerwartete Datentypen zurückgegeben werden können und gegebenenfalls in euren Anwendungen darauf reagieren. Besser als die Funktion zu vermeiden ist es, die Funktion zu verstehen und sich ihre Eigenheiten gegebenenfalls zunutze zu machen.

Referenzen

Ninja-Notebooks @ GitHub

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.