Datenqualität in SQL Server // TRY_CONVERT für date, datetime, datetime2 und time sicher anwenden

Wer einmal eine CSV-Spalte mit gemischten Datums-Formaten in eine datetime-Spalte importieren musste, weiß: An der Typ-Konvertierung entscheidet sich, ob fehlerhafte Datumswerte auffallen oder unbemerkt in die Pipeline gelangen. SQL Server lässt einen mit style-Codes alleine, sobald das Format vom Standard abweicht — TRY_CONVERT deckt die dokumentierten style-Formate ab, für alles andere braucht es eine eigene Parsing-Strategie.

Das nimmst du mit:

  • Wann TRY_CONVERT mit style-Code ausreicht — und wann eine eigene Funktion her muss.
  • Die wichtigsten style-Codes für date/datetime/datetime2/time im Überblick.
  • Vier wiederverwendbare UDFs fnConvertDatefnConvertTimefnConvertDateTimefnConvertDateTime2 als Implementierungs-Beispiel — angesprochen über sprechende Formatnamen (dd.mm.yyyy) statt numerischer style-Codes.
  • Die Postgres-Brücke (TO_DATETO_TIMESTAMP) als konzeptionelles Pendant für den Multi-Engine-ETL-Alltag — und wo dessen liberales Parsing zur Falle wird.
  • Ein FAQ-Block mit den häufigsten Stolperfallen aus dem CSV-/JSON-Import.

Voraussetzung: SQL Server 2012+ (TRY_CONVERT setzt Kompatibilitätsgrad 110+ voraus), für die Postgres-Brücke PostgreSQL 12+.

Inhalt

Überblick

Eine der heikelsten Konvertierungen im ETL-Alltag ist die eines Datums oder einer Uhrzeit, die als Text geliefert wird, in einen Wert vom Typ datetimedatetime oder datetime2.

SQL Server stellt für die Speicherung von Datum und Uhrzeit unter anderem die folgenden Datentypen zur Verfügung:

  • date
  • time
  • datetime
  • datetime2

Für die Konvertierung eines als Text gelieferten Datums stehen die Funktionen CONVERT und TRY_CONVERT als Standardfunktionen bereit, die über den Parameter style das jeweilige Datumsformat des gelieferten Datums identifizieren. So gibt zum Beispiel der style-Parameter 104 an, dass das zu konvertierende Datum im Parameter expression ein deutsches Datum mit vierstelliger Jahreszahl gemäß der Formatierungszeichenfolge dd.mm.yyyy ist.

  1: TRY_CONVERT(date, '24.04.2023', 104) -- 2023-04-24

Die unterstützten Datumsformate sind in der Online-Dokumentation unter CAST and CONVERT (Transact-SQL) beschrieben. Jedem Datumsformat ist ein style-Code zugewiesen, der als dritter Parameter an TRY_CONVERT oder CONVERT übergeben wird und damit die Interpretation des Datums steuert. Die Codes sind im Abschnitt style-Codes aufgeführt.

Der style-Parameter ist optional. Ohne ihn kann die Interpretation vom Session-Kontext abhängen (SET LANGUAGESET DATEFORMAT) — für ETL-Pipelines ist das nicht reproduzierbar, weshalb dieser Artikel durchgehend mit explizitem style-Code arbeitet. Daneben existiert mit TRY_PARSE ein .NET-basierter Parser mit culture-Parameter. Er ist deutlich langsamer und bleibt für Bulk-Imports deshalb außen vor.

Darüber hinaus kennt SQL Server einen zweiten Satz an Formatbezeichnern, den die Funktion FORMAT interpretiert, um ein Datum oder eine Zahl entsprechend der angegebenen Formatzeichenfolge in einen Text zu formatieren. So wird das Datum 24.04.2023 12:34:15.123 gemäß der Formatzeichenfolge yyyyMMddHHmmssfff in den Text 20230424123415123 übersetzt (HH steht für das 24-Stunden-Format — das kleingeschriebene hh wäre das 12-Stunden-Format und würde aus 22 Uhr eine 10 machen).

  1: FORMAT(TRY_CONVERT(datetime, N'24.04.2023 12:34:15.123', 104), 'yyyyMMddHHmmssfff')
  2: -- 20230424123415123

Weitere Informationen zu den benutzerdefinierten Formatzeichenfolgen finden sich im Abschnitt Formatbezeichner.

Umgekehrt fehlt SQL Server eine Konvertierungsfunktion, die ein als Text vorliegendes Datum gemäß einer solchen Formatzeichenfolge interpretiert. Wer ein Datum in einem Format erhält, zu dem kein style-Code existiert, muss die Konvertierung selbst entwickeln. Dieser Artikel stellt dafür vier benutzerdefinierte Funktionen vor, die einen Eingangswert in die Datentypen datetimedatetime oder datetime2 konvertieren:

Der Code der vier Funktionen steht im Abschnitt Benutzerdefinierte Funktionen.

Das Rückgabeergebnis hängt vom Zieldatentyp ab — dieser legt den unterstützten Wertebereich und (bei Uhrzeit-Anteilen) die Genauigkeit fest:

  • date: 01.01.0001 bis 31.12.9999, ohne Uhrzeit.
  • time: bis zu 7 Nachkommastellen bei Sekundenbruchteilen (100-Nanosekunden-Auflösung).
  • datetime: 01.01.1753 bis 31.12.9999, Granularität ~3,33 ms — Sekundenbruchteile werden auf .000.003 oder .007 gerundet, nicht frei wählbar auf drei Dezimalstellen.
  • datetime2: 01.01.0001 bis 31.12.9999, bis zu 7 Nachkommastellen. Das ISO-8601-Literalformat parst session-unabhängig.

Für neue Entwicklungen empfiehlt Microsoft datetime2 statt datetimedatetime2 hat einen größeren Wertebereich und präzisere Sekundenbruchteile, und das ISO-8601-Literalformat (yyyy-MM-ddTHH:mm:ss[.fffffff], Style 126) wird unabhängig von SET LANGUAGE und SET DATEFORMAT interpretiert. Die Genauigkeit ist wählbar (datetime2(0) bis datetime2(7)) und sollte zur fachlichen Anforderung passen. Ein detaillierter Vergleich zu Oracle (DATETIMESTAMP) und MySQL (DATETIMETIMESTAMP) ist bewusst ausgeklammert. Die Wertebereiche und Präzisionen anderer RDBMS-Anbieter unterscheiden sich erheblich und sind im jeweiligen Hersteller-Handbuch nachzuschlagen.

