Datenqualität in SQL Server // TRY_CONVERT für bit sicher anwenden — Ja/Nein-Werte konvertieren

Datenqualität beginnt bei der Typ-Konvertierung — und bei bit-Spalten zeigt sich das schon beim Eingangswert: Yes/No-Informationen liegen in Legacy-Quellen in unterschiedlichsten Notationen vor ('J''Y''ON''1''x''-', …). SQL Servers eingebautes TRY_CONVERT(bit, …) deckt nur den Ziffern-Standard plus 'true'/'false' ab — alles andere braucht eine eigene Konvertierungs-Funktion. Dieser Artikel beschreibt beides: was eingebaut funktioniert, wann eine fn_convert_bit-Funktion her muss und wie das Pendant in Postgres aussieht.

Kurzüberblick:

  • Der Wertebereich eines bit umfasst logisch genau ein Bit Information (0/1, plus NULL als dritter Zustand — dreiwertige Logik). SQL Server speichert BIT-Spalten platzsparend und packt bis zu 8 BIT-Spalten einer Zeile in ein Byte.
  • TRY_CONVERT(bit, …) versteht den Ziffern-Standard plus 'true'/'false' — case-insensitiv und locale-unabhängig.
  • Eigene Funktion [dbo].[fn_convert_bit] mappt Yes/No-Notationen ('J'/'YES'/'ON'/'x'/'-', …) auf 0/1 und liefert NULL für unbekannte Werte.
  • Postgres-Brücke: boolean als nativer Typ, s::boolean mit eingebauter Yes/No-Toleranz, plus try_cast_boolean-Wrapper für non-standard Notationen.

Voraussetzung: SQL Server 2017+ (die fn_convert_bit-Funktion unten verwendet TRIM, das erst ab 2017 verfügbar ist — auf 2012–2016 stattdessen LTRIM(RTRIM(...))). Beispiele sind ohne Sample-Datensatz lauffähig — reine Inline-Literale.

Inhalt

Datentyp bit — Wertebereich und Speicherbedarf

EigenschaftWert
Wertebereich01 und NULL (dreiwertige Logik: 0, 1, „unbekannt“)
SpeicherbedarfLogisch ein Bit an Information pro Wert. SQL Server speichert BIT-Spalten platzsparend und packt bis zu 8 BIT-Spalten einer Zeile in 1 Byte.
Vergleich mit booleanSQL Server besitzt keinen nativen BOOLEAN-Datentyp für Spalten oder Variablen. Boolesche Ausdrücke existieren nur intern in Prädikaten und Bedingungen (IS NULLEXISTS, …). Postgres hat boolean als nativen Datentyp.

Die NULL-Möglichkeit ist im Import-Kontext relevant: ein leerer CSV-Wert ist semantisch meist „unbekannt“ und sollte als NULL landen — nicht als 0. Die in den folgenden Sektionen vorgestellten Konvertierungs-Wege mappen leere Zeichenfolgen daher konsequent auf NULL.

TRY_CONVERT(bit, …) — was eingebaut funktioniert

Bevor man eine eigene Funktion schreibt, lohnt sich der Blick auf das, was SQL Server schon liefert. TRY_CONVERT(bit, …) versteht eine schmale, aber präzise Liste von Inputs. Die Verarbeitung ist case-insensitiv und locale-unabhängig.

  1: -- Standard-Inputs, die TRY_CONVERT(bit, ...) direkt versteht (Ergebnis: 0 oder 1):
  2: SELECT TRY_CONVERT(bit, N'1');       -- 1
  3: SELECT TRY_CONVERT(bit, N'0');       -- 0
  4: SELECT TRY_CONVERT(bit, N'-1');      -- 1   (jeder von 0 verschiedene Integer-String > 1)
  5: SELECT TRY_CONVERT(bit, N'true');    -- 1   (case-insensitiv)
  6: SELECT TRY_CONVERT(bit, N'false');   -- 0
  7: SELECT TRY_CONVERT(bit,  1);         -- 1   (numerischer Input)
  8: SELECT TRY_CONVERT(bit, 42);         -- 1   (jeder von 0 verschiedene Integer > 1)

Der Built-in deckt also genau zwei Eingangs-Kategorien ab: Integer-Strings/Zahlen (mit der „alles außer 0 → 1″-Regel) und die Literale 'true'/'false' (case-insensitiv). Für alles andere fällt der Aufruf auf NULL zurück:

  1: -- Non-Standard-Inputs, an denen TRY_CONVERT(bit, ...) scheitert (Ergebnis: NULL):
  2: SELECT TRY_CONVERT(bit, N'J');       -- NULL
  3: SELECT TRY_CONVERT(bit, N'YES');     -- NULL
  4: SELECT TRY_CONVERT(bit, N'ON');      -- NULL
  5: SELECT TRY_CONVERT(bit, N'x');       -- NULL
  6: SELECT TRY_CONVERT(bit, N'-');       -- NULL
  7: SELECT TRY_CONVERT(bit, N'');        -- NULL  (leere Zeichenfolge; getestet auf SQL Server 2019/2022)
  8: SELECT TRY_CONVERT(bit, NULL);       -- NULL

