Behandeln von NULL-Werten

Ein NULL-Wert wird in einer relationalen Datenbank verwendet, wenn der Wert in einer Spalte unbekannt ist oder fehlt. Ein NULL-Wert ist weder eine leere Zeichenfolge (für Zeichen- oder datetime-Datentypen) noch der Wert 0 (für numerische Datentypen). Die ANSI-Spezifikation SQL-92 besagt, dass ein NULL-Wert für alle Datentypen gleich sein muss, damit alle NULL-Werte einheitlich behandelt werden. Der Namespace System.Data.SqlTypes bietet Semantik für NULL-Werte, indem die Schnittstelle INullable implementiert wird. Jeder der Datentypen in System.Data.SqlTypes hat seine eigene IsNull-Eigenschaft und einen Null-Wert, der einer Instanz dieses Datentyps zugewiesen werden kann.

Hinweis

Neu in .NET Framework 2.0 ist die Unterstützung für Nullwerte zulassende Wertetypen. Programmierer*innen können damit einen Werttyp so erweitern, dass dieser alle Werte des zugrunde liegenden Typs darstellen kann. Die Nullwerte zulassenden CLR-Typen stellen eine Instanz der Nullable-Struktur dar. Diese Fähigkeit ist besonders nützlich, wenn Werttypen geschachtelt oder ungeschachtelt sind, was eine verbesserte Kompatibilität mit Objekttypen ermöglicht. Nullwerte zulassende CLR-Wertetypen sind nicht zum Speichern von Datenbank-NULL-Werten gedacht, da sich ein ANSI-SQL-NULL-Wert anders als ein null-Verweis (oder Nothing in Visual Basic) verhält. Verwenden Sie zum Arbeiten mit ANSI-SQL-NULL-Werten in Datenbanken NULL-Werte des Typs System.Data.SqlTypes anstelle von Nullable. Weitere Informationen zum Verwenden von Nullwerte zulassenden CLR-Wertetypen in Visual Basic finden Sie unter Nullwerte zulassende Werttypen. Informationen zu C# finden Sie unter Nullwerte zulassende Werttypen.

NULL-Werte und dreiwertige Logik

Durch das Zulassen von NULL-Werten in Spaltendefinitionen wird in Ihre Anwendung dreiwertige Logik eingeführt. Ein Vergleich kann anhand einer von drei Bedingungen ausgewertet werden:

  • True

  • False

  • Unknown

Da NULL als unbekannt betrachtet wird, werden zwei miteinander verglichene NULL-Werte nicht als gleich angesehen. Wenn in Ausdrücken mit arithmetischen Operatoren einer der Operanden NULL ist, ist das Ergebnis ebenfalls NULL.

NULL-Werte und SqlBoolean

Beim Vergleich zwischen beliebigen System.Data.SqlTypes wird SqlBoolean zurückgegeben. Die IsNull-Funktion für jeden SqlType gibt SqlBoolean zurück und kann verwendet werden, um auf NULL-Werte zu prüfen. Die folgenden Wahrheitstabellen zeigen, wie die Operatoren AND, OR und NOT bei Vorhandensein eines NULL-Werts funktionieren. (T = TRUE, F = FALSE und U = Unbekannt oder NULL.)

Truth Table

Informationen zur ANSI_NULLS-Option

System.Data.SqlTypes bietet die gleiche Semantik wie bei aktivierter Option ANSI_NULLS in SQL Server. Alle arithmetischen Operatoren (+, -, *, /, %), bitweisen Operatoren (~, &, |) und die meisten Funktionen geben NULL zurück, wenn einer der Operanden oder eines der Argumente NULL ist, außer für die Eigenschaft IsNull.

Der ANSI SQL-92-Standard unterstützt columnName = NULL in einer WHERE-Klausel nicht. In SQL Server steuert die Option ANSI_NULLS sowohl die standardmäßige NULL-Zulässigkeit in der Datenbank als auch die Auswertung von Vergleichen mit NULL-Werten. Wenn ANSI_NULLS auf ON festgelegt ist (Standardeinstellung), muss in Ausdrücken bei Prüfen auf NULL-Werte der Operator IS NULL verwendet werden. Der folgende Vergleich gibt z. B. immer UNKNOWN zurück, wenn ANSI_NULLS auf ON festgelegt ist:

colname > NULL  

Der Vergleich mit einer Variablen, die einen NULL-Wert enthält, ergibt ebenfalls „Unbekannt“:

colname > @MyVariable  

