Datenqualität in SQL Server // TRY_CONVERT für decimal und numeric sicher anwenden

Wer einmal einen Preis-Import gesehen hat, der aus '123,45 €' ein NULL macht statt der erwarteten Dezimalzahl, weiß: TRY_CONVERT(decimal(5, 2), '123,45') liefert NULL, weil das Komma als Dezimaltrennzeichen nicht erlaubt ist. Und selbst wenn das Komma weg ist: TRY_CONVERT(decimal(5, 2), '1234.56') ist auch NULL — diesmal wegen einer Vorkomma-Stelle zu viel.

Auf einen Blick:

  • decimal(p, s) und numeric(p, s) sind in SQL Server funktionell identisch — der Artikel verwendet decimal, alle Aussagen gelten 1:1 für numeric.
  • TRY_CONVERT rundet Nachkommastellen still auf s, lehnt aber überzählige Vorkommastellen mit NULL ab.
  • Komma als Dezimaltrennzeichen, leerer String und wissenschaftliche Notation als Text ergeben jeweils NULL. Das sichere Konvertierungs-Pattern arbeitet zweistufig (text → float → decimal).
  • Postgres-Pendant ist numeric(p, s) oder decimal(p, s) (echte Synonyme); p darf bis 1000 gehen (vs. 38 in SQL Server).

Voraussetzung: SQL Server 2017+ (TRIM im sicheren Pattern; vor 2017 LTRIM(RTRIM(…))-Fallback). Postgres 12+ für die Brücke.

Inhalt

Decimal und Numeric — funktionell identisch

SQL Server stellt für die Speicherung von exakten Dezimalzahlen zwei Datentypen zur Verfügung: decimal(p, s) und numeric(p, s). Beide implementieren denselben SQL-Standard, akzeptieren dieselben Parameter und teilen sich das gleiche Speicher-Layout. Sie sind funktionell identisch — eine Spalte vom Typ decimal(10, 2) und eine vom Typ numeric(10, 2) verhalten sich in Konvertierung, Arithmetik und Speicher exakt gleich.

In diesem Artikel wird durchgehend decimal verwendet; alle Aussagen gelten 1:1 für numeric.

Precision und Scale wählen

decimal(p, s) hat zwei Parameter:

  • precision (p) — die Gesamtzahl der Dezimalstellen, die der Datentyp speichern kann. Erlaubt sind Werte von 1 bis 38.
  • scale (s) — die Anzahl der Dezimalstellen rechts vom Dezimaltrennzeichen. Erlaubt sind Werte von 0 bis p.

Daraus ergibt sich der Wertebereich:

decimal(p, s)MinMaxTypische Verwendung
decimal(5, 2)−999,99+999,99Prozent-Sätze, kleine Preise
decimal(10, 2)−99 999 999,99+99 999 999,99Beträge im Buchhaltungs-Bereich
decimal(19, 4)−999 999 999 999 999,9999+999 999 999 999 999,9999money-Pendant mit wählbarer Skala (vgl. TRY_CONVERT für money und smallmoney)
decimal(38, 10)(Max-Form, 28 Vorkomma- + 10 Nachkomma-Stellen)(Max-Form, 28 Vorkomma- + 10 Nachkomma-Stellen)finanzmathematische / wissenschaftliche Werte

Faustregel: Anzahl Vorkomma-Stellen + Anzahl Nachkomma-Stellen ≤ p. Wenn ein Wert mehr Vorkommastellen hat als p − s zulässt, ist die Konvertierung nicht möglich — das Ergebnis ist NULL. Nachkomma-Stellen werden hingegen still gerundet, ohne Fehler.

Rundung vs. Vorkomma-Overflow