Entscheidungshilfe:

  • Das Quellformat entspricht einem dokumentierten style-Code → TRY_CONVERT mit einem deterministischen Style (vierstellig-numerisch wie 104/112 oder ISO wie 23/121/126).
  • Das Format ist als Formatzeichenfolge bekannt, aber ohne passenden style-Code → eigene Funktion (unten) oder Transformations-Schritt im Staging.
  • Mehrere Formate in einer Spalte → kaskadierte TRY_CONVERT-Versuche mit COALESCE (siehe FAQ).

style-Codes

Ein Auszug der im ETL-Alltag gebräuchlichsten Codes — die vollständige Liste steht in der MS-Doku unter CAST and CONVERT (Transact-SQL):

StyleLand/BeschreibungFormat (yy)Format (yyyy)Typischer Zieltyp
-/0/100Default für datetime & smalldatetimemon dd yyyy hh:miAMdatetime
1/101United Statesmm/dd/yymm/dd/yyyydate
2/102ANSIyy.mm.ddyyyy.mm.dddate
3/103England/Frankreichdd/mm/yydd/mm/yyyydate
4/104Deutschlanddd.mm.yydd.mm.yyyydate
10/110United Statesmm-dd-yymm-dd-yyyydate
12/112ISOyymmddyyyymmdddate
13/113Europa Default (mit ms, 24h)dd mon yyyy hh:mi:ss:mmmdd mon yyyy hh:mi:ss:mmmdatetime/datetime2

Formatbezeichner


Für die textliche Darstellung eines Datums stellt Microsoft einen Satz an Formatbezeichnern zur Verfügung. Ein Auszug der wichtigsten Bezeichner findet sich in der folgenden Tabelle:

FormatbezeichnerBeschreibung
dDer Tag des Monats, von 1 bis 31.
ddDer Tag des Monats, von 01 bis 31.
dddDer abgekürzte Name des Tags der Woche.
ddddDer vollständige Name des Wochentags.
fDie Zehntelsekunde in einem Datums- und Uhrzeitwert.
ffDie Hundertstelsekunde in einem Datums- und Uhrzeitwert.
fffDie Millisekunden in einem Datums- und Uhrzeitwert.
hDie Stunde, von 1 bis 12 (12-Stunden-Format).
hhDie Stunde, von 01 bis 12 (12-Stunden-Format).
HDie Stunde, von 0 bis 23 (24-Stunden-Format).
HHDie Stunde, von 00 bis 23 (24-Stunden-Format).
mDie Minute, von 0 bis 59.
mmDie Minute, von 00 bis 59.
MDer Monat, von 1 bis 12.
MMDer Monat, von 01 bis 12.
sDie Sekunde, von 0 bis 59.
ssDie Sekunde, von 00 bis 59.
yyDas Jahr, von 00 bis 99.
yyyyDas Jahr als vierstellige Zahl.

Benutzerdefinierte Funktionen

Die Implementierungen der Konvertierungs-Funktionen erwarten im Parameter p_date ein Datum, das als Text übergeben wird. Der Parameter p_date_style gibt eine benutzerdefinierte Formatzeichenfolge oder einen der oben genannten style-Codes an. Die erwarteten Formatzeichenfolgen entsprechen dabei nicht exakt den Formatbezeichnern aus dem Abschnitt Formatbezeichner — Microsoft definiert die Bezeichner für die style-Formate und für FORMAT unterschiedlich. In den style-Formaten steht mm für den Monat und mi für die Minute, bei FORMAT steht MM für den Monat und mm für die Minute. Die Funktionen gehen mit diesem Umstand pragmatisch um: Sie interpretieren die Formatzeichenfolge nicht Zeichen für Zeichen, sondern übersetzen sie in den zugehörigen style-Code oder behandeln sie als konkret implementierten Sonderfall. Formatzeichenfolgen werden unabhängig von Groß- und Kleinschreibung behandelt.

Einordnung: Die vier Funktionen sind Komfort-Wrapper für Strecken, in denen das Quellformat als sprechende Formatzeichenfolge konfiguriert wird — keine generelle Empfehlung, jede Text-Konvertierung über eine skalare UDF abzuwickeln. Skalare UDFs werden zeilenweise ausgeführt und können bei Massendaten zum Engpass werden. Ob der Optimizer eine solche Funktion inlinen kann (Scalar-UDF-Inlining, SQL Server 2019+), hängt von Funktion und Kompatibilitätsgrad ab und gehört bei großen ETL-Läufen separat geprüft. Dort ist die style-Code-Übersetzung oft besser direkt als Ausdruck im SELECT oder als vorgelagerter Staging-Schritt aufgehoben.

Deklaration

Die Deklaration der vier Funktionen folgt einem gemeinsamen Muster:

Beschreibung

Konvertiert einen übergebenen Eingangswert in den Zieldatentyp datetimedatetime oder datetime2. Kann der Eingangswert nicht umgewandelt werden, wird NULL zurückgegeben. Die Formatzeichenfolge wird vor der Auswertung mit LOWER() normalisiert und ist damit unabhängig von Groß- und Kleinschreibung.

Syntax (Beispiel)

  1: [dbo].[fnConvertDate] (@p_date AS nvarchar(50), @p_date_style nvarchar(50))

Argumente

  • p_date — gibt den umzuwandelnden Eingangswert an.
  • p_date_style — gibt eine Formatzeichenfolge an, gemäß der ein Datum in p_date übergeben wird, oder einen style-Code. Die Formatzeichenfolge oder der style-Code steuert die Umwandlung des Datums. Die unterstützten Formatzeichenfolgen und style-Codes sind dem Code zu entnehmen.

Rückgabe

Gibt den umgewandelten Wert als Wert vom Typ datetimedatetime oder datetime2 zurück, wenn die Umwandlung erfolgreich ist. Kann der Eingangswert nicht umgewandelt werden, wird NULL zurückgegeben. Wird NULL oder ein leerer String übergeben, gibt die Funktion NULL zurück.