Bemerkenswert: TRY_CONVERT(bit, N'') liefert direkt NULL (getestet auf SQL Server 2019 und 2022). TRY_CONVERT(int, N'') konvertiert dagegen zum Wert 0 — siehe TRY_CONVERT // Konvertierung nach bigint, int, smallint, tinyint. Im bit-Pfad braucht es daher kein zusätzliches CASE/TRIM-Pattern, um die leere Zeichenfolge abzufangen. Wer auf älteren Versionen unterwegs ist, sollte das Verhalten gegen die konkrete Edition testen — String-Konvertierungen in SQL Server haben historisch versionsabhängige Eigenheiten.

Wenn die Quelle nur '0'/'1'/'true'/'false' liefert, ist TRY_CONVERT ausreichend. Für alles andere — 'J''YES''ON''x''-', … — kommt die nächste Sektion ins Spiel.

[dbo].[fn_convert_bit] — eigene Funktion für non-standard Notationen

In ETL-Pipelines tauchen aus Legacy-Quellen Yes/No-Notationen auf, die TRY_CONVERT(bit, …) nicht abbildet: deutsche 'J'/'JA'/'N'/'NEIN', englische 'Y'/'YES'/'NO', Switch-Notationen 'ON'/'OFF', plus historische Markierungen wie 'x' (gesetzt) und '-' (nicht gesetzt). Die folgende Funktion mappt diese Notationen deterministisch auf 0/1 und liefert NULL für unbekannte Werte.

Beschreibung

Konvertiert einen übergebenen Eingangswert in den Zieldatentyp bit. Kann der Eingangswert nicht zugeordnet werden, wird NULL zurückgegeben. Der übergebene Wert wird case-insensitiv behandelt (UPPER im Funktions-Body). Leerzeichen am Anfang und Ende werden ignoriert (TRIM).

Syntax

  1: [dbo].[fn_convert_bit](@p_value AS nvarchar(50))

Argumente

  • p_value — der umzuwandelnde Eingangswert. Parameter-Typ ist nvarchar(50) (statt der knapperen nvarchar(5) aus der Erstfassung) — das gibt Whitespace-Padding aus der Quelle Spielraum, bevor das TRIM greift.

Rückgabe

Gibt den umgewandelten Wert als bit zurück, wenn die Umwandlung erfolgreich ist. Kann der Eingangswert nicht zugeordnet werden, wird NULL zurückgegeben.

Unterstützte Eingangswerte

Eingangswert → 1Eingangswert → 0
JN
JANEIN
YNO
YES
TRUEFALSE
ONOFF
1-10
-

Werte sind case-insensitiv. Im Funktions-Body normalisiert UPPER den Eingangswert vor dem CASE-Vergleich. Die Funktion ist als Ausgangs-Skelett zu verstehen. Pro Datenquelle wird sie an die tatsächlich gelieferten Notationen angepasst (z. B. 'wahr'/'falsch''sí'/'no').

Code

Vollständige Funktions-Definition:

  1: CREATE FUNCTION [dbo].[fn_convert_bit] (@p_value AS nvarchar(50))
  2: RETURNS bit
  3: AS
  4: BEGIN
  5:    DECLARE @return_value AS bit;
  6: 
  7:    SET @p_value      = UPPER(TRIM(@p_value));
  8:    SET @return_value = CASE @p_value
  9:                             WHEN N'J'     THEN 1
 10:                             WHEN N'JA'    THEN 1
 11:                             WHEN N'Y'     THEN 1
 12:                             WHEN N'YES'   THEN 1
 13:                             WHEN N'N'     THEN 0
 14:                             WHEN N'NEIN'  THEN 0
 15:                             WHEN N'NO'    THEN 0
 16:                             WHEN N'TRUE'  THEN 1
 17:                             WHEN N'FALSE' THEN 0
 18:                             WHEN N'ON'    THEN 1
 19:                             WHEN N'OFF'   THEN 0
 20:                             WHEN N'-1'    THEN 1
 21:                             WHEN N'1'     THEN 1
 22:                             WHEN N'0'     THEN 0
 23:                             WHEN N'-'     THEN 0
 24:                             ELSE NULL
 25:                        END;
 26: 
 27:    RETURN @return_value;
 28: END;

Demo-Aufrufe