Die Asymmetrie zwischen Nachkomma und Vorkomma ist der zentrale Hebel beim Arbeiten mit decimal:

  1: SELECT TRY_CONVERT(decimal(5, 2), '123.456')   -- 123.46
  2: SELECT TRY_CONVERT(decimal(5, 2), '1234.56')   -- NULL
  3: SELECT TRY_CONVERT(decimal(5, 2), '12345.6')   -- NULL
  4: SELECT TRY_CONVERT(decimal(5, 2),  1234.56)    -- NULL
  5: SELECT TRY_CONVERT(decimal(5, 2),  123.456)    -- 123.46

  • Nachkomma wird auf s Stellen gerundet (kaufmännisch). Das passiert still, ohne Fehler, ohne NULL.
  • Vorkomma-Overflow liefert dagegen NULL — und zwar sowohl bei Text-Input (Zeilen 2, 3) als auch bei typisierten Zahlen (Zeile 4).

Take-Away: Eine TRY_CONVERT(decimal(p, s), …)-Konvertierung kann den nominalen Wert stillschweigend ändern (Nachkomma-Rundung) oder ihn stillschweigend verwerfen (Vorkomma-Overflow → NULL). In ETL-Pipelines ist letzteres oft die problematischere Falle: ein NULL im Ziel-Feld sieht aus wie „kein Wert geliefert“, war aber tatsächlich „Wert zu groß für die Skala“.

Text

Wird ein Wert vom Typ nvarchar/varchar an TRY_CONVERT übergeben, muss der Eingangswert eine Zahl darstellen. Als Dezimaltrennzeichen wird ausschließlich der Punkt erwartet. Tausendertrennzeichen sind nicht zulässig. Eine leere Zeichenfolge oder ein reiner Whitespace-String wird direkt zu NULL konvertiert — das ist ein wichtiger Unterschied zu float und money, wo leere Zeichenfolgen zu 0 konvertiert werden.

  1: SELECT TRY_CONVERT(decimal(5, 2), NULL        )  -- NULL
  2: SELECT TRY_CONVERT(decimal(5, 2), N'123'      )  -- 123.00
  3: SELECT TRY_CONVERT(decimal(5, 2), N'123,456'  )  -- NULL
  4: SELECT TRY_CONVERT(decimal(5, 2), N'123.456'  )  -- 123.46
  5: SELECT TRY_CONVERT(decimal(5, 2), N''         )  -- NULL
  6: SELECT TRY_CONVERT(decimal(5, 2), N' '        )  -- NULL
  7: SELECT TRY_CONVERT(decimal(5, 2), N' 123.456' )  -- 123.46
  8: SELECT TRY_CONVERT(decimal(5, 2), N'123.456 ' )  -- 123.46
  9: SELECT TRY_CONVERT(decimal(5, 2), N'1234.56'  )  -- NULL
 10: SELECT TRY_CONVERT(decimal(5, 2), N'123456E-3')  -- NULL

Im Detail:

  • Zeile 3: '123,456' mit Komma ergibt NULL — das Komma wird nicht als Dezimaltrennzeichen erkannt. Anders als bei TRY_CONVERT für money und smallmoney, wo Kommas ignoriert werden.
  • Zeilen 5, 6: leere Zeichenfolge und reiner Whitespace werden zu NULL. Anders als bei floatrealmoney und smallmoney, wo das Ergebnis 0 wäre.
  • Zeilen 7, 8: führende und folgende Leerzeichen werden abgeschnitten und verhindern die Konvertierung nicht.
  • Zeile 9: '1234.56' hat zu viele Vorkommastellen für decimal(5, 2) (max p − s = 3 Vorkomma-Stellen) → NULL.
  • Zeile 10: wissenschaftliche Notation '123456E-3' als Text ergibt NULL. Das ändert sich, wenn die Zahl als typisierter Wert übergeben wird (siehe nächste Sektion).

Kein Text

Wird die Zahl bereits typisiert übergeben (intfloatdecimal), kann TRY_CONVERT jeden numerischen Wert in decimal konvertieren — solange die Vorkomma-Stellen passen:

  1: SELECT TRY_CONVERT(decimal(5, 2), NULL     )  -- NULL
  2: SELECT TRY_CONVERT(decimal(5, 2), 123      )  -- 123.00
  3: SELECT TRY_CONVERT(decimal(5, 2), 123.456  )  -- 123.46
  4: SELECT TRY_CONVERT(decimal(5, 2), 1234.56  )  -- NULL
  5: SELECT TRY_CONVERT(decimal(5, 2), 123456E-3)  -- 123.46