[dbo].[fnConvertDate]

  1: CREATE FUNCTION [dbo].[fnConvertDate] (@p_date AS nvarchar(50), @p_date_style nvarchar(50))
  2: RETURNS date
  3: AS
  4: BEGIN
  5:    DECLARE @return_value AS date;
  6:    DECLARE @style       AS int;
  7: 
  8:    SET @p_date       = LTRIM(RTRIM(@p_date));
  9:    SET @p_date_style = LOWER(LTRIM(RTRIM(@p_date_style)));
 10: 
 11:    IF @p_date IS NULL OR @p_date = N''
 12:       BEGIN
 13:          SET @return_value = NULL;
 14:       END
 15:    ELSE
 16:       BEGIN
 17:          SET @style = CASE
 18:             WHEN @p_date_style IN (N'0',   N'100', N'mon dd yyyy hh:miam')             THEN 100
 19:             WHEN @p_date_style IN (N'1',   N'mm/dd/yy')                                THEN   1
 20:             WHEN @p_date_style IN (N'101', N'mm/dd/yyyy')                              THEN 101
 21:             WHEN @p_date_style IN (N'2',   N'yy.mm.dd')                                THEN   2
 22:             WHEN @p_date_style IN (N'102', N'yyyy.mm.dd')                              THEN 102
 23:             WHEN @p_date_style IN (N'3',   N'dd/mm/yy')                                THEN   3
 24:             WHEN @p_date_style IN (N'103', N'dd/mm/yyyy')                              THEN 103
 25:             WHEN @p_date_style IN (N'4',   N'dd.mm.yy')                                THEN   4
 26:             WHEN @p_date_style IN (N'104', N'dd.mm.yyyy')                              THEN 104
 27:             WHEN @p_date_style IN (N'5',   N'dd-mm-yy')                                THEN   5
 28:             WHEN @p_date_style IN (N'105', N'dd-mm-yyyy')                              THEN 105
 29:             WHEN @p_date_style IN (N'6',   N'dd mon yy')                               THEN   6
 30:             WHEN @p_date_style IN (N'106', N'dd mon yyyy')                             THEN 106
 31:             WHEN @p_date_style IN (N'7',   N'mon dd, yy')                              THEN   7
 32:             WHEN @p_date_style IN (N'107', N'mon dd, yyyy')                            THEN 107
 33:             WHEN @p_date_style IN (N'9',   N'109', N'mon dd yyyy hh:mi:ss:mmmam')      THEN 109
 34:             WHEN @p_date_style IN (N'10',  N'mm-dd-yy')                                THEN  10
 35:             WHEN @p_date_style IN (N'110', N'mm-dd-yyyy')                              THEN 110
 36:             WHEN @p_date_style IN (N'11',  N'yy/mm/dd')                                THEN  11
 37:             WHEN @p_date_style IN (N'111', N'yyyy/mm/dd')                              THEN 111
 38:             WHEN @p_date_style IN (N'12',  N'yymmdd')                                  THEN  12
 39:             WHEN @p_date_style IN (N'112', N'yyyymmdd')                                THEN 112
 40:             WHEN @p_date_style IN (N'13',  N'113', N'dd mon yyyy hh:mi:ss:mmm')        THEN 113
 41:             WHEN @p_date_style IN (N'20',  N'120', N'yyyy-mm-dd hh:mi:ss')             THEN 120
 42:             WHEN @p_date_style IN (N'21',  N'25', N'121', N'yyyy-mm-dd hh:mi:ss.mmm')  THEN 121
 43:             WHEN @p_date_style IN (N'22',  N'mm/dd/yy hh:mi:ss am')                    THEN  22
 44:             WHEN @p_date_style IN (N'23',  N'yyyy-mm-dd')                              THEN  23
 45:             WHEN @p_date_style IN (N'126', N'yyyy-mm-ddthh:mi:ss.mmm')                 THEN 126
 46:             WHEN @p_date_style IN (N'127', N'yyyy-mm-ddthh:mi:ss.mmmz')                THEN 127
 47:          END;
 48: 
 49:          IF @style IS NOT NULL
 50:             SET @return_value = TRY_CONVERT(date, @p_date, @style);
 51:          ELSE
 52:             SET @return_value = NULL;
 53:       END;
 54: 
 55:    RETURN @return_value;
 56: END;

[dbo].[fnConvertTime]

  1: CREATE FUNCTION [dbo].[fnConvertTime] (@p_time AS nvarchar(50), @p_time_style nvarchar(50))
  2: RETURNS time(7)
  3: AS
  4: BEGIN
  5:    DECLARE @return_value AS time(7);
  6:    DECLARE @style       AS int;
  7: 
  8:    SET @p_time       = LTRIM(RTRIM(@p_time));
  9:    SET @p_time_style = LOWER(LTRIM(RTRIM(@p_time_style)));
 10: 
 11:    IF @p_time IS NULL OR @p_time = N''
 12:       BEGIN
 13:          SET @return_value = NULL;
 14:       END
 15:    -- Kompakt-Datum+Zeit ohne Trenner: yyyymmddhhmmss[f...]
 16:    ELSE IF @p_time_style IN
 17:          (
 18:             N'yyyymmddhhmmss',        N'yyyymmddhhmmssf',
 19:             N'yyyymmddhhmmssff',      N'yyyymmddhhmmssfff',
 20:             N'yyyymmddhhmmssffff',    N'yyyymmddhhmmssfffff',
 21:             N'yyyymmddhhmmssffffff',  N'yyyymmddhhmmssfffffff'
 22:          )
 23:       BEGIN
 24:          SET @return_value = TRY_CONVERT(time(7)
 25:             ,SUBSTRING(@p_time,  1, 4) + N'-' +    -- yyyy
 26:              SUBSTRING(@p_time,  5, 2) + N'-' +    -- mm
 27:              SUBSTRING(@p_time,  7, 2) + N' ' +    -- dd
 28:              SUBSTRING(@p_time,  9, 2) + N':' +    -- hh
 29:              SUBSTRING(@p_time, 11, 2) + N':' +    -- mi
 30:              SUBSTRING(@p_time, 13, 2) +           -- ss
 31:              CASE WHEN LEN(@p_time) > 14
 32:                   THEN N'.' + SUBSTRING(@p_time, 15, LEN(@p_time) - 14)
 33:                   ELSE N''
 34:              END
 35:             );
 36:       END
 37:    -- Kompakt-Zeit ohne Trenner: hhmmss[f...]
 38:    ELSE IF @p_time_style IN
 39:          (
 40:             N'hhmmss',        N'hhmmssf',
 41:             N'hhmmssff',      N'hhmmssfff',
 42:             N'hhmmssffff',    N'hhmmssfffff',
 43:             N'hhmmssffffff',  N'hhmmssfffffff'
 44:          )
 45:       BEGIN
 46:          SET @return_value = TRY_CONVERT(time(7)
 47:             ,SUBSTRING(@p_time, 1, 2) + N':' +     -- hh
 48:              SUBSTRING(@p_time, 3, 2) + N':' +     -- mi
 49:              SUBSTRING(@p_time, 5, 2) +            -- ss
 50:              CASE WHEN LEN(@p_time) > 6
 51:                   THEN N'.' + SUBSTRING(@p_time, 7, LEN(@p_time) - 6)
 52:                   ELSE N''
 53:              END
 54:             );
 55:       END
 56:    -- Klassische MS-style-Codes mit Zeit-Anteil
 57:    ELSE
 58:       BEGIN
 59:          SET @style = CASE
 60:             WHEN @p_time_style IN (N'0',   N'100', N'mon dd yyyy hh:miam')             THEN 100
 61:             WHEN @p_time_style IN (N'8',   N'24', N'108', N'hh:mi:ss')                 THEN 108
 62:             WHEN @p_time_style IN (N'9',   N'109', N'mon dd yyyy hh:mi:ss:mmmam')      THEN 109
 63:             WHEN @p_time_style IN (N'13',  N'113', N'dd mon yyyy hh:mi:ss:mmm')        THEN 113
 64:             WHEN @p_time_style IN (N'14',  N'114', N'hh:mi:ss:mmm')                    THEN 114
 65:             WHEN @p_time_style IN (N'20',  N'120', N'yyyy-mm-dd hh:mi:ss')             THEN 120
 66:             WHEN @p_time_style IN (N'21',  N'25', N'121', N'yyyy-mm-dd hh:mi:ss.mmm')  THEN 121
 67:             WHEN @p_time_style IN (N'22',  N'mm/dd/yy hh:mi:ss am')                    THEN  22
 68:             WHEN @p_time_style IN (N'126', N'yyyy-mm-ddthh:mi:ss.mmm')                 THEN 126
 69:             WHEN @p_time_style IN (N'127', N'yyyy-mm-ddthh:mi:ss.mmmz')                THEN 127
 70:          END;
 71: 
 72:          IF @style IS NOT NULL
 73:             SET @return_value = TRY_CONVERT(time(7), @p_time, @style);
 74:          ELSE
 75:             SET @return_value = NULL;
 76:       END;
 77: 
 78:    RETURN @return_value;
 79: END;