Verwenden Sie die Prädikate IS NULL oder IS NOT NULL, um zu überprüfen, ob ein NULL-Wert vorliegt. Dadurch nimmt die Komplexität der WHERE-Klausel möglicherweise zu. Beispielsweise lässt die Spalte TerritoryID in der AdventureWorks-Tabelle „Customer“ NULL-Werte zu. Soll eine SELECT-Anweisung u. a. das Vorhandensein von NULL-Werten überprüfen, muss ein IS NULL-Prädikat eingefügt werden:

SELECT CustomerID, AccountNumber, TerritoryID  
FROM AdventureWorks.Sales.Customer  
WHERE TerritoryID IN (1, 2, 3)  
   OR TerritoryID IS NULL  

Wenn Sie ANSI_NULLS in SQL Server auf OFF festlegen, können Sie Ausdrücke erstellen, die den Gleichheitsoperator zum Vergleich mit NULL verwenden. Sie können jedoch nicht verhindern, dass verschiedene Verbindungen NULL-Optionen für diese Verbindung festlegen. Die Verwendung von IS NULL zum Testen auf NULL-Werte funktioniert immer, und zwar unabhängig von den ANSI_NULLS-Einstellungen für eine Verbindung.

Das Deaktivieren von ANSI_NULLS wird in einem DataSet nicht unterstützt, das immer dem ANSI-Standard SQL-92 für die Behandlung von NULL-Werten in System.Data.SqlTypes folgt.

Zuweisen von NULL-Werten

NULL-Werte sind etwas Besonderes, und ihre Speicher- und Zuordnungssemantik unterscheidet sich in verschiedenen Typen- und Speichersystemen. Ein Dataset ist für die Verwendung mit verschiedenen Typ- und Speichersystemen konzipiert.

In diesem Abschnitt wird die NULL-Semantik für das Zuweisen von NULL-Werten zu einer DataColumn in einer DataRow in den verschiedenen Typensystemen beschrieben.

DBNull.Value
Diese Zuweisung ist gültig für eine DataColumn eines beliebigen Typs. Wenn der Typ INullable implementiert, wird DBNull.Value in den entsprechenden stark typisierten NULL-Wert gezwungen.

SqlType.Null
Alle System.Data.SqlTypes-Datentypen implementieren INullable. Wenn der stark typisierte NULL-Wert mithilfe impliziter Umwandlungsoperatoren in den Datentyp der Spalte konvertiert werden kann, sollte die Zuweisung durchgeführt werden. Andernfalls wird eine Ausnahme aufgrund ungültiger Umwandlung ausgelöst.

null
Wenn 'null' ein zulässiger Wert für den angegebenen Datentyp DataColumn ist, wird er in den entsprechenden DbNull.Value oder Null gezwungen, der dem INullable-Typ (SqlType.Null) zugeordnet ist.

derivedUdt.Null
Bei UDT-Spalten werden NULL-Werte immer basierend auf dem Typ gespeichert, der DataColumn zugeordnet ist. Betrachten Sie den Fall einer UDT, die einer DataColumn zugeordnet ist, die nicht INullable implementiert, während ihre Unterklasse dies tut. Wenn in diesem Fall ein stark typisierter NULL-Wert der abgeleiteten Klasse zugewiesen ist, wird er als nicht typisierter DbNull.Value gespeichert, da die NULL-Speicherung immer im Einklang mit dem Datentyp von DataColumn ist.

Hinweis

Die Struktur von Nullable<T> oder Nullable wird in DataSet derzeit nicht unterstützt.

Der Standardwert für jede System.Data.SqlTypes-Instanz ist NULL.

NULL-Werte in System.Data.SqlTypes sind typspezifisch und können nicht durch einen einzelnen Wert, wie z. B. DbNull, dargestellt werden. Verwenden Sie die IsNull-Eigenschaft, um auf NULL-Werte zu prüfen.

NULL-Werte können einem DataColumn zugewiesen werden, wie im folgenden Codebeispiel gezeigt. Sie können SqlTypes-Variablen direkt NULL-Werte zuweisen, ohne eine Ausnahme auszulösen.

Beispiel

Das folgende Codebeispiel erstellt eine DataTable mit zwei Spalten, die als SqlInt32 und SqlString definiert sind. Der Code fügt eine Zeile mit bekannten Werten und eine Zeile mit NULL-Werten hinzu und iteriert dann durch die DataTable. Dabei werden die Werte den Variablen zugewiesen, und die Ausgabe wird im Konsolenfenster angezeigt.

