Wer einmal einen Kassen-Report mit Werten wie '1.234,56 €' aus einer CSV in eine SQL-Server-Datenbank importieren musste, kennt das Muster: TRY_CONVERT(money, '1,234.56') liefert 1234.5600. Doch TRY_CONVERT(money, '1.234,56') liefert NULL. Und selbst wenn der Import sauber läuft: money / 100 * 100 ist nicht zwangsläufig dasselbe wie der Ausgangswert.
Kurzüberblick — was dieser Artikel klärt:
- Wertebereich und Speicherbedarf von
money(±9,22e14, 8 Byte) undsmallmoney(±214 748,3647, 4 Byte), plus diedecimal(p,s)-Äquivalenz. - Die Rundungs-Pitfall
money / 100 * 100 != money— und wanndecimal(p,s)der bessere Datentyp ist. TRY_CONVERT-Verhalten bei Text-Inputs (Komma wird ignoriert, leere Zeichenfolge wird0) und bei typisierten Werten (Komma wird zum style-Parameter-Trenner).- Ein sicheres Konvertierungs-Pattern mit
CASE/TRIM/REPLACEund das Postgres-Pendant (numeric(19,4)portabel,moneymitlc_monetary-Caveat).
Voraussetzung: SQL Server 2017+ (für TRIM im sicheren Pattern; vor 2017 als LTRIM(RTRIM(...))-Fallback). Kein Sample-Datensatz nötig — alle Beispiele arbeiten mit Inline-Literalen.
Inhalt
- Wertebereich und Speicherbedarf
- Rundungs-Pitfall — money vs. decimal
- Text
- Kein Text
- Sichere Typ-Konvertierung
- Postgres-Brücke
- Zusammenfassung
- FAQ
- Verwandte Artikel
Wertebereich und Speicherbedarf
SQL Server stellt für die Speicherung von Währungswerten zwei Datentypen zur Verfügung. Beide haben fest vier Nachkommastellen und sind in den gespeicherten Werten im Wesentlichen identisch zu einem decimal(p, 4) mit passender Gesamt-Stellenzahl. Intern unterscheidet sich die Speicher-Repräsentation, für die fachliche Interpretation des Wertes ist der Unterschied nicht relevant.
| Typ | Min | Max | Bytes | Nachkommastellen | decimal-Äquivalent |
|---|---|---|---|---|---|
money | −922 337 203 685 477,5808 | +922 337 203 685 477,5807 | 8 | 4 | decimal(19, 4) |
smallmoney | −214 748,3648 | +214 748,3647 | 4 | 4 | decimal(10, 4) |
Praktische Konsequenz für die Typ-Wahl: smallmoney ist nur dort sinnvoll, wo der Wertebereich sicher unter ±214 748 bleibt (Speicher-Halbierung, 4 statt 8 Byte). Bei Zweifel money wählen — oder gleich decimal(p, s), wenn Berechnungsgenauigkeit entscheidend ist (nächster Abschnitt).
Rundungs-Pitfall — money vs. decimal
Die Datentypen money und smallmoney weisen eine Besonderheit bei mehrstufigen Berechnungen auf: SQL Server konvertiert das Zwischen-Ergebnis nach jedem Teilschritt implizit zurück auf vier Nachkommastellen, bevor weitergerechnet wird. Bei decimal(p, s) wächst die Skala in Divisionen statt zu runden — der nominale Wert bleibt erhalten.
1: SELECT CAST(123.45678 AS money) AS [Output]; -- 123.4568
2: SELECT CAST(123.45678 AS money) / 100 AS [Output]; -- 1.2345 (Verlust ab hier)
3: SELECT CAST(123.45678 AS money) / 100 * 100 AS [Output]; -- 123.4500 (nominal nicht gleich 123.4568)
1: SELECT CAST(123.45678 AS decimal(10, 4)) AS [Output]; -- 123.4568
2: SELECT CAST(123.45678 AS decimal(10, 4)) / 100 AS [Output]; -- 1.23456800
3: SELECT CAST(123.45678 AS decimal(10, 4)) / 100 * 100 AS [Output]; -- 123.45680000
Bei der Verwendung des Datentyps money verliert das Ergebnis in der ersten Division an Genauigkeit. Die Werte in Zeile 1 und Zeile 3 sind nominell nicht gleich. Bei decimal(10, 4) liefert dieselbe Berechnung den Ausgangswert vollständig zurück.
Take-Away für die Typ-Wahl: Bei mehrstufigen Reporting-Berechnungen (Brutto/Netto, Steuer-Aufrechnungen, Allokations-Schlüsseln) führt die implizite money-Rundung zu akkumulierten Fehlern. Dieser Fehler kann vermieden werden, wenn money nur für die Speicher-Spalten verwendet und für die Arithmetik auf decimal(p, s) ausgewichen wird — siehe auch TRY_CONVERT // Konvertierung nach decimal oder numeric.
Das Rückgabe-Ergebnis der Konvertierung eines Wertes in den Datentyp money hängt zudem davon ab, in welchem Datentyp die Zahl übergeben wird. Zu unterscheiden ist der Fall, dass expression als Text-Wert vom Typ nvarchar/varchar übergeben wird, vom Fall einer bereits typisierten Zahl (int, float, decimal).
Text
Wird ein Wert vom Typ nvarchar/varchar übergeben, muss der Eingangswert eine Zahl darstellen. Als Dezimaltrennzeichen wird ausschließlich der Punkt erwartet. Ein Komma im Eingangswert wird ignoriert — nicht als Dezimaltrennzeichen interpretiert. Dieses Verhalten ist Locale-unabhängig und unterscheidet sich von der Locale-abhängigen Interpretation in manchen Anwendungs-Schichten.
Da Dezimalzahlen aus deutschsprachigen Textdateien typischerweise ein Komma als Dezimaltrennzeichen verwenden, muss das Komma vor der Konvertierung durch einen Punkt ersetzt werden. Enthält der Eingangswert sowohl einen Dezimalpunkt als auch Kommas als Tausendertrennzeichen, führt diese Ersetzung zu einem Wert mit mehreren Punkten und damit zu NULL — die folgende Test-Liste illustriert die Spannweite.
Führende und folgende Leerzeichen werden von TRY_CONVERT abgeschnitten und verhindern die Konvertierung nicht. Eine leere Zeichenfolge wird nicht zu NULL, sondern zu 0 konvertiert — ein Sonderfall, der im sicheren Pattern unten explizit abgefangen wird. Enthält der Eingangswert mehr als vier Nachkommastellen, rundet TRY_CONVERT auf die vierte Nachkommastelle.
1: SELECT TRY_CONVERT(money, NULL ) -- NULL
2: SELECT TRY_CONVERT(money, N'12345678' ) -- 12345678.00
3: SELECT TRY_CONVERT(money, N'123,45678' ) -- 12345678.00
4: SELECT TRY_CONVERT(money, N'123.45678' ) -- 123.4568
5: SELECT TRY_CONVERT(money, N'' ) -- 0.00
6: SELECT TRY_CONVERT(money, N' ' ) -- 0.00
7: SELECT TRY_CONVERT(money, N' 123.45678') -- 123.4568
8: SELECT TRY_CONVERT(money, N'123.45678 ') -- 123.4568
9: SELECT TRY_CONVERT(money, N'12345678E-3') -- NULL
10: SELECT TRY_CONVERT(money, N'1,234.5678' ) -- 1234.5678
11: SELECT TRY_CONVERT(money, N'1.234.5678' ) -- NULL
12: SELECT TRY_CONVERT(money, N'1,2,3,4' ) -- 1234.00
13: SELECT TRY_CONVERT(money, N'1,2,3.4' ) -- 123.40
14: SELECT TRY_CONVERT(money, N'1,2.3.4' ) -- NULL
15: SELECT TRY_CONVERT(money, N'1,2.3,4' ) -- 12.34
Die Zeilen 10 bis 15 zeigen teils unsinnige Kombinationen von Punkt- und Komma-Setzungen, die das Verhalten beim Tausender-/Dezimaltrennzeichen veranschaulichen. Kurz zusammengefasst: ein einzelner Punkt wirkt als Dezimaltrennzeichen, beliebig viele Kommas davor werden ignoriert; zwei Punkte oder ein Punkt nach einem Punkt liefern NULL.
In den Zeilen 5 und 6 ist der Eingangswert eine leere Zeichenfolge bzw. ein Text mit nur Leerzeichen. TRY_CONVERT konvertiert beides zur Zahl 0. Dieses Verhalten ist im Kontext eines ETL-Prozesses fast immer fachlich falsch — eine leere Zeichenfolge bedeutet „kein Wert geliefert“, nicht „der Wert ist null“. Daher ist die leere Zeichenfolge vor der Konvertierung explizit auf NULL zu mappen.
Kein Text
Wird die Zahl bereits typisiert übergeben (int, float, decimal), kann die Funktion jeden beliebigen numerischen Wert in money konvertieren. Werte mit mehr als vier Nachkommastellen werden auf die vierte Nachkommastelle gerundet — der nominale Ausgangswert kann sich also bereits in der Konvertierung ändern.
1: SELECT TRY_CONVERT(money, NULL ) -- NULL
2: SELECT TRY_CONVERT(money, 12345678 ) -- 12345678.00
3: SELECT TRY_CONVERT(money, 123,45678 ) -- 123.00
4: SELECT TRY_CONVERT(money, 123.45678 ) -- 123.4568
5: SELECT TRY_CONVERT(money, 12345678E-5) -- 123.4568
Beachtenswert ist Zeile 3: die Zahl ist keine Dezimalzahl. Das Komma wird hier als style-Parameter-Trenner von TRY_CONVERT interpretiert — die Funktion erhält die Ganzzahl 123 als expression und den (für money wirkungslosen) Wert 45678 als style. In Zeile 4 wird expression als typisierte Dezimalzahl mit Nachkommastellen übergeben (intern float); die Konvertierung greift dann wie erwartet.
Für smallmoney greift zusätzlich der engere Wertebereich: Werte außerhalb ±214 748,3647 liefern bei TRY_CONVERT ein NULL (statt einer Exception wie bei CONVERT).
Sichere Typ-Konvertierung
Die beiden Sektionen oben haben zwei Sonderfälle gezeigt, die im Import-Pfad explizit behandelt werden müssen:
- Eine leere Zeichenfolge wird zu
0konvertiert. Wenn das fachlich falsch ist (Default-Fall bei CSV-Imports), muss die leere Zeichenfolge vor demTRY_CONVERTaufNULLgemappt werden. - Ein Komma als Dezimaltrennzeichen wird ignoriert —
'123,45'wird so zu12345, nicht zu123,45. Wenn die Quelle deutsche Notation liefert, muss das Komma vor demTRY_CONVERTdurch einen Punkt ersetzt werden.
Das folgende Beispiel löst beide Sonderfälle gemeinsam:
1: DECLARE @p_input AS nvarchar(30);
2: SET @p_input = N' 123.45678';
3: SELECT TRY_CONVERT(
4: money
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];
Wer das Pattern in einem ETL-Prozess in mehreren Spalten gleichzeitig braucht, abstrahiert es zu einer benutzerdefinierten Funktion fn_try_convert_money(@p_input nvarchar) — siehe Design Pattern // Sichere Typ-Konvertierung mit T-SQL.
Postgres-Brücke
Postgres bietet zwei Wege, um Währungswerte zu speichern — die Wahl ist eine portabilitäts- vs. komfort-Entscheidung:
numeric(19, 4)ist das direkte, portable Pendant zu T-SQLmoney. Gleicher Wertebereich, gleiche Skala, Locale-unabhängig. Empfehlung für ETL-Workloads.moneyist ein Postgres-eigener Typ, der das Aussehen (Währungssymbol, Tausender- und Dezimaltrennzeichen, Skala) über die Session-Localelc_monetarysteuert. Beide_DE.UTF-8erwartet Postgres beim Cast den String'1.234,56 €', beien_US.UTF-8den String'$1,234.56'. Für portable Workloads nicht empfohlen — Format-Drift zwischen Session-Locales macht das Verhalten fragil.
Postgres hat kein eingebautes try_cast (Stand Postgres 18.0, 2025-09-25). Ein PL/pgSQL-Wrapper mit EXCEPTION-Block liefert das NULL-statt-Exception-Verhalten von TRY_CONVERT:
1: CREATE OR REPLACE FUNCTION try_cast_numeric_19_4 (p_input text)
2: RETURNS numeric(19, 4)
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 REPLACE(p_input, ',', '.')::numeric(19, 4);
11: EXCEPTION
12: WHEN invalid_text_representation OR numeric_value_out_of_range
13: THEN
14: RETURN NULL;
15: END;
16: $$;
Wertebereich- und Skala-Mapping zwischen den Engines:
| T-SQL | Postgres-Pendant (portabel) | IEEE 754 / Skala | Bytes (T-SQL / Postgres) |
|---|---|---|---|
money | numeric(19, 4) | Festkomma, 4 NK | 8 / variabel (typ. 16) |
smallmoney | numeric(10, 4) | Festkomma, 4 NK | 4 / variabel (typ. 12) |
decimal(p, s) | numeric(p, s) | Festkomma, s NK | 5–17 / variabel |
Plattform-übergreifender Hinweis: Postgres-numeric kennt keine implizite Zwischen-Rundung auf eine feste Skala — die Rundungs-Pitfall aus der Sektion zuvor existiert in Postgres numeric nicht. numeric arbeitet mit beliebig hoher Präzision (bis 131 072 signifikante Stellen) und erweitert die Skala in Divisionen. Für Cross-DB-Reporting ist das ein wichtiger Konsistenz-Anker — und ein zusätzliches Argument gegen den Postgres-money-Typ.
Zusammenfassung
Bei der Konvertierung eines Wertes in den Datentyp money greifen mehrere Sonderfälle: Leere Zeichenfolgen werden zur Zahl 0 konvertiert, Zahlen in wissenschaftlicher Notation als Text liefern NULL, ein Komma im Text wird ignoriert und nicht als Dezimaltrennzeichen interpretiert. Das oben gezeigte Pattern fängt diese Fälle ab und stellt sicher, dass leere Eingaben als NULL (statt 0) im Ziel-System ankommen.
Die money-Rundungs-Pitfall spielt bei der reinen Konvertierung eine untergeordnete Rolle. Sobald in einem ETL-Prozess aber Berechnungen mit money-Werten erfolgen — Aggregationen, Allokationen, Brutto/Netto-Aufrechnungen — ist die implizite 4-NK-Zwischen-Rundung zu berücksichtigen und gegebenenfalls auf decimal(p, s) auszuweichen.
Take-Away in vier Bullets:
moneyundsmallmoneyhaben vier Nachkommastellen (fest). Bei mehrstufigen Berechnungen werden Zwischen-Ergebnisse implizit gerundet — für arithmetik-intensive Workloadsdecimal(p, s)bevorzugen.TRY_CONVERT(money, '')ergibt0.00, nichtNULL. Leere Zeichenfolgen vor der Konvertierung explizit aufNULLmappen.- Komma im Text wird ignoriert, nicht als Dezimaltrennzeichen interpretiert.
'123,45'ergibt12345.00. Bei deutschsprachigen Eingangsdaten Komma durch Punkt ersetzen. - Postgres-Pendant:
numeric(19, 4)ist die portable Empfehlung; der Postgres-eigenemoney-Typ ist Locale-abhängig (lc_monetary) und für ETL fragil. Kein eingebautestry_cast— eigenen PL/pgSQL-Wrapper schreiben.
FAQ
Warum gibt TRY_CONVERT(money, '123,45') 12345.00 zurück und nicht 123.45? Das Komma in einem Text-Input wird von TRY_CONVERT(money, …) ignoriert, nicht als Dezimaltrennzeichen interpretiert. Dieses Verhalten ist Locale-unabhängig — auch auf einem Server mit deutscher Spracheinstellung bleibt es bei diesem Verhalten. Bei deutschsprachigen Eingangsdaten muss das Komma vor der Konvertierung via REPLACE(…, ',', '.') durch einen Punkt ersetzt werden (siehe sicheres Pattern oben).
money oder decimal — wann was? money ist für reine Speicher-Spalten und einfache Konvertierungen ohne mehrstufige Arithmetik gut geeignet. Sobald Aggregationen, Divisionen oder Multiplikations-Ketten ins Spiel kommen, führt die implizite Zwischen-Rundung auf vier Nachkommastellen zu akkumulierten Fehlern — dann ist decimal(p, s) mit explizit gewählter Skala die bessere Wahl. Faustregel: money an den Systemgrenzen (Import/Export), decimal für die Verarbeitung.
Warum ist (money) 123.45678 / 100 * 100 nicht 123.45678? SQL Server konvertiert das Ergebnis jedes Teilschritts einer money-Berechnung implizit zurück auf vier Nachkommastellen, bevor weitergerechnet wird. Im konkreten Beispiel wird 123.45678 / 100 zunächst zu 1.2345 gerundet (statt 1.2345678 zu bleiben), die folgende Multiplikation mit 100 ergibt dann 123.4500 — nicht den Ausgangswert. Bei decimal(p, s) wächst die Skala in Divisionen statt zu runden, der nominale Wert bleibt vollständig erhalten.
Wann smallmoney statt money? smallmoney ist auf den Wertebereich ±214 748,3647 begrenzt und belegt nur 4 statt 8 Byte pro Wert. Für Spalten mit garantiert kleinen Werten (Stückpreise, Gebühren, kleinere Beträge) ist das die kompaktere Wahl. Bei jeder Unsicherheit über den maximal möglichen Wert führt der Wechsel auf money zu keinen Nachteilen außer der Speicher-Verdopplung — und vermeidet eine spätere Datentyp-Migration.
Postgres-Pendant für TRY_CONVERT(money, …)? Für portable Workloads numeric(19, 4) plus einen PL/pgSQL-Wrapper mit EXCEPTION-Block, der bei invalid_text_representation oder numeric_value_out_of_range NULL zurückgibt (statt die Exception zu werfen) — Vollausführung in der Postgres-Brücke oben. Der Postgres-eigene money-Typ existiert, ist aber Locale-abhängig über lc_monetary und für ETL-Workloads nicht empfohlen.
Verwandte Artikel
- Datenqualität in SQL Server // TRY_CONVERT für date, datetime, datetime2 und time sicher anwenden
- TRY_CONVERT // Konvertierung nach decimal oder numeric
- Datenqualität in SQL Server // TRY_CONVERT für bigint, int, smallint und tinyint sicher anwenden
- Datenqualität in SQL Server // TRY_CONVERT für float und real sicher anwenden
- TRY_CONVERT // Konvertierung nach bit