[dbo].[fnConvertDateTime]

  1: CREATE FUNCTION [dbo].[fnConvertDateTime] (@p_date AS nvarchar(50), @p_date_style nvarchar(50))
  2: RETURNS datetime
  3: AS
  4: BEGIN
  5:    DECLARE @return_value AS datetime;
  6:    DECLARE @style       AS int;
  7: 
  8:    SET @p_date       = LTRIM(RTRIM(@p_date));
  9:    SET @p_date_style = LOWER(LTRIM(RTRIM(@p_date_style)));
 10: 
 11:    IF @p_date IS NULL OR @p_date = N''
 12:       BEGIN
 13:          SET @return_value = NULL;
 14:       END
 15:    -- Kompakt-Datum+Zeit ohne Trenner: yyyymmddhhmmss[fff]
 16:    ELSE IF @p_date_style IN
 17:          (
 18:             N'yyyymmddhhmmss',     N'yyyymmddhhmmssf',
 19:             N'yyyymmddhhmmssff',   N'yyyymmddhhmmssfff'
 20:          )
 21:       BEGIN
 22:          SET @return_value = TRY_CONVERT(datetime
 23:             ,SUBSTRING(@p_date,  1, 4) + N'-' +    -- yyyy
 24:              SUBSTRING(@p_date,  5, 2) + N'-' +    -- mm
 25:              SUBSTRING(@p_date,  7, 2) + N' ' +    -- dd
 26:              SUBSTRING(@p_date,  9, 2) + N':' +    -- hh
 27:              SUBSTRING(@p_date, 11, 2) + N':' +    -- mi
 28:              SUBSTRING(@p_date, 13, 2) +           -- ss
 29:              CASE WHEN LEN(@p_date) > 14
 30:                   THEN N'.' + SUBSTRING(@p_date, 15, LEN(@p_date) - 14)
 31:                   ELSE N''
 32:              END
 33:             );
 34:       END
 35:    -- Sonderform mit Unterstrich-Separator: yyyymmdd_hhmissmmm
 36:    ELSE IF @p_date_style = N'yyyymmdd_hhmissmmm'
 37:       BEGIN
 38:          SET @return_value = TRY_CONVERT(datetime
 39:             ,SUBSTRING(@p_date,  1, 4) + N'-' +    -- yyyy
 40:              SUBSTRING(@p_date,  5, 2) + N'-' +    -- mm
 41:              SUBSTRING(@p_date,  7, 2) + N' ' +    -- dd
 42:              SUBSTRING(@p_date, 10, 2) + N':' +    -- hh (nach Unterstrich)
 43:              SUBSTRING(@p_date, 12, 2) + N':' +    -- mi
 44:              SUBSTRING(@p_date, 14, 2) + N'.' +    -- ss
 45:              SUBSTRING(@p_date, 16, 3)             -- mmm
 46:             );
 47:       END
 48:    -- Klassische MS-style-Codes
 49:    ELSE
 50:       BEGIN
 51:          SET @style = CASE
 52:             WHEN @p_date_style IN (N'0',   N'100', N'mon dd yyyy hh:miam')             THEN 100
 53:             WHEN @p_date_style IN (N'1',   N'mm/dd/yy')                                THEN   1
 54:             WHEN @p_date_style IN (N'101', N'mm/dd/yyyy')                              THEN 101
 55:             WHEN @p_date_style IN (N'2',   N'yy.mm.dd')                                THEN   2
 56:             WHEN @p_date_style IN (N'102', N'yyyy.mm.dd')                              THEN 102
 57:             WHEN @p_date_style IN (N'3',   N'dd/mm/yy')                                THEN   3
 58:             WHEN @p_date_style IN (N'103', N'dd/mm/yyyy')                              THEN 103
 59:             WHEN @p_date_style IN (N'4',   N'dd.mm.yy')                                THEN   4
 60:             WHEN @p_date_style IN (N'104', N'dd.mm.yyyy')                              THEN 104
 61:             WHEN @p_date_style IN (N'5',   N'dd-mm-yy')                                THEN   5
 62:             WHEN @p_date_style IN (N'105', N'dd-mm-yyyy')                              THEN 105
 63:             WHEN @p_date_style IN (N'6',   N'dd mon yy')                               THEN   6
 64:             WHEN @p_date_style IN (N'106', N'dd mon yyyy')                             THEN 106
 65:             WHEN @p_date_style IN (N'7',   N'mon dd, yy')                              THEN   7
 66:             WHEN @p_date_style IN (N'107', N'mon dd, yyyy')                            THEN 107
 67:             WHEN @p_date_style IN (N'8',   N'24', N'108', N'hh:mi:ss')                 THEN 108
 68:             WHEN @p_date_style IN (N'9',   N'109', N'mon dd yyyy hh:mi:ss:mmmam')      THEN 109
 69:             WHEN @p_date_style IN (N'10',  N'mm-dd-yy')                                THEN  10
 70:             WHEN @p_date_style IN (N'110', N'mm-dd-yyyy')                              THEN 110
 71:             WHEN @p_date_style IN (N'11',  N'yy/mm/dd')                                THEN  11
 72:             WHEN @p_date_style IN (N'111', N'yyyy/mm/dd')                              THEN 111
 73:             WHEN @p_date_style IN (N'12',  N'yymmdd')                                  THEN  12
 74:             WHEN @p_date_style IN (N'112', N'yyyymmdd')                                THEN 112
 75:             WHEN @p_date_style IN (N'13',  N'113', N'dd mon yyyy hh:mi:ss:mmm')        THEN 113
 76:             WHEN @p_date_style IN (N'14',  N'114', N'hh:mi:ss:mmm')                    THEN 114
 77:             WHEN @p_date_style IN (N'20',  N'120', N'yyyy-mm-dd hh:mi:ss')             THEN 120
 78:             WHEN @p_date_style IN (N'21',  N'25', N'121', N'yyyy-mm-dd hh:mi:ss.mmm')  THEN 121
 79:             WHEN @p_date_style IN (N'22',  N'mm/dd/yy hh:mi:ss am')                    THEN  22
 80:             WHEN @p_date_style IN (N'23',  N'yyyy-mm-dd')                              THEN  23
 81:             WHEN @p_date_style IN (N'126', N'yyyy-mm-ddthh:mi:ss.mmm')                 THEN 126
 82:             WHEN @p_date_style IN (N'127', N'yyyy-mm-ddthh:mi:ss.mmmz')                THEN 127
 83:          END;
 84: 
 85:          IF @style IS NOT NULL
 86:             SET @return_value = TRY_CONVERT(datetime, @p_date, @style);
 87:          ELSE
 88:             SET @return_value = NULL;
 89:       END;
 90: 
 91:    RETURN @return_value;
 92: END;