15 Aufrufe, mit dem jeweils erwarteten Ergebnis als Kommentar:

  1: SELECT [dbo].[fn_convert_bit](N'1');      -- 1
  2: SELECT [dbo].[fn_convert_bit](N'0');      -- 0
  3: SELECT [dbo].[fn_convert_bit](N'J');      -- 1
  4: SELECT [dbo].[fn_convert_bit](N'ja');     -- 1   (case-insensitiv über UPPER im Funktions-Body)
  5: SELECT [dbo].[fn_convert_bit](N'N');      -- 0
  6: SELECT [dbo].[fn_convert_bit](N'nein');   -- 0
  7: SELECT [dbo].[fn_convert_bit](N' ');      -- NULL (nach TRIM eine leere Zeichenfolge, nicht in CASE-Liste)
  8: SELECT [dbo].[fn_convert_bit](N'X');      -- NULL (unbekannter Eingangswert)
  9: SELECT [dbo].[fn_convert_bit](NULL);      -- NULL (NULL-Input bleibt NULL)
 10: SELECT [dbo].[fn_convert_bit](N'true');   -- 1
 11: SELECT [dbo].[fn_convert_bit](N'false');  -- 0
 12: SELECT [dbo].[fn_convert_bit](N'ON');     -- 1
 13: SELECT [dbo].[fn_convert_bit](N'OFF');    -- 0
 14: SELECT [dbo].[fn_convert_bit](N'-');      -- 0
 15: SELECT [dbo].[fn_convert_bit](N'-1');     -- 1

Postgres-Brücke

In Postgres ist die Lage komfortabler: boolean ist ein nativer Datentyp, und der Direkt-Cast s::boolean versteht ohne Zusatz-Aufwand eine umfangreichere Yes/No-Liste als SQL Servers TRY_CONVERT(bit, …).

  1: SELECT 't'::boolean;       -- true
  2: SELECT 'true'::boolean;    -- true
  3: SELECT 'y'::boolean;       -- true
  4: SELECT 'yes'::boolean;     -- true
  5: SELECT 'on'::boolean;      -- true
  6: SELECT '1'::boolean;       -- true
  7: 
  8: SELECT 'f'::boolean;       -- false
  9: SELECT 'n'::boolean;       -- false
 10: SELECT 'no'::boolean;      -- false
 11: SELECT 'off'::boolean;     -- false

Allerdings: s::boolean wirft eine Exception bei ungültigen Inputs ('J'::boolean → ERROR: invalid input syntax for type boolean: "J"). Für non-standard Notationen — analog zur SQL-Server-Custom-Funktion — braucht es einen PL/pgSQL-Wrapper, der NULL statt Exception liefert und die deutschen 'J'/'JA'/'NEIN' plus 'x'/'-' mit abdeckt:

  1: CREATE OR REPLACE FUNCTION try_cast_boolean (p_value text)
  2:    RETURNS boolean
  3:    LANGUAGE plpgsql
  4:    IMMUTABLE
  5: AS $$
  6: DECLARE
  7:    normalized text;
  8: BEGIN
  9:    IF p_value IS NULL THEN
 10:       RETURN NULL;
 11:    END IF;
 12:    normalized := UPPER(TRIM(p_value));
 13:    IF normalized = '' THEN
 14:       RETURN NULL;
 15:    END IF;
 16:    RETURN CASE normalized
 17:              WHEN 'J'     THEN true
 18:              WHEN 'JA'    THEN true
 19:              WHEN 'Y'     THEN true
 20:              WHEN 'YES'   THEN true
 21:              WHEN 'TRUE'  THEN true
 22:              WHEN 'T'     THEN true
 23:              WHEN 'ON'    THEN true
 24:              WHEN '1'     THEN true
 25:              WHEN '-1'    THEN true
 26:              WHEN 'N'     THEN false
 27:              WHEN 'NEIN'  THEN false
 28:              WHEN 'NO'    THEN false
 29:              WHEN 'FALSE' THEN false
 30:              WHEN 'F'     THEN false
 31:              WHEN 'OFF'   THEN false
 32:              WHEN '0'     THEN false
 33:              WHEN '-'     THEN false
 34:              ELSE NULL
 35:           END;
 36: END;
 37: $$;

SQL ServerPostgres-PendantBemerkung
TRY_CONVERT(bit, '1')'1'::boolean oder try_cast_boolean('1')Built-in identisch
TRY_CONVERT(bit, 'true')'true'::boolean oder try_cast_boolean('true')Built-in identisch
TRY_CONVERT(bit, 'J') → NULL'J'::boolean → Exception; try_cast_boolean('J') → trueWrapper deckt deutsche Notation
[dbo].[fn_convert_bit](N'YES')try_cast_boolean('YES')volle Parallelität

Zusammenfassung

