Поделиться через


Обработка значений NULL

Значение NULL в реляционной базе данных используется, если значение в столбце неизвестно или отсутствует. Значение NULL не является пустой строкой (для типов данных символа или даты и времени) или нулевого значения (для числовых типов данных). Спецификация ANSI SQL-92 указывает, что значение NULL должно быть одинаковым для всех типов данных, чтобы все значения NULL обрабатывались согласованно. Пространство имен System.Data.SqlTypes предоставляет нулевую семантику, реализуя интерфейс INullable. Каждый из типов данных в System.Data.SqlTypes имеет собственное IsNull свойство и Null значение, которое можно назначить экземпляру данного типа данных.

Замечание

Платформа .NET Framework версии 2.0 представила поддержку типов значений, допускающих значение NULL, что позволяет программистам расширить тип значения для представления всех значений базового типа. Типы значений CLR, допускающие значение NULL, представляют экземпляр структуры Nullable. Эта возможность особенно полезна, если типы значений упаковываются и распаковываются, обеспечивая улучшенную совместимость с типами объектов. nullable типы значений CLR не предназначены для хранения значений NULL базы данных, так как NULL ANSI SQL не ведет себя так же, как null ссылка (или Nothing в Visual Basic). Для работы со значениями null в базе данных ANSI SQL используйте System.Data.SqlTypes вместо Nullable. Дополнительные сведения о работе с CLR-значениями, допускающими значение NULL, в Visual Basic см. в разделе Типы значений, допускающие значение NULL, а для C# см. Типы значений, допускающие значение NULL.

Нули и Three-Valued логика

Разрешение значений NULL в определениях столбцов вводит в приложение трехзначную логику. Сравнение может оценить одно из трех условий:

  • Верно

  • Неправда

  • Неизвестно

Поскольку значение NULL считается неизвестным, два значения NULL по сравнению с другими не считаются равными. В выражениях с использованием арифметических операторов, если любой из операндов имеет значение NULL, результат также имеет значение NULL.

Пустые значения и SqlBoolean

Сравнение любого System.Data.SqlTypes вернет SqlBoolean. Функция IsNull для каждого SqlType возвращает значение SqlBoolean и может использоваться для проверки значений NULL. В следующих таблицах истины показано, как операторы AND, OR и NOT работают в присутствии значения NULL. (T=true, F=false и U=неизвестно, или нулевое.)

Таблица истины

Общие сведения о параметре ANSI_NULLS

System.Data.SqlTypes предоставляет ту же семантику, что и при установке параметра ANSI_NULLS в SQL Server. Все арифметические операторы (+, -, *, /, %), побитовые операторы (~, &, |) и большинство функций возвращают значение NULL, если любой из операндов или аргументов имеет значение NULL, за исключением свойства IsNull.

Стандарт ANSI SQL-92 не поддерживает columnName = NULL в предложении WHERE. В SQL Server параметр ANSI_NULLS управляет значением NULL по умолчанию в базе данных и оценке сравнений со значениями NULL. Если ANSI_NULLS включен (по умолчанию), оператор IS NULL должен использоваться в выражениях при тестировании значений NULL. Например, следующее сравнение всегда приводит к результату "неизвестно", когда ANSI_NULLS включен:

colname > NULL  

Сравнение с переменной, содержащей значение NULL, также дает неизвестное значение:

colname > @MyVariable  

Используйте предикат IS NULL или IS NOT NULL, чтобы проверить значение NULL. Это может добавить сложность в предложение WHERE. Например, столбец TerritoryID в таблице AdventureWorks Customer допускает значения NULL. Если оператор SELECT должен проверять значения NULL, а также другие значения, он должен включать предикат IS NULL.

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

Если отключить ANSI_NULLS в SQL Server, можно создать выражения, использующие оператор равенства для сравнения с null. Однако не удается запретить другим подключениям задавать параметры NULL для этого подключения. Использование IS NULL для проверки значений NULL всегда работает независимо от ANSI_NULLS параметров подключения.

Если отключить ANSI_NULLS, это не поддерживается в DataSet, который всегда следует стандарту ANSI SQL-92 для обработки значений NULL в System.Data.SqlTypes.

Назначение значений NULL

Значения NULL являются особыми, а их семантика хранения и назначения отличаются в разных системах типов и системах хранения. A Dataset предназначен для использования с различными типами и системами хранения.

В этом разделе описывается семантика присвоения нулевых значений элементам DataColumn в различных системах типов DataRow.