[dbo].[fnConvertDateTime2]

  1: CREATE FUNCTION [dbo].[fnConvertDateTime2] (@p_date AS nvarchar(50), @p_date_style nvarchar(50))
  2: RETURNS datetime2(7)
  3: AS
  4: BEGIN
  5:    DECLARE @return_value AS datetime2(7);
  6:    DECLARE @style       AS int;
  7: 
  8:    SET @p_date       = LTRIM(RTRIM(@p_date));
  9:    SET @p_date_style = LOWER(LTRIM(RTRIM(@p_date_style)));
 10: 
 11:    IF @p_date IS NULL OR @p_date = N''
 12:       BEGIN
 13:          SET @return_value = NULL;
 14:       END
 15:    -- Kompakt-Datum+Zeit ohne Trenner: yyyymmddhhmmss[f...] (bis 7 Stellen)
 16:    ELSE IF @p_date_style IN
 17:          (
 18:             N'yyyymmddhhmmss',        N'yyyymmddhhmmssf',
 19:             N'yyyymmddhhmmssff',      N'yyyymmddhhmmssfff',
 20:             N'yyyymmddhhmmssffff',    N'yyyymmddhhmmssfffff',
 21:             N'yyyymmddhhmmssffffff',  N'yyyymmddhhmmssfffffff'
 22:          )
 23:       BEGIN
 24:          SET @return_value = TRY_CONVERT(datetime2(7)
 25:             ,SUBSTRING(@p_date,  1, 4) + N'-' +    -- yyyy
 26:              SUBSTRING(@p_date,  5, 2) + N'-' +    -- mm
 27:              SUBSTRING(@p_date,  7, 2) + N' ' +    -- dd
 28:              SUBSTRING(@p_date,  9, 2) + N':' +    -- hh
 29:              SUBSTRING(@p_date, 11, 2) + N':' +    -- mi
 30:              SUBSTRING(@p_date, 13, 2) +           -- ss
 31:              CASE WHEN LEN(@p_date) > 14
 32:                   THEN N'.' + SUBSTRING(@p_date, 15, LEN(@p_date) - 14)
 33:                   ELSE N''
 34:              END
 35:             );
 36:       END
 37:    -- Sonderform mit Unterstrich-Separator: yyyymmdd_hhmissmmm
 38:    ELSE IF @p_date_style = N'yyyymmdd_hhmissmmm'
 39:       BEGIN
 40:          SET @return_value = TRY_CONVERT(datetime2(7)
 41:             ,SUBSTRING(@p_date,  1, 4) + N'-' +    -- yyyy
 42:              SUBSTRING(@p_date,  5, 2) + N'-' +    -- mm
 43:              SUBSTRING(@p_date,  7, 2) + N' ' +    -- dd
 44:              SUBSTRING(@p_date, 10, 2) + N':' +    -- hh
 45:              SUBSTRING(@p_date, 12, 2) + N':' +    -- mi
 46:              SUBSTRING(@p_date, 14, 2) + N'.' +    -- ss
 47:              SUBSTRING(@p_date, 16, 3)             -- mmm
 48:             );
 49:       END
 50:    -- Klassische MS-style-Codes
 51:    ELSE
 52:       BEGIN
 53:          SET @style = CASE
 54:             WHEN @p_date_style IN (N'0',   N'100', N'mon dd yyyy hh:miam')             THEN 100
 55:             WHEN @p_date_style IN (N'1',   N'mm/dd/yy')                                THEN   1
 56:             WHEN @p_date_style IN (N'101', N'mm/dd/yyyy')                              THEN 101
 57:             WHEN @p_date_style IN (N'2',   N'yy.mm.dd')                                THEN   2
 58:             WHEN @p_date_style IN (N'102', N'yyyy.mm.dd')                              THEN 102
 59:             WHEN @p_date_style IN (N'3',   N'dd/mm/yy')                                THEN   3
 60:             WHEN @p_date_style IN (N'103', N'dd/mm/yyyy')                              THEN 103
 61:             WHEN @p_date_style IN (N'4',   N'dd.mm.yy')                                THEN   4
 62:             WHEN @p_date_style IN (N'104', N'dd.mm.yyyy')                              THEN 104
 63:             WHEN @p_date_style IN (N'5',   N'dd-mm-yy')                                THEN   5
 64:             WHEN @p_date_style IN (N'105', N'dd-mm-yyyy')                              THEN 105
 65:             WHEN @p_date_style IN (N'6',   N'dd mon yy')                               THEN   6
 66:             WHEN @p_date_style IN (N'106', N'dd mon yyyy')                             THEN 106
 67:             WHEN @p_date_style IN (N'7',   N'mon dd, yy')                              THEN   7
 68:             WHEN @p_date_style IN (N'107', N'mon dd, yyyy')                            THEN 107
 69:             WHEN @p_date_style IN (N'8',   N'24', N'108', N'hh:mi:ss')                 THEN 108
 70:             WHEN @p_date_style IN (N'9',   N'109', N'mon dd yyyy hh:mi:ss:mmmam')      THEN 109
 71:             WHEN @p_date_style IN (N'10',  N'mm-dd-yy')                                THEN  10
 72:             WHEN @p_date_style IN (N'110', N'mm-dd-yyyy')                              THEN 110
 73:             WHEN @p_date_style IN (N'11',  N'yy/mm/dd')                                THEN  11
 74:             WHEN @p_date_style IN (N'111', N'yyyy/mm/dd')                              THEN 111
 75:             WHEN @p_date_style IN (N'12',  N'yymmdd')                                  THEN  12
 76:             WHEN @p_date_style IN (N'112', N'yyyymmdd')                                THEN 112
 77:             WHEN @p_date_style IN (N'13',  N'113', N'dd mon yyyy hh:mi:ss:mmm')        THEN 113
 78:             WHEN @p_date_style IN (N'14',  N'114', N'hh:mi:ss:mmm')                    THEN 114
 79:             WHEN @p_date_style IN (N'20',  N'120', N'yyyy-mm-dd hh:mi:ss')             THEN 120
 80:             WHEN @p_date_style IN (N'21',  N'25', N'121', N'yyyy-mm-dd hh:mi:ss.mmm')  THEN 121
 81:             WHEN @p_date_style IN (N'22',  N'mm/dd/yy hh:mi:ss am')                    THEN  22
 82:             WHEN @p_date_style IN (N'23',  N'yyyy-mm-dd')                              THEN  23
 83:             WHEN @p_date_style IN (N'126', N'yyyy-mm-ddthh:mi:ss.mmm')                 THEN 126
 84:             WHEN @p_date_style IN (N'127', N'yyyy-mm-ddthh:mi:ss.mmmz')                THEN 127
 85:          END;
 86: 
 87:          IF @style IS NOT NULL
 88:             SET @return_value = TRY_CONVERT(datetime2(7), @p_date, @style);
 89:          ELSE
 90:             SET @return_value = NULL;
 91:       END;
 92: 
 93:    RETURN @return_value;
 94: END;

