Ein CSV-Import läuft ohne Fehlermeldung durch, und hinterher steht in der Mengen-Spalte eine 0, wo das Quellfeld schlicht leer war: TRY_CONVERT(int, N'') liefert 0, nicht NULL. Die zweite Eigenheit betrifft bereits typisierte Dezimalzahlen: TRY_CONVERT(int, 1234.5) rundet nicht, sondern schneidet ab — das Ergebnis ist 1234, nicht 1235.
Auf einen Blick:
- Die vier Integer-Typen von SQL Server decken klar abgestufte Wertebereiche ab — vom 1-Byte-
tinyint(0..255, als einziger unsigned) bis zum 8-Byte-bigint. Bei der Konvertierung aus demselben Quelltyp verhalten sich alle vier gleich. - Aus Text konvertiert
TRY_CONVERTnur ganzzahlige Darstellungen. Vorzeichen und umgebende Leerzeichen sind erlaubt, Dezimal- und Tausendertrennzeichen ergeben Locale-unabhängigNULL. - Zwei stille Sonderfälle: Eine leere Zeichenfolge wird zu
0konvertiert (im Import meist falsch), und typisierte Dezimalzahlen werden trunkiert statt gerundet. Das sichere Pattern behandelt beide. - In Postgres wirft
CAST(… AS bigint)bei ungültigem Input eine Exception — ein eingebautes Try-Pendant fehlt. Der Artikel zeigt den PL/pgSQL-Wrapperfn_try_cast_bigint.
Voraussetzung: TRY_CONVERT gibt es seit SQL Server 2012. Das sichere Pattern verwendet zusätzlich TRIM und braucht damit SQL Server 2017+ (davor LTRIM(RTRIM(…))). Die Beispiele laufen ohne Sample-Datenbank mit reinen Inline-Literalen, die Postgres-Seite setzt keine bestimmte Version voraus.
Inhalt
- Wertbereich der Integer-Typen
- Text nach Integer konvertieren
- Typisierte Zahlen nach Integer konvertieren
- Sichere Typ-Konvertierung
- Postgres-Brücke
- Zusammenfassung
- FAQ
- Verwandte Artikel
Wertbereich der Integer-Typen
Vor der Konvertierung steht die Typ-Wahl. Die vier Integer-Typen in SQL Server unterscheiden sich durch Wertebereich und Speicherbedarf. Für Konvertierungen aus demselben Quelltyp gelten bei allen vier Ziel-Typen dieselben Regeln — entscheidend ist, ob der Wert in den Ziel-Wertebereich passt.
| Datentyp | Min | Max | Bytes | Typische Verwendung |
|---|---|---|---|---|
bigint | -9 223 372 036 854 775 808 | 9 223 372 036 854 775 807 | 8 | Riesige Surrogate-Keys, Counter über Milliarden, globale 64-Bit-IDs |
int | -2 147 483 648 | 2 147 483 647 | 4 | Standard-Surrogate-Key, Mengen, Counter im 9-stelligen Bereich |
smallint | -32 768 | 32 767 | 2 | Jahre, Klein-Mengen, ältere Lookup-Schlüssel |
tinyint | 0 | 255 | 1 | Flags, Status-Codes, kleine Lookup-Werte (unsigned) |
tinyint ist eine Microsoft-Eigenheit und der einzige SQL-Server-Integer-Typ ohne Vorzeichen. Ein negativer Wert lässt sich nicht in tinyint konvertieren, der TRY_CONVERT-Aufruf gibt NULL zurück. Postgres kennt diesen Typ gar nicht — kleinster Integer dort ist smallint (signed, -32 768..32 767).
Text nach Integer konvertieren
Text-Eingangswerte kommen typischerweise aus CSV-, JSON- und XML-Importen. Wird ein solcher nvarchar-/varchar-Wert an TRY_CONVERT übergeben, muss der Text eine ganze Zahl darstellen. Erlaubt sind Ziffern, ein optionales Vorzeichen und umgebende Leerzeichen. Ein Dezimal- oder Tausendertrennzeichen beendet die Konvertierung dagegen mit NULL, und eine leere Zeichenfolge wird zu 0 konvertiert. Das ist ein wichtiger Unterschied zu TRY_CONVERT(decimal(18, 2), N''), das NULL liefert.
1: SELECT TRY_CONVERT(int, NULL ) -- NULL
2: SELECT TRY_CONVERT(int, N'123' ) -- 123
3: SELECT TRY_CONVERT(int, N'123,4' ) -- NULL
4: SELECT TRY_CONVERT(int, N'1,234' ) -- NULL
5: SELECT TRY_CONVERT(int, N'123.4' ) -- NULL
6: SELECT TRY_CONVERT(int, N'1.234' ) -- NULL
7: SELECT TRY_CONVERT(int, N'' ) -- 0
8: SELECT TRY_CONVERT(int, N' ' ) -- 0
9: SELECT TRY_CONVERT(int, N' 123' ) -- 123
10: SELECT TRY_CONVERT(int, N'123 ' ) -- 123
11: SELECT TRY_CONVERT(int, N'2147483648' ) -- NULL
12: SELECT TRY_CONVERT(int, N'123456E-3' ) -- NULL
Die Zeilen zeigen im Einzelnen:
- Zeile 2: Eine reine Ziffernfolge wird wie erwartet konvertiert.
- Zeilen 3–6: Bei der Text-nach-Integer-Konvertierung scheitern Komma und Punkt in jeder Rolle, als Dezimal- wie als Tausendertrennzeichen. Diese Konvertierung ist Locale-unabhängig: Weder
SET LANGUAGEnochSET DATEFORMATändern etwas an diesem Ergebnis. - Zeilen 7, 8: Eine leere Zeichenfolge und ein String nur aus Leerzeichen werden zu
0konvertiert, nicht zuNULL. - Zeilen 9, 10: Führende und folgende Leerzeichen sind erlaubt und ändern das Ergebnis nicht.
- Zeile 11: Der Wert liegt um
1über demint-Maximum von2 147 483 647.TRY_CONVERTbehandelt den Overflow als fehlgeschlagene Konvertierung und liefertNULL. - Zeile 12: Wissenschaftliche Notation wird als Text nicht akzeptiert. Als typisierte Zahl funktioniert sie, das zeigt die nächste Sektion.
Kernaussage: Ein leeres Feld ist im Import semantisch ein „unbekannt“, TRY_CONVERT macht daraus aber den fachlichen Wert 0: still, ohne Fehler, ohne NULL. Wo das falsch ist (der Regelfall bei CSV-Importen), mappt das Pattern aus „Sichere Typ-Konvertierung“ die leere Zeichenfolge vorab auf NULL.
Typisierte Zahlen nach Integer konvertieren
Kommt der Eingangswert bereits typisiert an, etwa als Ganzzahl mit anderem Wertebereich oder als Dezimalzahl aus einer Pipeline-Berechnung, reduzieren sich die Szenarien auf zwei Fragen: Passt der Wert in den Ziel-Wertebereich? Und was passiert mit Nachkommastellen?
1: SELECT TRY_CONVERT(int, 2147483648) -- NULL
2: SELECT TRY_CONVERT(int, 123) -- 123
3: SELECT TRY_CONVERT(int, 1234.5) -- 1234
4: SELECT TRY_CONVERT(int, 123456E-3) -- 123
Die Zeilen zeigen im Einzelnen:
- Zeile 1:
2147483648liegt um1über demint-Maximum, die Konvertierung schlägt fehl und liefertNULL. - Zeile 3: Die typisierte Dezimalzahl wird zum ganzzahligen Anteil abgeschnitten, aus
1234.5wird1234. Wer Rundung will, schaltet sie explizit vor:ROUND(1234.5, 0)liefert1235.0, das anschließend zu1235konvertiert. - Zeile 4: Wissenschaftliche Notation funktioniert als typisiertes
float-Literal:123456E-3ist123.456, trunkiert123. Als Text (Zeile 12 der vorigen Sektion) ergibt dieselbe NotationNULL.
Kernaussage: TRY_CONVERT nach Integer trunkiert die Nachkommastellen von decimal– und float-Werten stillschweigend — anders als die Konvertierung nach decimal(p, s), die kaufmännisch rundet (half away from zero). Eine dokumentierte Ausnahme ist money als Quelltyp, das SQL Server beim Integer-Ziel rundet. Overflow quittiert TRY_CONVERT mit NULL, wo CAST und CONVERT einen Laufzeitfehler auslösen.
Die Wertebereichs-Grenzen pro Typ zeigt ein zweiter Block:
1: SELECT TRY_CONVERT(smallint, 32767) -- 32767 (smallint-MAX)
2: SELECT TRY_CONVERT(smallint, 32768) -- NULL (Overflow)
3: SELECT TRY_CONVERT(tinyint, 255) -- 255 (tinyint-MAX, unsigned)
4: SELECT TRY_CONVERT(tinyint, 256) -- NULL (Overflow)
5: SELECT TRY_CONVERT(tinyint, -1) -- NULL (tinyint ist unsigned)
Bei tinyint schlägt die Konvertierung in beide Richtungen fehl: 256 ist zu groß, -1 verletzt die Unsigned-Grenze.
Sichere Typ-Konvertierung
„Sicher“ meint in dieser Serie fehlertolerant: kein Abbruch, NULL statt Laufzeitfehler. Verlustfrei ist damit nicht automatisch gemeint — bei den Integer-Typen gehört die stille Trunkierung ausdrücklich zu den Verlusten, die das Pattern nicht verhindert. Auch semantisch falsche Werte (wie die 0 aus einer leeren Zeichenfolge) verhindert TRY_CONVERT von sich aus nicht, genau dafür gibt es das Pattern unten. Und grundsätzlich unzulässige Typ-Kombinationen fängt die Funktion ebenfalls nicht ab: Sie lösen weiterhin einen Fehler aus.
Zwei Sonderfälle aus den Sektionen oben brauchen im Import-Pfad eine explizite Behandlung:
- Leere Zeichenfolge →
0: Wenn das fachlich falsch ist (der Regelfall bei CSV-Importen), muss die leere Zeichenfolge vor demTRY_CONVERTaufNULLgemappt werden. - Typisierte Dezimalzahl → Trunkierung: Falls Rundung gewünscht ist, kommt vor das
TRY_CONVERTeinROUND(…, 0). Die Alternativedecimal(p, 0)als Ziel-Typ rundet zwar, tut das aber ebenfalls still (siehe FAQ).
Das Anwendungs-Beispiel mit DECLARE und snake_case-Variablen-Konvention löst den ersten Sonderfall:
1: DECLARE @p_input AS nvarchar(30);
2: SET @p_input = N'123';
3:
4: SELECT TRY_CONVERT( int
5: , CASE WHEN TRIM(@p_input) = '' THEN NULL ELSE @p_input END
6: ) AS [Output];
Zwei Einschränkungen gehören zu dem Pattern dazu:
- Eingabegrammatik: Das Pattern fängt nur die leere Zeichenfolge ab. Tausendertrennzeichen und Währungszeichen muss eine vorgelagerte Bereinigung entfernen, sonst bleibt das Ergebnis
NULL. UndTRIMentfernt ohne weitere Angabe nur das normale Leerzeichen (char(32)) — einen String aus Tabs oder anderem Unicode-Leerraum erkennt das Pattern deshalb nicht als „leer“. Können solche Zeichen in den Quelldaten vorkommen, muss die Normalisierung sie explizit berücksichtigen. - Keine Rundung: Bei der Konvertierung in einen Integer-Zieltyp wird der Nachkommateil abgeschnitten, das Pattern ändert daran nichts. Soll gerundet werden, kommt die Rundung explizit als
ROUND(…, 0)vor dasTRY_CONVERT.
Wer das Pattern in einem ETL-Prozess in mehreren Spalten gleichzeitig braucht, abstrahiert es zu einer benutzerdefinierten Funktion fn_try_convert_int(@p_input nvarchar) — siehe Design Pattern // Sichere Typ-Konvertierung mit T-SQL.
Postgres-Brücke
Auf der Postgres-Seite ist die Typ-Frage schnell beantwortet: smallint, integer und bigint decken dieselben Wertebereiche ab wie ihre SQL-Server-Pendants, nur tinyint fehlt (eine tinyint-Spalte wird bei einer Migration üblicherweise auf smallint abgebildet). Der eigentliche Unterschied liegt im Fehlerverhalten:
CAST(… AS bigint)wirft bei ungültigem Input eine Exception: Ein eingebautes Try-Pendant gibt es nicht — auch Postgres 18 (Release 2025-09-25) bringt weder einetry_cast-Funktion noch die SQL/JSON-SyntaxCAST … ON ERROR NULLmit.- Auch die leere Zeichenfolge wirft eine Exception:
CAST('' AS bigint)scheitert mitinvalid_text_representation. Das ist strenger als T-SQL, woTRY_CONVERT(int, N'')still0liefert.
Das NULL-statt-Exception-Verhalten liefert ein PL/pgSQL-Wrapper. Er übernimmt dabei gleich die sichere Semantik des Patterns oben: Die leere Zeichenfolge wird zu NULL gemappt, nicht zu 0:
1: CREATE OR REPLACE FUNCTION fn_try_cast_bigint
2: (
3: IN p_input text
4: )
5: RETURNS bigint
6: LANGUAGE plpgsql
7: IMMUTABLE
8: AS $function$
9: BEGIN
10:
11: IF p_input IS NULL OR TRIM(p_input) = '' THEN
12: RETURN NULL;
13: END IF;
14:
15: RETURN CAST(p_input AS bigint);
16:
17: EXCEPTION
18: WHEN invalid_text_representation OR numeric_value_out_of_range THEN
19: RETURN NULL;
20: END;
21: $function$;
22:
23: SELECT fn_try_cast_bigint('123'); -- 123
24: SELECT fn_try_cast_bigint(' 123 '); -- 123
25: SELECT fn_try_cast_bigint('1,234'); -- NULL
26: SELECT fn_try_cast_bigint(''); -- NULL
Damit verhält sich fn_try_cast_bigint('1,234') wie TRY_CONVERT(int, '1,234'): Der Aufruf gibt NULL zurück, statt eine Exception zu werfen. Für int und smallint lassen sich analoge Wrapper mit angepasstem Rückgabe-Typ definieren.
Der EXCEPTION-Block fängt gezielt die zwei Fehlerklassen, die bei der Integer-Konvertierung erwartbar sind: invalid_text_representation für ungültige Zahl-Darstellungen und numeric_value_out_of_range für Overflow. Ein pauschales WHEN OTHERS würde auch unerwartete Fehler still zu NULL machen. Kostenlos ist der EXCEPTION-Teil allerdings nicht — die Postgres-Doku stuft einen Block mit Exception-Handler als deutlich teurer ein als einen ohne. Bei Massen-Imports mit vielen erwartbaren Fehl-Werten lohnt es sich deshalb, set-basiert vorzuprüfen, welche Werte konvertierbar sind, und nur diese zu casten. Seit Postgres 16 gibt es dafür passendes Werkzeug: pg_input_is_valid('123', 'bigint') prüft die Konvertierbarkeit, ohne einen fehlschlagenden Cast auszulösen, und pg_input_error_info(…) liefert die Fehlerdetails dazu. Der Wrapper bleibt das Komfort-Pattern für die wiederverwendbare Einzelwert-Konvertierung.
Liefert die Quelle formatierte Zahlen mit Tausendertrennzeichen, übernimmt to_number mit einem Format-Pattern das Parsen:
1: SELECT to_number('1.234', 'FM999G999') -- 1234 (deutsche Notation, lc_numeric = 'de_DE.UTF-8')
2: SELECT to_number('1,234', 'FM999G999') -- 1234 (US-Notation, lc_numeric = 'en_US.UTF-8')
Locale-abhängig ist to_number über die Format-Zeichen G (Tausendertrenner) und D (Dezimaltrenner): Beide richten sich nach der Session-Variable lc_numeric, während ein literales . oder , im Format-Pattern Locale-unabhängig bleibt. Damit unterscheidet sich to_number von der Text-nach-Integer-Konvertierung in SQL Server, für die das Session-Locale keine Rolle spielt. Die Locale-Namen in den Kommentaren sind plattformabhängig, de_DE.UTF-8 und en_US.UTF-8 dienen der Illustration. Für reproduzierbare ETL-Pipelines sollte das Zahlenformat deshalb nie implizit an der Server-Konfiguration hängen: entweder lc_numeric pro Session explizit setzen oder die Eingabe vorab so normalisieren, dass das Format-Pattern ohne Locale-Zeichen auskommt. Der Rückgabe-Typ von to_number ist außerdem numeric, für eine Integer-Spalte folgt also noch ein CAST.
Zusammenfassung
- Die vier Integer-Typen unterscheiden sich beim Konvertieren vor allem im Wertebereich. Typ-Wahl:
intals Default,bigintbei nachweislich größerem Wertebereich,smallintundtinyint, wenn ihr Wertebereich fachlich dauerhaft ausreicht und die geringere Breite ins Gewicht fällt. - Aus Text akzeptiert
TRY_CONVERTbei Integer-Zielen nur ganzzahlige Darstellungen (optional Vorzeichen und umgebende Leerzeichen) — Trennzeichen, wissenschaftliche Notation und Overflow ergebenNULL, unabhängig vom Session-Locale. - Die zwei stillen Fallen: Eine leere Zeichenfolge wird zu
0konvertiert, typisierte Dezimalzahlen werden trunkiert. Das sichere Pattern mappt die leere Zeichenfolge perCASE/TRIMaufNULL, Rundung kommt explizit alsROUND(…, 0)davor. - Postgres hat kein eingebautes Try-Pendant: Der Wrapper
fn_try_cast_bigintliefertNULLstatt Exception,to_numberparst formatierte Zahlen (Locale-abhängig überlc_numeric).
Für die Praxis als Entscheidungshilfe:
| Eingangslage | Empfohlener Weg |
|---|---|
| Reine Ziffernfolge, optional mit Vorzeichen | direkt TRY_CONVERT(int, …) |
| Leere Zeichenfolgen möglich (CSV-Import) | sicheres Pattern: CASE/TRIM mappt auf NULL, dann TRY_CONVERT |
| Dezimalzahlen, Rundung gewünscht | ROUND(…, 0) vor dem TRY_CONVERT |
| Tausendertrennzeichen, Quell-Locale bekannt | gezielt normalisieren (REPLACE), in Postgres to_number |
| Quell-Locale unbekannt oder gemischt | nicht blind normalisieren — als Datenqualitätsfehler zurückweisen |
NULL unklarer Herkunft im Ziel-Feld | Gegenprobe mit TRY_CONVERT(bigint, …): int-Overflow eingrenzen (siehe FAQ) |
FAQ
TRY_CONVERT(int, N'') zu 0 statt NULL? SQL Server behandelt die leere Zeichenfolge bei der Text-nach-Integer-Konvertierung wie eine 0 — die Konvertierung gilt als erfolgreich, es gibt weder Fehler noch NULL. Dasselbe gilt für Strings, die nur aus Leerzeichen bestehen. Im Import ist das meist nicht gemeint, denn ein leeres CSV-Feld steht für „unbekannt“. Das sichere Pattern aus „Sichere Typ-Konvertierung“ mappt die leere Zeichenfolge deshalb per CASE/TRIM vorab auf NULL.
TRY_CONVERT(int, '1,234') NULL zurück? Weil die Text-nach-Integer-Konvertierung Locale-unabhängig arbeitet und weder Dezimal- noch Tausendertrennzeichen kennt. Erwartet wird eine reine Ziffernfolge, optional mit Vorzeichen und umgebenden Leerzeichen. Sobald ein Komma oder ein Punkt im String steht, scheitert die Konvertierung, unabhängig von SET LANGUAGE oder SET DATEFORMAT. Liefert die Quelle verlässlich deutsche Notation ('1.234,56'), hilft eine REPLACE-Bereinigung: REPLACE(REPLACE(@p_input, '.', ''), ',', '.') macht daraus '1234.56', das anschließend per TRY_CONVERT(decimal(18, 2), …) konvertiert.
int oder bigint — welcher Typ wann? int reicht für jeden Wertebereich bis rund ±2,1 Milliarden und ist der Default für Surrogate-Keys, Counter und Mengen. bigint kommt zum Einsatz, sobald der Wertebereich nachweislich darüber hinauswächst, etwa bei globalen 64-Bit-ID-Generatoren. smallint und tinyint sind sinnvoll, wenn ihr Wertebereich fachlich dauerhaft ausreicht und die geringere Speicherbreite bei Tabelle oder Indizes ins Gewicht fällt. Wer dagegen nur auf Verdacht verkleinert, bezahlt beim Schema-Wachstum mit dem Overflow-Risiko.
1234.5? TRY_CONVERT(int, 1234.5) liefert 1234, also Trunkierung. Für kaufmännische Rundung wird vorher explizit ROUND eingeschoben: TRY_CONVERT(int, ROUND(1234.5, 0)) liefert 1235. Achtung: decimal(p, 0) ist kein strikter Ersatz für int — SQL Server rundet bei der Skala-Reduktion still (TRY_CONVERT(decimal(10, 0), 1234.5) liefert 1235, keinen Fehler und kein NULL). Eine Konvertierung, die den Verlust von Nachkommastellen von sich aus als NULL signalisiert, gibt es in SQL Server nicht. Nur SET NUMERIC_ROUNDABORT ON macht aus dem stillen Rundungsschritt einen Fehler (Default: OFF).
Zuerst klären, ob das Quell-Locale bekannt ist — nur dann ist Normalisieren zulässig. Steht fest, dass das Komma Dezimaltrennzeichen und der Punkt Tausendertrennzeichen ist (deutsche Notation), normalisiert REPLACE(REPLACE(@p_input, '.', ''), ',', '.') vor dem TRY_CONVERT. Liefert die Spalte beide Notationen gemischt, lässt sich das nicht zuverlässig automatisch unterscheiden, denn '1,234' ist mehrdeutig. Solche Werte sind ein Datenqualitätsfehler der Eingangs-Datenquelle: zurückweisen und protokollieren statt raten. Im Zweifel pro Datensatz dokumentieren, welches Locale die Quelle nutzt.
TRY_CONVERT(int, …) NULL, obwohl die Zahl gültig aussieht? Die zwei häufigsten Ursachen sind Overflow und unsichtbare Zeichen. Eine Gegenprobe trennt die Fälle: Liefert TRY_CONVERT(bigint, …) plötzlich einen Wert, war es int-Overflow. Bleibt auch die Gegenprobe NULL, kommen zwei Ursachen infrage: eine ungültige Darstellung (verstecktes Trennzeichen, wissenschaftliche Notation oder Leerraum-Zeichen jenseits des normalen Leerzeichens char(32), etwa Tabs oder geschützte Leerzeichen, die das einfache TRIM nicht entfernt) oder ein Wert, der selbst den bigint-Wertebereich sprengt.
TRY_CONVERT(int, …)? Ein direktes Pendant gibt es nicht: CAST(s AS int) wirft bei ungültigem Input eine Exception, und ein eingebautes try_cast fehlt auch in Postgres 18. Der Weg ist ein PL/pgSQL-Wrapper wie fn_try_cast_bigint aus der Postgres-Brücke, der die Fehlerklassen invalid_text_representation und numeric_value_out_of_range gezielt zu NULL macht. Für formatierte Zahlen mit Tausendertrennzeichen ist to_number(s, format) der Postgres-Weg — Locale-abhängig über lc_numeric, anders als in SQL Server, wo das Session-Locale für die Integer-Konvertierung keine Rolle spielt.
Verwandte Artikel
ETL-Kontext:
- Datenqualität in einem ETL-Prozess
- ETL vs. ELT — woran du erkennst, welches Muster du wirklich gebaut hast — Makro-Einordnung: ETL vs. ELT als Architektur-Entscheidung, nicht als Reihenfolge der Buchstaben.
TRY_CONVERT für andere Datentypen:
- Datenqualität in SQL Server // TRY_CONVERT für date, datetime, datetime2 und time sicher anwenden
- Datenqualität in SQL Server // TRY_CONVERT für decimal und numeric sicher anwenden
- Datenqualität in SQL Server // TRY_CONVERT für money und smallmoney sicher anwenden
- Datenqualität in SQL Server // TRY_CONVERT für float und real sicher anwenden
- Datenqualität in SQL Server // TRY_CONVERT für bit sicher anwenden — Ja/Nein-Werte konvertieren
Grundlagen:
- Datenqualität // Grundlagen der Typ-Konvertierung mit T-SQL — CAST, CONVERT, TRY_CAST und TRY_CONVERT im Vergleich.