DBNull.Value
Это назначение допустимо для DataColumn любого типа. Если тип реализует INullable, DBNull.Value приводится в строго типизированное значение null.

SqlType.Null
Все System.Data.SqlTypes типы данных реализуют INullable. Если строго типизированное значение NULL можно преобразовать в тип данных столбца с помощью неявных операторов приведения, присвоение должно выполниться. В противном случае создается недопустимое исключение приведения.

null
Если значение "null" является законным для данного DataColumn типа данных, оно приводится к соответствующему DbNull.Value или Null, связанному с типом INullable (SqlType.Null)

derivedUdt.Null
Для столбцов определяемых пользователем типов, NULL-значения всегда хранятся в зависимости от типа, связанного с ним DataColumn. Рассмотрим случай пользовательского типа (UDT), связанного с DataColumn, который не реализует INullable, в то время как его подкласс это делает. В этом случае, если присваивается строго типизированное значение NULL, связанное с производным классом, оно хранится как нетипизированное DbNull.Value, так как хранилище NULL всегда соответствует типу данных DataColumn.

Замечание

В настоящее время структура Nullable<T> или Nullable не поддерживается в DataSet.

Значение по умолчанию для любого System.Data.SqlTypes экземпляра равно NULL.

NULL в System.Data.SqlTypes являются специфичными для типа и не могут быть представлены одним значением, таким как DbNull. Используйте свойство IsNull для проверки нулевых значений.

Нулевые значения можно присвоить DataColumn, как показано в следующем примере кода. Можно напрямую назначать значения SqlTypes NULL переменным без активации исключения.

Пример

В следующем примере кода создается DataTable, в котором имеются два столбца, определенные как SqlInt32 и SqlString. Код добавляет одну строку известных значений, одну строку значений NULL, а затем выполняет итерацию, DataTableприсваивая значения переменным и отображая выходные данные в окне консоли.

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

В этом примере отображаются следующие результаты:

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

Назначение нескольких столбцов или строк

DataTable.Add, DataTable.LoadDataRow или другие API, которые принимают значение ItemArray, которое сопоставляется со строкой, сопоставляют 'null' со значением по умолчанию DataColumn. Если объект в массиве содержит DbNull.Value или его строго типизированный аналог, применяются те же правила, что и описано выше.

Кроме того, для экземпляра назначений DataRow.["columnName"] NULL применяются следующие правила:

  1. Значение по умолчанию применяется ко всем, кроме строго типизированных столбцов NULL, где используется соответствующее строго типизированное значение NULL.

  2. Значения NULL никогда не записываются во время сериализации в XML-файлы (как в xsi:nil).

  3. Все значения, отличные от NULL, включая значения по умолчанию, всегда записываются при сериализации в XML. Это в отличие от семантики XSD/XML, где значение NULL (xsi:nil) явно, и значение по умолчанию неявно (если оно отсутствует в XML, проверяющий средство синтаксического анализа может получить его из связанной схемы XSD). Верно обратное для DataTable: значение NULL неявно, а значение по умолчанию явно.

  4. Все отсутствующие значения столбцов для строк, считываемых из входных данных XML, назначаются NULL. Строкам, созданным с помощью NewRow или с помощью аналогичных методов, присваивается значение по умолчанию столбца данных.

  5. Метод IsNull возвращает true как для DbNull.Value, так и для INullable.Null.

Сравнение значений NULL с типами SQLTypes и CLR

При сравнении значений NULL важно понимать разницу между тем, как метод Equals оценивает значения NULL в System.Data.SqlTypes и тем, как он работает с типами CLR. System.Data.SqlTypes Equals Все методы используют семантику базы данных для оценки значений NULL: если одно или оба значения равно NULL, сравнение дает значение NULL. С другой стороны, использование метода CLR Equals в двух System.Data.SqlTypes случаях будет иметь значение true, если оба значения имеют значение NULL. Это отражает разницу между использованием метода экземпляра, такого как метод CLR String.Equals , и использованием статического или общего метода SqlString.Equals.

В следующем примере показано различие в результатах между SqlString.Equals методом и String.Equals методом при передаче пары значений NULL, а затем пары пустых строк.

    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={SqlStringEquals(a, b)}");

        // Compare nulls using instance method String.Equals.
        Console.WriteLine();
        Console.WriteLine("String.Equals instance method:");
        Console.WriteLine($"  Two nulls={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={SqlStringEquals(a, b)}");

        Console.WriteLine();
        Console.WriteLine("String.Equals instance method:");
        Console.WriteLine($"  Two empty strings={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

Код создает следующие выходные данные:

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

См. также