Demo-Aufrufe

Eine kompakte Demonstration der vier Funktionen — typische Aufrufe entlang der unterstützten style-Codes und Formatzeichenfolgen, plus Negativ-Fälle, die bei dieser Implementierung NULL zurückgeben.

  1: -- -----------------------------------------------------------------------------
  2: -- 1) Klassische style-Codes als String oder als sprechender Format-Bezeichner
  3: -- -----------------------------------------------------------------------------
  4: SELECT [dbo].[fnConvertDate]    (N'24.04.2023',                       N'104'        ) AS d1   -- 2023-04-24
  5:       ,[dbo].[fnConvertDate]    (N'24.04.2023',                       N'dd.mm.yyyy' ) AS d2   -- identisch: Format-String statt Code
  6:       ,[dbo].[fnConvertDate]    (N'2023-04-24',                       N'23'         ) AS d3
  7:       ,[dbo].[fnConvertDate]    (N'24/04/2023',                       N'103'        ) AS d4
  8:       ,[dbo].[fnConvertDate]    (N'20230424',                         N'112'        ) AS d5;
  9: 
 10: -- -----------------------------------------------------------------------------
 11: -- 2) Zeit-Anteil
 12: -- -----------------------------------------------------------------------------
 13: SELECT [dbo].[fnConvertTime]    (N'12:34:15',                         N'hh:mi:ss'                  ) AS t1
 14:       ,[dbo].[fnConvertTime]    (N'2023-04-24 12:34:15.1234567',     N'yyyy-mm-dd hh:mi:ss.mmm'   ) AS t2   -- Code 121: Datum wird verworfen, Zeit-Anteil bleibt
 15:       ,[dbo].[fnConvertTime]    (N'123415',                           N'hhmmss'                    ) AS t3
 16:       ,[dbo].[fnConvertTime]    (N'1234151234567',                    N'hhmmssfffffff'             ) AS t4;
 17: 
 18: -- -----------------------------------------------------------------------------
 19: -- 3) Datum + Zeit als datetime (Granularitaet ~3,33 ms)
 20: -- -----------------------------------------------------------------------------
 21: SELECT [dbo].[fnConvertDateTime](N'24.04.2023 12:34:15',              N'104'                       ) AS dt1   -- Style 104 ignoriert die Zeit
 22:       ,[dbo].[fnConvertDateTime](N'2023-04-24T12:34:15.123',          N'126'                       ) AS dt2
 23:       ,[dbo].[fnConvertDateTime](N'20230424123415',                   N'yyyymmddhhmmss'            ) AS dt3
 24:       ,[dbo].[fnConvertDateTime](N'20230424123415123',                N'yyyymmddhhmmssfff'         ) AS dt4
 25:       ,[dbo].[fnConvertDateTime](N'20230424_123415123',               N'yyyymmdd_hhmissmmm'        ) AS dt5;
 26: 
 27: -- -----------------------------------------------------------------------------
 28: -- 4) datetime2 mit voller 100-Nanosekunden-Aufloesung
 29: -- -----------------------------------------------------------------------------
 30: SELECT [dbo].[fnConvertDateTime2](N'2023-04-24T12:34:15.1234567',     N'126'                       ) AS d21
 31:       ,[dbo].[fnConvertDateTime2](N'24.04.2023',                      N'dd.mm.yyyy'                ) AS d22
 32:       ,[dbo].[fnConvertDateTime2](N'20230424123415',                  N'yyyymmddhhmmss'            ) AS d23
 33:       ,[dbo].[fnConvertDateTime2](N'20230424123415123',               N'yyyymmddhhmmssfff'         ) AS d24
 34:       ,[dbo].[fnConvertDateTime2](N'202304241234151234567',           N'yyyymmddhhmmssfffffff'     ) AS d25;
 35: 
 36: -- -----------------------------------------------------------------------------
 37: -- 5) Negative Faelle - alle geben NULL zurueck
 38: -- -----------------------------------------------------------------------------
 39: SELECT [dbo].[fnConvertDate]    (N'',                                 N'104'                       ) AS n1   -- leerer Eingang
 40:       ,[dbo].[fnConvertDate]    (NULL,                                N'104'                       ) AS n2   -- NULL
 41:       ,[dbo].[fnConvertDate]    (N'24.04.2023',                       N'unbekannt'                 ) AS n3   -- unbekannter Format-String
 42:       ,[dbo].[fnConvertDate]    (N'32.04.2023',                       N'104'                       ) AS n4   -- ungueltiger Tag
 43:       ,[dbo].[fnConvertDate]    (N'irgendwas',                        N'104'                       ) AS n5;  -- nicht parsebar

Postgres-Brücke

Wer ETL-Strecken aus SQL Server nach Postgres portiert oder beide Engines parallel betreibt, sucht früher oder später das Pendant zu TRY_CONVERT(date, …, 104). Postgres bietet TO_DATE und TO_TIMESTAMP — beide nehmen Format-Patterns, keine numerischen style-Codes. Damit ist die Aufruf-Form näher an der Schnittstelle der fnConvertDate-Funktion aus diesem Artikel als an der numerischen style-Code-Logik von TRY_CONVERT:

  1: -- SQL Server
  2: TRY_CONVERT(date, '24.04.2023', 104);                                     -- 2023-04-24
  3: 
  4: -- Postgres
  5: SELECT TO_DATE      ('24.04.2023',          'DD.MM.YYYY');                -- 2023-04-24
  6: SELECT TO_TIMESTAMP ('24.04.2023 12:34:15', 'DD.MM.YYYY HH24:MI:SS');     -- 2023-04-24 12:34:15