Im Detail:

  • Zeile 3: typisierte Dezimalzahl wird auf s gerundet (123.456 → 123.46).
  • Zeile 4: 1234.56 als typisierte Dezimalzahl (intern float) überschreitet p − s = 3 Vorkommastellen → NULL.
  • Zeile 5: wissenschaftliche Notation funktioniert als typisierte Zahl (123456E-3 → 123.456 → gerundet auf 123.46). Der Unterschied zur Text-Variante ist der Hebel für das Zwei-Stufen-Pattern in der nächsten Sektion.

Sichere Typ-Konvertierung

Die Sektionen oben haben zwei Sonderfälle gezeigt, die im Import-Pfad explizit behandelt werden müssen:

  • Wissenschaftliche Notation als Text ('12345E-3') ergibt NULL. Lösung: erst zu float konvertieren (das toleriert die Notation), dann zu decimal (das setzt Präzision und Skala durch).
  • Eine leere Zeichenfolge wird vom direkten TRY_CONVERT(decimal, …) zwar zu NULL — sobald aber das Zwei-Stufen-Pattern via float läuft, gilt das nicht mehr: TRY_CONVERT(float, '') ergibt 0. Die leere Zeichenfolge muss daher vor dem float-Schritt explizit auf NULL gemappt werden.

Das folgende Beispiel löst beide Sonderfälle gemeinsam:

  1: DECLARE @p_input AS nvarchar(30);
  2: SET @p_input = N'123,456';
  3: 
  4: SELECT TRY_CONVERT( decimal(5, 2)
  5:                   , TRY_CONVERT( float
  6:                                , REPLACE( CASE WHEN TRIM(@p_input) = ''
  7:                                                   THEN NULL
  8:                                                   ELSE @p_input
  9:                                              END
 10:                                         , ','
 11:                                         , '.'
 12:                                         )
 13:                                )
 14:                   ) AS [Output];  -- 123.46

Wer das Pattern in einem ETL-Prozess in mehreren Spalten gleichzeitig braucht, abstrahiert es zu einer benutzerdefinierten Funktion fn_try_convert_decimal(@p_input nvarchar, @p_precision int, @p_scale int) — siehe Design Pattern // Sichere Typ-Konvertierung mit T-SQL.

Postgres-Brücke

In Multi-Engine-ETL-Pipelines stellt sich die Frage nach dem Postgres-Pendant. Die gute Nachricht: Postgres ist hier die einfachste Brücke im ganzen TRY_CONVERT-Cluster, weil numeric(p, s) und decimal(p, s) exakt dem SQL-Standard folgen.

Drei Unterschiede gegenüber SQL Server:

  • Synonyme statt nur identisch. In Postgres sind numeric(p, s) und decimal(p, s) echte Synonyme — die Spaltendefinition speichert intern immer numeric. In SQL Server bleibt der ursprüngliche Bezeichner erhalten.
  • p bis 1000. Postgres lässt precision bis 1000 zu (vs. 38 in SQL Server). Für klassische ETL-Workloads selten relevant, für Finanzmathematik mit Millionen aggregierten Werten gelegentlich nützlich. Zusätzlich kennt Postgres numeric ohne Parameter — beliebige Präzision, beliebige Skala (bis zur Implementierungs-Grenze).
  • Wissenschaftliche Notation als Text funktioniert. Postgres CAST('123456E-3' AS numeric(5, 2)) liefert 123.46. Das Zwei-Stufen-Pattern via float ist in Postgres also nicht nötig — die Notation wird direkt akzeptiert.