Konvertierung nach bit ist zweistufig zu denken. Erst prüfen, ob TRY_CONVERT(bit, …) die Quelle abdeckt: der eingebaute Konverter versteht den Ziffern-Standard plus 'true'/'false', case-insensitiv und locale-unabhängig. Erst dann, wenn die Quelle Nicht-Standard-Notationen liefert ('J''YES''ON''x', …), eine eigene Funktion [dbo].[fn_convert_bit] mit CASE-Zuordnung einsetzen. Beide Wege liefern NULL für unbekannte Werte — das passt zur dreiwertigen Logik (0/1/NULL) des bit-Typs.

Take-Away:

  • TRY_CONVERT(bit, …) zuerst. Der eingebaute Konverter deckt mehr ab, als der Erst-Reflex „ich brauche eine eigene Funktion“ vermuten lässt.
  • fn_convert_bit nur für Nicht-Standard-Notationen. Pro Datenquelle wird die CASE-Liste an die tatsächlich gelieferten Werte angepasst.
  • @p_value AS nvarchar(50) als Default-Parameter-Typ. Whitespace-Padding aus der Quelle bricht nicht vor dem TRIM.
  • Postgres-Pendant über s::boolean (Built-in) und try_cast_boolean (Wrapper für Nicht-Standard-Notationen). Vorteil gegenüber SQL Server: boolean ist nativ, und der Built-in akzeptiert von Haus aus eine breitere Yes/No-Liste als TRY_CONVERT(bit, …).

FAQ

Warum gibt TRY_CONVERT(bit, 'J') NULL zurück?

Der bit-Parser kennt nur Integer-Strings ('0''1''-1', jede Zahl außer 0 → 1) und die Literale 'true'/'false' (case-insensitiv). 'J' ist keines davon, also fällt der Aufruf auf NULL zurück. Im ETL-Pfad ist das gewollt — TRY_CONVERT löst keine Exception aus, sondern signalisiert „passt nicht“ über NULL. Für Yes/No-Notationen wie 'J' braucht es eine eigene Funktion mit CASE-Mapping (siehe [dbo].[fn_convert_bit]).

Was ist der Unterschied zwischen bit und boolean?

SQL Server besitzt keinen nativen BOOLEAN-Datentyp für Spalten oder Variablen. Boolesche Ausdrücke existieren nur intern in Prädikaten und Bedingungen (IS NULLEXISTSWHERE-Klauseln). Wer Wahrheitswerte in einer Tabelle ablegen will, nutzt bit als Ersatz. Postgres dagegen hat boolean als nativen Datentyp und akzeptiert beim Cast eine breitere Yes/No-Liste ('t'/'true'/'y'/'yes'/'on'/'1' und Pendants). Praktisch: in einer Cross-Engine-ETL-Pipeline ist bit (SQL Server) ↔ boolean (Postgres) das semantische Pendant, der Wertebereich ist identisch (0/1/NULL bzw. true/false/NULL).

Wie speichere ich bit performant in einer breiten Tabelle?

SQL Server speichert mehrere bit-Spalten einer Zeile gemeinsam in einem Byte (bis zu 8 Spalten in 1 Byte). Die Gruppierung übernimmt die Storage-Engine intern — die Deklarationsreihenfolge im CREATE TABLE muss dafür nicht künstlich umgestellt werden. Bei einzelnen bit-Spalten ist der Speicher-Vorteil gegenüber tinyint in der Praxis meist gering. Der Unterschied ist primär semantisch: bit signalisiert „dreiwertige Logik mit 0/1/NULL“, tinyint signalisiert „kleine ganze Zahl 0–255″.

Was tun, wenn die Quelle gemischt 'YES''Y''1' und 'true' liefert?

Die fn_convert_bit-Funktion erweitern (alle vier Werte sind dort schon abgedeckt) — oder im SSIS-Derived-Column-Task vorverarbeiten, falls die Konvertierung in der Pipeline statt im T-SQL stattfindet. Wichtig ist nur die Erschöpfung der Liste: jede tatsächlich in der Quelle vorkommende Notation muss in der CASE-Liste stehen, sonst landen Datensätze auf NULL und der Import wirkt unvollständig. Im Zweifel: pro Datenquelle eine Inventur der distinct-Werte ziehen (SELECT DISTINCT col FROM stage o. ä.) und die Liste verbindlich festhalten.

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

Direkt: s::boolean (siehe Postgres-Brücke). Der Built-in deckt schon 't'/'true'/'y'/'yes'/'on'/'1' und Pendants ab — mehr als SQL Servers bit-Cast. Aber: er wirft Exception statt NULL bei ungültigem Input. Für äquivalente Fehlertoleranz braucht es einen try_cast_boolean-PL/pgSQL-Wrapper analog zu TRY_CONVERT // Konvertierung nach bigint, int, smallint, tinyint. Ab Postgres 18 (Ende 2025) ist try_cast eingebaut.

Verwandte Artikel