Pattern-Korrespondenz für die häufigsten Formate

SQL Server TRY_CONVERT (style)Postgres TO_DATE / TO_TIMESTAMP (Pattern)Beispiel-Eingabe
104 — dd.mm.yyyy (Deutsch)DD.MM.YYYY24.04.2023
103 — dd/mm/yyyy (Brit./Franz.)DD/MM/YYYY24/04/2023
101 — mm/dd/yyyy (USA)MM/DD/YYYY04/24/2023
102 — yyyy.mm.dd (ANSI)YYYY.MM.DD2023.04.24
112 — yyyymmdd (ISO)YYYYMMDD20230424
23 — yyyy-mm-dd (ISO 8601)YYYY-MM-DD2023-04-24
120 — yyyy-mm-dd hh:mi:ss (ODBC)YYYY-MM-DD HH24:MI:SS2023-04-24 12:34:15
121 — yyyy-mm-dd hh:mi:ss.mmm (ODBC mit ms)YYYY-MM-DD HH24:MI:SS.MS2023-04-24 12:34:15.123
126 — yyyy-mm-ddThh:mi:ss.mmm (ISO 8601)YYYY-MM-DD"T"HH24:MI:SS.MS2023-04-24T12:34:15.123
127 — ISO 8601 mit Zeitzone ZYYYY-MM-DD"T"HH24:MI:SS.MS"Z"2023-04-24T12:34:15.123Z

NULL statt Exception: Eigenes try_to_date

Anders als TRY_CONVERT liefert TO_DATE bei einer nicht parsebaren Eingabe kein NULL, sondern wirft eine Exception — invalid_datetime_format (SQLSTATE 22007) bei unlesbaren Zeichen, datetime_field_overflow (22008) bei Werten außerhalb des Kalenders wie '32.04.2023'. Für Bulk-Imports, die mit Fehl-Datensätzen tolerant umgehen sollen, kapselt man das in einer kleinen PL/pgSQL-Funktion. Sie ist ein Fangnetz für Parse-Fehler, noch keine Format-Validierung (dazu gleich mehr). Gefangen werden gezielt die beiden Datums-Fehlerklassen, denn ein pauschales WHEN OTHERS würde auch Fehler verschlucken, die nichts mit der Eingabe zu tun haben:

  1: CREATE OR REPLACE FUNCTION try_to_date (p_text text, p_pattern text)
  2:    RETURNS date
  3:    LANGUAGE plpgsql
  4: AS $$
  5: BEGIN
  6:    RETURN TO_DATE(p_text, p_pattern);
  7: EXCEPTION
  8:    WHEN invalid_datetime_format OR datetime_field_overflow THEN
  9:       RETURN NULL;
 10: END;
 11: $$;
 12: 
 13: SELECT try_to_date('irgendwas',  'DD.MM.YYYY');   -- NULL statt Exception
 14: SELECT try_to_date('24.04.2023', 'DD.MM.YYYY');   -- 2023-04-24

Der Exception-Wrapper allein ist aber noch keine strikte Validierung. TO_DATE parst bewusst liberal: Trennzeichen müssen nicht exakt dem Pattern entsprechen (24/04/2023 passt auch auf DD.MM.YYYY), und kürzere Ziffern-Gruppen werden wörtlich übernommen. TO_DATE('24.4.23', 'DD.MM.YYYY') wirft deshalb keine Exception, sondern liefert stillschweigend das Jahr 23 (0023-04-24). Die 2020-Näherungs-Regel der Postgres-Doku greift nur bei Jahres-Patterns mit weniger als vier Stellen, bei YYYY zählt der Wert wörtlich. Auch der FX-Modifier („fixed format“) schließt diese Lücke nicht: Er unterbindet das Überspringen von zusätzlichem Leerraum, akzeptiert aber weiterhin fremde Einzel-Trennzeichen (TO_DATE('24/04/2023', 'FXDD.MM.YYYY') parst anstandslos) und verkürzte Ziffern-Gruppen (Verhalten verifiziert auf PostgreSQL 17).

Ein try_to_date, das nur Exceptions abfängt, akzeptiert solche Eingaben deshalb als gültig — genau die Sorte Fehler, die eine Datenqualitäts-Strecke finden soll. Strikt wird die Prüfung über einen Round-Trip: Das Ergebnis wird mit demselben Pattern zurückformatiert und muss die Eingabe exakt reproduzieren. Das erzwingt die kanonische Darstellung des Patterns (zweistellige Tage und Monate, vierstelliges Jahr, exakte Trennzeichen) und kommt der style-Code-Semantik von TRY_CONVERT damit sehr nahe:

  1: CREATE OR REPLACE FUNCTION try_to_date_strict (p_text text, p_pattern text)
  2:    RETURNS date
  3:    LANGUAGE plpgsql
  4: AS $$
  5: DECLARE
  6:    l_result   date;
  7: BEGIN
  8:    l_result := TO_DATE(p_text, p_pattern);
  9: 
 10:    -- Round-Trip: nur die kanonische Darstellung des Patterns gilt als valide
 11:    IF TO_CHAR(l_result, p_pattern) <> p_text THEN
 12:       RETURN NULL;
 13:    END IF;
 14: 
 15:    RETURN l_result;
 16: EXCEPTION
 17:    WHEN invalid_datetime_format OR datetime_field_overflow THEN
 18:       RETURN NULL;
 19: END;
 20: $$;
 21: 
 22: SELECT try_to_date_strict('24.04.2023', 'DD.MM.YYYY');   -- 2023-04-24
 23: SELECT try_to_date_strict('24.4.23',    'DD.MM.YYYY');   -- NULL statt 0023-04-24

Der Round-Trip trägt für kanonische numerische Patterns wie DD.MM.YYYY. Für Patterns mit Monatsnamen oder FM-Modifier ist er kein universeller Validator: Dort weicht die TO_CHAR-Rückgabe auch bei akzeptierten Eingaben von der Original-Schreibweise ab — '24-OCT-2023' parst, wird aber als 24-Oct-2023 zurückformatiert und fiele durch den Vergleich.

Locale-Abhängigkeit beachten