static void WorkWithSqlNulls()
{
    DataTable table = new();

    // Specify the SqlType for each column.
    DataColumn idColumn =
        table.Columns.Add("ID", typeof(SqlInt32));
    DataColumn descColumn =
        table.Columns.Add("Description", typeof(SqlString));

    // Add some data.
    DataRow nRow = table.NewRow();
    nRow["ID"] = 123;
    nRow["Description"] = "Side Mirror";
    table.Rows.Add(nRow);

    // Add null values.
    nRow = table.NewRow();
    nRow["ID"] = SqlInt32.Null;
    nRow["Description"] = SqlString.Null;
    table.Rows.Add(nRow);

    // Initialize variables to use when
    // extracting the data.
    SqlBoolean isColumnNull = false;
    SqlInt32 idValue = SqlInt32.Zero;
    SqlString descriptionValue = SqlString.Null;

    // Iterate through the DataTable and display the values.
    foreach (DataRow row in table.Rows)
    {
        // Assign values to variables. Note that you
        // do not have to test for null values.
        idValue = (SqlInt32)row["ID"];
        descriptionValue = (SqlString)row["Description"];

        // Test for null value in ID column.
        isColumnNull = idValue.IsNull;

        // Display variable values in console window.
        Console.Write("isColumnNull={0}, ID={1}, Description={2}",
            isColumnNull, idValue, descriptionValue);
        Console.WriteLine();
    }
Private Sub WorkWithSqlNulls()
    Dim table As New DataTable()

    ' Specify the SqlType for each column.
    Dim idColumn As DataColumn = _
      table.Columns.Add("ID", GetType(SqlInt32))
    Dim descColumn As DataColumn = _
      table.Columns.Add("Description", GetType(SqlString))

    ' Add some data.
    Dim row As DataRow = table.NewRow()
    row("ID") = 123
    row("Description") = "Side Mirror"
    table.Rows.Add(row)

    ' Add null values.
    row = table.NewRow()
    row("ID") = SqlInt32.Null
    row("Description") = SqlString.Null
    table.Rows.Add(row)

    ' Initialize variables to use when
    ' extracting the data.
    Dim isColumnNull As SqlBoolean = False
    Dim idValue As SqlInt32 = SqlInt32.Zero
    Dim descriptionValue As SqlString = SqlString.Null

    ' Iterate through the DataTable and display the values.
    For Each row In table.Rows
        ' Assign values to variables. Note that you 
        ' do not have to test for null values.
        idValue = CType(row("ID"), SqlInt32)
        descriptionValue = CType(row("Description"), SqlString)

        ' Test for null value with ID column
        isColumnNull = idValue.IsNull

        ' Display variable values in console window.
        Console.Write("isColumnNull={0}, ID={1}, Description={2}", _
          isColumnNull, idValue, descriptionValue)
        Console.WriteLine()
    Next row
End Sub

In diesem Beispiel werden die folgenden Ergebnisse gezeigt:

isColumnNull=False, ID=123, Description=Side Mirror  
isColumnNull=True, ID=Null, Description=Null  

Zuweisung mehrerer Spalten (Zeilen)

DataTable.Add, DataTable.LoadDataRow oder andere APIs, die ein ItemArray akzeptieren, das einer Zeile zugeordnet wird, ordnen 'null' dem Standardwert von DataColumn zu. Wenn ein Objekt im Array DbNull.Value oder sein stark typisiertes Pendant enthält, gelten die gleichen Regeln wie oben beschrieben.

Außerdem gelten die folgenden Regeln für eine Instanz von NULL-Zuweisungen für DataRow.["columnName"]:

  1. Für alle Spalten (mit Ausnahme der stark typisierten NULL-Spalten) lautet der StandardwertDbNull.Value. In den stark typisierten NULL-Spalten ist es der entsprechende stark typisierte NULL-Wert.

  2. NULL-Werte werden niemals bei der Serialisierung in XML-Dateien geschrieben (wie in „xsi: nil“).

  3. Alle Nicht-NULL-Werte, einschließlich der Standardwerte, werden bei der Serialisierung in XML immer geschrieben. Dies steht im Gegensatz zur XSD/XML-Semantik, bei der ein NULL-Wert (xsi:nil) explizit und der Standardwert implizit ist (wenn er in XML nicht vorhanden ist, kann ein validierender Parser ihn aus einem zugehörigen XSD-Schema abrufen). Das Gegenteil gilt für DataTable: Ein NULL-Wert ist implizit und der Standardwert ist explizit.

  4. Allen fehlenden Spaltenwerten für aus der XML-Eingabe gelesene Zeilen wird NULL zugewiesen. Zeilen, die mit NewRow oder ähnlichen Methoden erstellt werden, wird der Standardwert von DataColumn zugewiesen.

  5. Die IsNull-Methode gibt true sowohl für DbNull.Value als auch für INullable.Null zurück.

Vergleichen von NULL-Werten mit "SqlTypes" und CLR-Typen

Beim Vergleich von NULL-Werten ist es wichtig, den Unterschied zwischen der Art und Weise zu verstehen, wie die Equals-Methode NULL-Werte in System.Data.SqlTypes auswertet, im Vergleich dazu, wie sie mit CLR-Typen arbeitet. Alle System.Data.SqlTypesEquals-Methoden verwenden Datenbanksemantik zur Auswertung von NULL-Werten. Wenn einer oder beide Werte NULL sind, ergibt der Vergleich NULL. Andererseits ergibt das Anwenden der CLR-Methode Equals auf zwei System.Data.SqlTypes TRUE, wenn beide NULL sind. Dies spiegelt den Unterschied zwischen der Verwendung einer Instanzmethode wie der CLR-Methode String.Equals und der Verwendung der statischen/gemeinsamen Methode SqlString.Equals wider.

Das folgende Beispiel veranschaulicht den Unterschied in den Ergebnissen zwischen der SqlString.Equals-Methode und der String.Equals-Methode, wenn jeweils ein Paar NULL-Werte und dann ein Paar leere Zeichenfolgen übergeben werden.

    static void CompareNulls()
    {
        // Create two new null strings.
        SqlString a = new();
        SqlString b = new();

        // Compare nulls using static/shared SqlString.Equals.
        Console.WriteLine("SqlString.Equals shared/static method:");
        Console.WriteLine("  Two nulls={0}", SqlStringEquals(a, b));

        // Compare nulls using instance method String.Equals.
        Console.WriteLine();
        Console.WriteLine("String.Equals instance method:");
        Console.WriteLine("  Two nulls={0}", StringEquals(a, b));

        // Make them empty strings.
        a = "";
        b = "";

        // When comparing two empty strings (""), both the shared/static and
        // the instance Equals methods evaluate to true.
        Console.WriteLine();
        Console.WriteLine("SqlString.Equals shared/static method:");
        Console.WriteLine("  Two empty strings={0}", SqlStringEquals(a, b));

        Console.WriteLine();
        Console.WriteLine("String.Equals instance method:");
        Console.WriteLine("  Two empty strings={0}", StringEquals(a, b));
    }

    static string SqlStringEquals(SqlString string1, SqlString string2)
    {
        // SqlString.Equals uses database semantics for evaluating nulls.
        var returnValue = SqlString.Equals(string1, string2).ToString();
        return returnValue;
    }

    static string StringEquals(SqlString string1, SqlString string2)
    {
        // String.Equals uses CLR type semantics for evaluating nulls.
        var returnValue = string1.Equals(string2).ToString();
        return returnValue;
    }
}
Private Sub CompareNulls()
    ' Create two new null strings.
    Dim a As New SqlString
    Dim b As New SqlString

    ' Compare nulls using static/shared SqlString.Equals.
    Console.WriteLine("SqlString.Equals shared/static method:")
    Console.WriteLine("  Two nulls={0}", SqlStringEquals(a, b))

    ' Compare nulls using instance method String.Equals.
    Console.WriteLine()
    Console.WriteLine("String.Equals instance method:")
    Console.WriteLine("  Two nulls={0}", StringEquals(a, b))

    ' Make them empty strings.
    a = ""
    b = ""

    ' When comparing two empty strings (""), both the shared/static and
    ' the instance Equals methods evaluate to true.
    Console.WriteLine()
    Console.WriteLine("SqlString.Equals shared/static method:")
    Console.WriteLine("  Two empty strings={0}", SqlStringEquals(a, b))

    Console.WriteLine()
    Console.WriteLine("String.Equals instance method:")
    Console.WriteLine("  Two empty strings={0}", StringEquals(a, b))
End Sub

Private Function SqlStringEquals(ByVal string1 As SqlString, _
    ByVal string2 As SqlString) As String

    ' SqlString.Equals uses database semantics for evaluating nulls.
    Dim returnValue As String = SqlString.Equals(string1, string2).ToString()
    Return returnValue
End Function

Private Function StringEquals(ByVal string1 As SqlString, _
    ByVal string2 As SqlString) As String

    ' String.Equals uses CLR type semantics for evaluating nulls.
    Dim returnValue As String = string1.Equals(string2).ToString()
    Return returnValue
End Function

Der Code erzeugt folgende Ausgabe:

SqlString.Equals shared/static method:  
  Two nulls=Null  
  
String.Equals instance method:  
  Two nulls=True  
  
SqlString.Equals shared/static method:  
  Two empty strings=True  
  
String.Equals instance method:  
  Two empty strings=True

Weitere Informationen