Postgres hat allerdings kein eingebautes try_cast (auch in Postgres 18, Release 2025-09-25 nicht). Ein PL/pgSQL-Wrapper liefert das NULL-statt-Exception-Verhalten:

  1: CREATE OR REPLACE FUNCTION try_cast_numeric_5_2 (p_input text)
  2:    RETURNS numeric(5, 2)
  3:    LANGUAGE plpgsql
  4:    IMMUTABLE
  5: AS $$
  6: BEGIN
  7:    IF p_input IS NULL OR TRIM(p_input) = '' THEN
  8:       RETURN NULL;
  9:    END IF;
 10:    RETURN CAST(REPLACE(p_input, ',', '.') AS numeric(5, 2));
 11: EXCEPTION
 12:    WHEN OTHERS THEN
 13:       RETURN NULL;
 14: END;
 15: $$;
 16: 
 17: SELECT try_cast_numeric_5_2('123,456');     -- 123.46
 18: SELECT try_cast_numeric_5_2('123456E-3');   -- 123.46
 19: SELECT try_cast_numeric_5_2('1234.56');     -- NULL
 20: SELECT try_cast_numeric_5_2('');            -- NULL

Zusammenfassung

Zusammengefasst:

  • decimal(p, s) und numeric(p, s) sind in SQL Server funktionell identisch — der Artikel verwendet decimal, alle Aussagen gelten 1:1 für numeric.
  • TRY_CONVERT(decimal(p, s), …) rundet Nachkommastellen still, lehnt aber überzählige Vorkommastellen mit NULL ab. Die Asymmetrie ist die zentrale Falle in ETL-Pipelines.
  • Bei Text-Eingangswerten: Komma → NULL, leere Zeichenfolge → NULL, wissenschaftliche Notation → NULL. Bei typisierten Zahlen funktioniert wissenschaftliche Notation. Daher das Zwei-Stufen-Pattern (text → float → decimal).
  • Postgres-Pendant: numeric(p, s) oder decimal(p, s) — gleiche Wertebereiche bis p = 38, darüber hinaus bis p = 1000. Wissenschaftliche Notation als Text wird direkt akzeptiert; das Zwei-Stufen-Pattern ist nicht nötig.

FAQ

decimal oder numeric — was nehmen? Beide implementieren denselben SQL-Standard und sind in SQL Server funktionell identisch. Welcher Bezeichner verwendet wird, ist Konvention: decimal ist in T-SQL-Communitys häufiger anzutreffen, numeric in der ANSI-Standard-Doku. Tools wie der SSMS-Designer defaulten oft auf decimal.

Warum gibt TRY_CONVERT(decimal(5, 2), '123,45') NULL zurück? Weil das Komma in SQL Server nicht als Dezimaltrennzeichen erkannt wird — TRY_CONVERT erwartet ausschließlich einen Punkt. Bei deutschen Eingangsdaten muss das Komma vor der Konvertierung durch einen Punkt ersetzt werden; das sichere Konvertierungs-Pattern oben übernimmt das mit REPLACE.

Rundet TRY_CONVERT(decimal, …) oder lehnt es ab? Beides — je nachdem, ob das Problem im Nachkomma oder im Vorkomma liegt. Nachkommastellen werden still auf s gerundet (kaufmännisch). Vorkomma-Overflow liefert NULL. Diese Asymmetrie ist in ETL-Pipelines wichtig: ein NULL im Ziel-Feld bedeutet bei decimal nicht zwangsläufig „Quelle hat keinen Wert geliefert“, sondern kann auch „Quelle hatte zu viele Vorkommastellen“ heißen.

Wie konvertiere ich wissenschaftliche Notation ('12345E-3') sicher zu decimal? Das Zwei-Stufen-Pattern aus der Sektion „Sichere Typ-Konvertierung“ — erst zu float (das toleriert die Notation als Text), dann zu decimal (das setzt Präzision/Skala durch). Querverweis: TRY_CONVERT für float und real.

Postgres-Pendant für TRY_CONVERT(decimal, …)? CAST(s AS numeric(p, s)) oder CAST(s AS decimal(p, s)) — Postgres macht keinen Unterschied. Für NULL-statt-Exception-Verhalten ist ein PL/pgSQL-Wrapper nötig (Postgres hat kein eingebautes try_cast, Stand 18.0). Postgres ist außerdem liberaler bei Text-Inputs: wissenschaftliche Notation wird direkt akzeptiert.

Verwandte Artikel

1 Kommentar zu „Datenqualität in SQL Server // TRY_CONVERT für decimal und numeric sicher anwenden“

Die Kommentare sind geschlossen.