Monatsnamen-Patterns (MONMONTHDYDAY) parsen in Postgres ausschließlich englische Namen. Der TM-Modifier, der bei TO_CHAR lokalisierte Monats- und Wochentagsnamen erzeugt, wird von TO_DATE/TO_TIMESTAMP ignoriert — das lc_time-Setting der Session hat auf das Parsen keinen Einfluss. TO_DATE('24-Oct-2023', 'DD-Mon-YYYY') funktioniert deshalb auch bei deutscher Locale, das deutsche '24-Okt-2023' scheitert dagegen immer mit invalid_datetime_format. Das ist die umgekehrte Situation zu SQL Server, wo Monatsnamen-Styles je nach SET LANGUAGE unterschiedlich parsen (siehe FAQ). Für reproduzierbare ETL-Pipelines gilt in beiden Engines derselbe Rat: rein numerische Patterns (DDMMYYYY) verwenden.

Zusammenfassung

SQL Server unterstützt bei der Konvertierung eines Textes in ein Datum nur die unter CAST and CONVERT (Transact-SQL) dokumentierten Datumsformate und weist den dort aufgelisteten Formaten einen style-Code zu.

Wenn Daten aus Quellsystemen über eine Text-Datei (CSV, XML, JSON) geliefert werden, muss unbedingt geklärt werden, in welchem Format ein Datum geliefert wird. Existiert zu einem Datumsformat kein style-Code, braucht es eine eigene Parsing- oder Transformations-Logik — als Ausdruck im Staging, im ETL-Werkzeug oder als benutzerdefinierte Funktion wie in diesem Artikel.

Dieser Artikel stellt vier Funktionen vor, die ein Datum in die Datentypen datetimedatetime oder datetime2 konvertieren. Die Funktionen übersetzen die übergebene Formatzeichenfolge in einen style-Code und konvertieren das ebenfalls übergebene Datum in den jeweiligen Zieldatentyp.

Take-Away:

  • TRY_CONVERT mit einem expliziten deterministischen style-Code — vierstellig-numerisch wie 104/112 oder ISO wie 121/126 — ist die erste Wahl für CSV-/JSON-Datums-Imports. yy-Codes und Monatsnamen-Styles (106107109113) bleiben kontext-abhängig.
  • Wenn das Quellformat als sprechende Formatzeichenfolge (dd.mm.yyyy) bekannt ist, nimm die fn-Wrapper aus diesem Artikel. Sie sind lesbar und kommen ohne Magic Numbers aus.
  • Für neue Anwendungsfälle ist in der Regel datetime2 statt datetime die bessere Wahl — mit einer Genauigkeit (datetime2(0) bis datetime2(7)), die zur fachlichen Anforderung passt. Das ISO-8601-Literalformat parst datetime2 unabhängig von den Session-Einstellungen.
  • Beim Cross-Engine-ETL übernehmen TO_DATE/TO_TIMESTAMP mit Format-Patterns diese Rolle konzeptionell — semantisch identisch mit TRY_CONVERT sind sie nicht: Sie parsen liberal (auch mit FX-Modifier), strikte Prüfung liefert erst der Round-Trip (siehe Postgres-Brücke). Robust bleiben rein numerische Patterns (DDMMYYYY).

FAQ

Warum gibt TRY_CONVERT NULL statt einer Fehlermeldung?

TRY_CONVERT ist das fehlertolerante Gegenstück zu CONVERT. Wenn die Konvertierung scheitert — falsches Format, ungültiger Wertebereich, ein sprach-abhängiger Style auf falsch konfigurierter Session — bekommst du NULL statt einer Exception, die die ganze Pipeline abbricht. Genau das macht die Funktion für ETL-Bulk-Imports brauchbar: Fehl-Datensätze landen in einer Reject-Spalte statt das Insert zu sprengen. Wenn du explizit das harte Verhalten brauchst (z. B. um Format-Bugs früh aufzudecken), nimm CONVERT ohne TRY_.

Was tun bei gemischten Sprach-Formaten in einer Spalte?

Ein CSV-Import kann durchaus 24.04.2023 (Deutsch), 04/24/2023 (USA) und 2023-04-24 (ISO) gemischt enthalten. TRY_CONVERT mit einem fest gewählten Style erschlägt nur eines der drei. Als pragmatische Lösung sind mehrere TRY_CONVERT-Aufrufe mit COALESCE zu kaskadieren — der erste, der nicht NULL zurückgibt, gewinnt:

  1: COALESCE(
  2:    TRY_CONVERT(date, @value, 104),    -- dd.mm.yyyy
  3:    TRY_CONVERT(date, @value, 101),    -- mm/dd/yyyy
  4:    TRY_CONVERT(date, @value,  23)     -- yyyy-mm-dd (ISO 8601)
  5: )

Die Reihenfolge ist so zu wählen, dass das wahrscheinlichste Quellformat zuerst getestet wird.

datetime oder datetime2 — welcher Datentyp wann?

Für neue Entwicklungen ist in der Regel datetime2 die richtige Wahl: ein größerer Wertebereich (ab 0001-01-01), bis zu 100-ns-Genauigkeit statt ~3,33 ms, und das ISO-8601-Literalformat (yyyy-MM-ddTHH:mm:ss[.fffffff]) parst unabhängig von SET LANGUAGE und SET DATEFORMAT. Die Genauigkeit gehört zur fachlichen Anforderung: datetime2(0) reicht für Sekunden-Auflösung, datetime2(3) für Millisekunden, datetime2(7) nur bei echtem Bedarf an 100-ns-Auflösung. datetime bleibt relevant, wenn ein Legacy-Schema darauf festgenagelt ist.

Hängt das Konvertierungs-Ergebnis vom Server-SET LANGUAGE / SET DATEFORMAT ab?

Ja — und zwar genau bei den style-Codes, die laut MS-Doku „nicht-deterministisch“ sind: 0/1009/10913/113 und alle yy-Varianten (123456710111222) parsen Monatsnamen oder zweistellige Jahre kontextabhängig. '24 Dez 2023' parst mit Style 106 unter SET LANGUAGE Deutsch, unter us_english liefert derselbe Aufruf NULL. Für deterministische ETL-Pipelines sind daher die vierstellig-numerischen Varianten (z. B. 104 statt 4) und die ISO-Codes (23121126) zu bevorzugen. Vierstellig allein reicht nicht: Monatsnamen-Styles wie 106/113 bleiben trotz yyyy-Jahr sprach-abhängig.

Postgres-Pendant für TRY_CONVERT(date, …, 104)?

TO_DATE('24.04.2023', 'DD.MM.YYYY') — siehe Postgres-Brücke für die Pattern-Korrespondenz-Tabelle und die try_to_date-Wrapper-Funktion, die das NULL-Verhalten von TRY_CONVERT nachbildet. Ein exaktes 1:1-Pendant ist das nicht: TO_DATE parst liberal (Trennzeichen und Ziffern-Breiten sind nicht strikt). Für strikte Validierung dient die Round-Trip-Variante try_to_date_strict aus derselben Sektion.

Verwandte Artikel

ETL-Kontext:

TRY_CONVERT für andere Datentypen:

Grundlagen: