NULL-значения в SQL
Достаточно часто встречаются такие случаи, когда в таблице имеются записи с не заданными значениями какого-либо из полей, потому что значение поля неизвестно или его просто нет. В таких случаях SQL позволяет указать в поле NULL-значение. Строго говоря, NULL-значение вовсе не представлено в поле. Когда значение поля есть NULL — это значит, что программа базы данных специальным образом помечает поле, как не содержащее какого-либо значения для данной строки (записи).
Дело обстоит не так в случае простого приписывания полю значения «нуль» или «пробел», которые база данных трактует как любое другое значение. Поскольку NULL не является значением как таковым, он не имеет типа данных. NULL может размещаться в поле любого типа. Тем не менее, NULL, как NULL-значение, часто используется в SQL.
Предположим, появился покупатель, которому еще не назначен продавец. Чтобы констатировать этот факт, нужно ввести значение NULL в поле snum, а реальное значение включить туда позже, когда данному покупателю будет назначен продавец.
SQL IS NULL
Поскольку NULL фиксирует пропущенные значения, результат любого сравнения при наличии NULL-значений неизвестен. Когда NULL-значение сравнивается с любым значением, даже с NULL-значением, результат просто неизвестен. Булево значение «неизвестно» ведет себя также, как «ложь» — строка, на которой предикат принимает значение «неизвестно», не включается в результат запроса – при одном важном исключении: NOT от лжи есть истина (NOT (false)=true), тогда как NOT от неизвестного значения есть также неизвестное значение. Следовательно, такое выражение как «city = NULL» или «city IN (NULL)» является неизвестным независимо от значения city.
Часто необходимо различать false и unknown – строки, содержащие значения столбца, не удовлетворяющие предикату, и строки, которые содержат NULL. Для этой цели SQL располагает специальным оператором IS, который используется с ключевым словом NULL для локализации NULL-значения. SQL IS NULL. Пример. Вывести все поля из талицы Customers, значения поля city которых равны NULL:
SELECT * FROM Customers WHERE city IS NULL
В данном случае выходных данных не будет, поскольку в поле city нет NULL-значений.
SQL IS NOT NULL
Условие IS NOT NULL используется в запросах для выборки записей со значениями не равных значению NULL SQL IS NOT NULL. Пример. Вывести все поля из талицы Customers, значения поля city которых НЕ равны NULL:
Обработка значений NULL
Значение NULL в реляционной базе данных используется, если значение в столбце неизвестно или отсутствует. NULL не является ни пустой строкой (для типов данных character или datetime), ни нулевым значением (для числовых типов данных). В спецификации ANSI SQL-92 указано, что значение NULL должно быть одинаковым для всех типов данных, чтобы все значения NULL обрабатывались согласованно. Пространство имен System.Data.SqlTypes обеспечивает семантику со значением NULL, реализуя интерфейс INullable. Каждый из типов данных в System.Data.SqlTypes имеет собственное свойство IsNull и значение Null , которое может быть назначено экземпляру этого типа данных.
В версиях .NET Framework 2.0 и .NET Core 1.0 появилась поддержка типов, допускающих значение NULL, что позволяет программистам расширять тип значения для представления всех значений базового типа. Эти типы CLR, допускающие значение NULL, представляют экземпляр структуры Nullable. Эта возможность особенно полезна, если типы значений упакованы и распакованы, что обеспечивает улучшенную совместимость с типами объектов. Типы CLR, допускающие значение NULL, не предназначены для хранения значений NULL базы данных, так как значение NULL ANSI SQL не работает так же, как ссылка на null (или Nothing в Visual Basic). Для работы со значениями NULL в базе данных ANSI SQL используйте значения NULL System.Data.SqlTypes вместо Nullable. Дополнительные сведения о работе на C# с типами CLR, допускающими значения NULL, см. в этой статье.
Значения NULL и логика трех значений
Разрешение значений NULL в определениях столбцов вводит в приложение логику трех значений. Результатом сравнения может быть одно из трех условий:
Так как значение NULL считается неизвестным, два значения NULL, сравниваемые друг с другом, не считаются равными. В выражениях, использующих арифметические операторы, если какой-либо из операндов имеет значение NULL, результат также равен NULL.
Значения NULL и SqlBoolean
При сравнении между любыми типами System.Data.SqlTypes будет возвращаться значение SqlBoolean. Функция IsNull для каждого типа SqlType возвращает SqlBoolean и может использоваться для проверки на наличие значений NULL. В следующих таблицах истинности показано, как работают операторы AND, OR и NOT при наличии значения NULL. (T = true, F = false и U = неизвестно или NULL.)
Основные сведения о параметре 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 включен (по умолчанию), то при проверке на наличие значений NULL в выражениях должен использоваться оператор IS NULL. Например, результатом следующего сравнения всегда является неизвестность при включенном параметре ANSI_NULLS:
colname > NULL
Сравнение с переменной, содержащей значение NULL, также приводит к неизвестному результату:
colname > @MyVariable
Для тестирования на значение NULL используются предикаты IS NULL и IS NOT 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
Если в SQL Server параметр ANSI_NULLS отключен, можно создать выражения, которые используют оператор равенства для сравнения со значением NULL. Однако нельзя запретить другим подключениям задавать параметры NULL для этого подключения. Использование параметра IS NULL для проверки на наличие значений NULL всегда работает, независимо от установленного значения ANSI_NULLS для подключения.
Установка ANSI_NULLS OFF не поддерживается в DataSet , который всегда соответствует стандарту ANSI SQL-92 для обработки значений NULL в System.Data.SqlTypes.
Присвоение значений NULL
Значения NULL являются специальными, и их семантика хранения и назначения различается в разных системах типов и системах хранения. Dataset предназначен для использования с различными системами типов и хранения.
В этом разделе описывается семантика значений NULL для присвоения значений NULL для 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 . Рассмотрим случай пользовательского типа, связанного с DataColumn , который не реализует INullable в отличие от своего подкласса. В этом случае, если назначено строго типизированное значение NULL, связанное с производным классом, оно сохраняется как нетипизированное значение DbNull.Value , так как хранилище значений NULL всегда согласуется с типом данных DataColumn.
В настоящее время структура Nullable или Nullable не поддерживается в DataSet .
Назначение нескольких столбцов (строк)
DataTable.Add , DataTable.LoadDataRow или другие API-интерфейсы, принимающие массив ItemArray, который сопоставляется со строкой, сопоставляют значение NULL со значением по умолчанию DataColumn. Если объект в массиве содержит DbNull.Value или строго типизированный аналог, применяются те же правила, которые описаны выше.
Кроме того, следующие правила применяются к экземпляру назначений NULL DataRow.[«columnName»] :
- Используемое по умолчанию значение default является DbNull.Value для всех столбцов, за исключением строго типизированных нулевых столбцов с допустимыми строго типизированными значениями NULL.
- Значения NULL никогда не записываются во время сериализации в XML-файлы (как в xsi:nil).
- Все значения, в том числе по умолчанию, отличные от NULL, всегда записываются при сериализации в XML. Это отличается от семантики XSD/XML, где значение NULL (xsi: nil) является явным, а значение по умолчанию — неявным (если отсутствует в XML, то проверяющее средство синтаксического анализа может получить его из связанной схемы XSD). Обратное верно для DataTable : значение NULL является неявным, а значение по умолчанию — явным.
- Всем отсутствующим значениям столбцов для строк, считываемых из входных данных XML, присваивается значение NULL. Строкам, созданным с помощью NewRow или аналогичных методов, присваивается значение по умолчанию DataColumn.
- Метод IsNull возвращает true как для DbNull.Value , так и для INullable.Null .
Присвоение значений NULL для SqlTypes
Значение по умолчанию для любого экземпляра System.Data.SqlTypes— NULL.
Значения NULL в System.Data.SqlTypes относятся к определенному типу и не могут быть представлены одним значением, таким как DbNull . Чтобы проверить на наличие значений NULL, используйте свойство IsNull .
Значения NULL могут быть назначены DataColumn, как показано в следующем примере кода. Вы можете напрямую назначить значения NULL для переменных SqlTypes без запуска исключения.
Пример
В следующем примере кода показано создание DataTable с двумя столбцами, определенными как SqlInt32 и SqlString. Код добавляет одну строку известных значений, одну строку значений NULL, а затем выполняет итерацию по DataTable, присваивая значения переменным и отображая выходные данные в окне консоли.
using Microsoft.Data.SqlClient; using System.Data.SqlTypes; class Program < static void Main() < WorkWithSqlNulls(); Console.ReadLine(); >static private void WorkWithSqlNulls() < DataTable table = new DataTable(); // 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=, Description=", isColumnNull, idValue, descriptionValue); Console.WriteLine(); > > >
В этом примере отображаются следующие результаты:
isColumnNull=False, Description=Side Mirror isColumnNull=True, Description=Null
Сравнение значений NULL с SqlTypes и типами CLR
При сравнении значений NULL важно понимать разницу между тем, как метод Equals вычисляет значения NULL в System.Data.SqlTypes по сравнению с тем, как он работает с типами CLR. Все методы System.Data.SqlTypes Equals используют семантику базы данных для вычисления значений NULL. Если одно или оба значения являются NULL, результатом сравнения будет NULL. С другой стороны, при использовании метода Equals CLR для двух System.Data.SqlTypes вернется значение true, если оба значения соответствуют NULL. Это отражает разницу между использованием метода экземпляра, такого как метод String.Equals CLR, и использованием статического или общего метода SqlString.Equals .
В следующем примере показана разница результатов между методами SqlString.Equals и String.Equals , если каждому из них передается пара значений NULL, а затем пара пустых строк.
using System.Data.SqlTypes; namespace SqlNullsCS < class Program < static void Main() < CompareNulls(); Console.ReadLine(); >private static void CompareNulls() < // Create two new null strings. SqlString a = new SqlString(); SqlString b = new SqlString(); // 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)); > private static string SqlStringEquals(SqlString string1, SqlString string2) < // SqlString.Equals uses database semantics for evaluating nulls. string returnValue = SqlString.Equals(string1, string2).ToString(); return returnValue; >private static string StringEquals(SqlString string1, SqlString string2) < // String.Equals uses CLR type semantics for evaluating nulls. string returnValue = string1.Equals(string2).ToString(); return returnValue; >> >
Получается следующий вывод:
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
Следующие шаги
Обработка значений NULL
Значение NULL в реляционной базе данных используется, если значение в столбце неизвестно или отсутствует. NULL не является ни пустой строкой (для типов данных character или datetime), ни нулевым значением (для числовых типов данных). В спецификации ANSI SQL-92 указано, что значение NULL должно быть одинаковым для всех типов данных, чтобы все значения NULL обрабатывались согласованно. Пространство имен System.Data.SqlTypes обеспечивает семантику со значением NULL, реализуя интерфейс INullable. Каждый из типов данных в System.Data.SqlTypes имеет собственное свойство IsNull и значение Null , которое может быть назначено экземпляру этого типа данных.
В платформа .NET Framework версии 2.0 появилась поддержка типов значений, допускающих значение NULL, что позволяет программистам расширить тип значения для представления всех значений базового типа. Эти типы значений Nullable CLR, допускающие значение NULL, представляют экземпляр структуры. Эта возможность особенно полезна, если типы значений упакованы и распакованы, что обеспечивает улучшенную совместимость с типами объектов. Типы значений, допускающие значение CLR, не предназначены для хранения значений NULL базы данных, так как значение NULL ANSI SQL не ведет себя так же, как null ссылка (или Nothing в Visual Basic). Для работы со значениями NULL в базе данных ANSI SQL используйте значения NULL System.Data.SqlTypes вместо Nullable. Дополнительные сведения о работе со значениями CLR, допускающих значение NULL, в Visual Basic см. в разделе «Типы значений, допускающие значение NULL», и для C# см . типы значений, допускающие значение NULL.
Значения NULL и тройственная логика
Разрешение значений NULL в определениях столбцов вводит в приложение логику трех значений. Результатом сравнения может быть одно из трех условий:
Так как значение NULL считается неизвестным, два значения NULL, сравниваемые друг с другом, не считаются равными. В выражениях, использующих арифметические операторы, если какой-либо из операндов имеет значение NULL, результат также равен NULL.
Значения NULL и SqlBoolean
При сравнении между любыми типами System.Data.SqlTypes будет возвращаться значение SqlBoolean. Функция IsNull для каждого типа SqlType возвращает SqlBoolean и может использоваться для проверки на наличие значений NULL. В следующих таблицах истинности показано, как работают операторы AND, OR и NOT при наличии значения NULL. (T = true, F = false и U = неизвестно или NULL.)
Основные сведения о параметре 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 включен (по умолчанию), то при проверке на наличие значений NULL в выражениях должен использоваться оператор IS NULL. Например, результатом следующего сравнения всегда является неизвестность при включенном параметре ANSI_NULLS:
colname > NULL
Сравнение с переменной, содержащей значение NULL, также приводит к неизвестному результату:
colname > @MyVariable
Для тестирования на значение NULL используются предикаты IS NULL и IS NOT 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
Если в SQL Server параметр ANSI_NULLS отключен, можно создать выражения, которые используют оператор равенства для сравнения со значением NULL. Однако нельзя запретить другим подключениям задавать параметры NULL для этого подключения. Использование параметра IS NULL для проверки на наличие значений NULL всегда работает, независимо от установленного значения ANSI_NULLS для подключения.
Установка ANSI_NULLS OFF не поддерживается в DataSet , который всегда соответствует стандарту ANSI SQL-92 для обработки значений NULL в System.Data.SqlTypes.
Присвоение значений Null
Значения NULL являются специальными, и их семантика хранения и назначения различается в разных системах типов и системах хранения. Dataset предназначен для использования с различными системами типов и хранения.
В этом разделе описывается семантика значений NULL для присвоения значений NULL для 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 . Рассмотрим случай пользовательского типа, связанного с DataColumn , который не реализует INullable в отличие от своего подкласса. В этом случае, если назначено строго типизированное значение NULL, связанное с производным классом, оно сохраняется как нетипизированное значение DbNull.Value , так как хранилище значений NULL всегда согласуется с типом данных DataColumn.
В настоящее время структура Nullable или Nullable не поддерживается в DataSet .
Значение по умолчанию для любого экземпляра System.Data.SqlTypes— NULL.
Значения NULL в System.Data.SqlTypes относятся к определенному типу и не могут быть представлены одним значением, таким как DbNull . Чтобы проверить на наличие значений NULL, используйте свойство IsNull .
Значения NULL могут быть назначены DataColumn, как показано в следующем примере кода. Вы можете напрямую назначить значения NULL для переменных SqlTypes без запуска исключения.
Пример
В следующем примере кода показано создание 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=, Description=", 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=, Description=", _ isColumnNull, idValue, descriptionValue) Console.WriteLine() Next row End Sub
В этом примере отображаются следующие результаты:
isColumnNull=False, Description=Side Mirror isColumnNull=True, Description=Null
Присвоение для многих столбцов или строк
DataTable.Add , DataTable.LoadDataRow или другие API-интерфейсы, принимающие массив ItemArray, который сопоставляется со строкой, сопоставляют значение NULL со значением по умолчанию DataColumn. Если объект в массиве содержит DbNull.Value или строго типизированный аналог, применяются те же правила, которые описаны выше.
Кроме того, следующие правила применяются к экземпляру назначений NULL DataRow.[«columnName»] :
- Значение по умолчанию для DbNull.Value всех, кроме строго типизированных столбцов NULL, где оно является соответствующим строго типизированным значением NULL.
- Значения NULL никогда не записываются во время сериализации в XML-файлы (как в xsi:nil).
- Все значения, в том числе по умолчанию, отличные от NULL, всегда записываются при сериализации в XML. Это отличается от семантики XSD/XML, где значение NULL (xsi: nil) является явным, а значение по умолчанию — неявным (если отсутствует в XML, то проверяющее средство синтаксического анализа может получить его из связанной схемы XSD). Обратное верно для DataTable : значение NULL является неявным, а значение по умолчанию — явным.
- Всем отсутствующим значениям столбцов для строк, считываемых из входных данных XML, присваивается значение NULL. Строкам, созданным с помощью NewRow или аналогичных методов, присваивается значение по умолчанию DataColumn.
- Метод IsNull возвращает true как для DbNull.Value , так и для INullable.Null .
Сравнение значений NULL с типами SqlType и CLR
При сравнении значений NULL важно понимать разницу между тем, как метод Equals вычисляет значения NULL в System.Data.SqlTypes по сравнению с тем, как он работает с типами CLR. Все методы System.Data.SqlTypes Equals используют семантику базы данных для вычисления значений NULL. Если одно или оба значения являются NULL, результатом сравнения будет NULL. С другой стороны, при использовании метода Equals CLR для двух System.Data.SqlTypes вернется значение true, если оба значения соответствуют NULL. Это отражает разницу между использованием метода экземпляра, такого как метод String.Equals CLR, и использованием статического или общего метода 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=", 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)) 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
См. также
- Типы данных SQL Server и ADO.NET
- Общие сведения об ADO.NET
Неполные данные и null
Что может означать тот факт, что у студента null в столбце GroupId?
- Значение неизвестно (нет информации, из какой группы студент)
- Значение неверно (студент учится в какой-то группе, но эта группа не представлена в БД)
- Значение еще/уже не существует (студент был зачислен, но еще не распределен в группу или уже отчислен)
- Значение не имеет смысла (студент из другого университета, который пришел с какими-то целями в ИТМО)
- Значение недоступно (недостаточно прав узнать группу)
На основе этих предположений можно сделать вывод, что значение null сильно зависит от контекста (какую предметную область мы моделируем итд.).
Вполне возможно, что возникнет необходимость различать разные виды того, что значение в том или ином смысле отсутствует.
Можно ли обойтись без null?
Как представить кортеж с неопределенными частями в нашем случае?
- Разбить на 2 группы и сделать необязательную связь 1:1. В таком случае, в дополнительной таблице будет запись (StudentId, GroupId) тогда и только тогда, когда у студента определена группа
Где еще появляется null
- Результаты внешних соединений
- Результаты множественных операций
Оказывается, что в некоторых случаях без null не обойтись и надо уметь с ним работать.
Тернарная логика с использованием null
С точки зрения SQL, результат логического выражения может быть true, false или unknown.
С другой стороны есть тип boolean, и у него есть 3 значения: true, false и null
То есть формально unknown — это результат вычисления, а null — это конкретное значение, которое может быть записано в БД. На практике unknown представляется значением null, и это различие не будет иметь большого значения.
Конъюнкция
[math]\bf[/math] | [math]\bf[/math] | [math]\bf[/math] | [math]\bf[/math] |
---|---|---|---|
[math]\bf[/math] | [math]true[/math] | [math]unknown[/math] | [math]false[/math] |
[math]\bf[/math] | [math]unknown[/math] | [math]unknown[/math] | [math]false[/math] |
[math]\bf[/math] | [math]false[/math] | [math]false[/math] | [math]false[/math] |
Дизъюнкция
[math]\bf[/math] | [math]\bf[/math] | [math]\bf[/math] | [math]\bf[/math] |
---|---|---|---|
[math]\bf[/math] | [math]true[/math] | [math]true[/math] | [math]true[/math] |
[math]\bf[/math] | [math]true[/math] | [math]unknown[/math] | [math]unknown[/math] |
[math]\bf[/math] | [math]true[/math] | [math]unknown[/math] | [math]false[/math] |
Отрицание
[math][/math] | [math]\bf[/math] | [math]\bf[/math] | [math]\bf[/math] |
---|---|---|---|
[math]\bf[/math] | [math]false[/math] | [math]unknown[/math] | [math]true[/math] |
Сравнение
Равенство
[math]\bf[/math] | [math]\bf[/math] | [math]\bf[/math] | [math]\bf[/math] |
---|---|---|---|
[math]\bf[/math] | [math]true[/math] | [math]unknown[/math] | [math]false[/math] |
[math]\bf[/math] | [math]unknown[/math] | [math]unknown[/math] | [math]unknown[/math] |
[math]\bf[/math] | [math]false[/math] | [math]unknown[/math] | [math]true[/math] |
is
[math][/math] | [math]\bf[/math] | [math]\bf[/math] | [math]\bf[/math] |
---|---|---|---|
[math]\bf[/math] | [math]true[/math] | [math]false[/math] | [math]false[/math] |
[math]\bf[/math] | [math]false[/math] | [math]true[/math] | [math]false[/math] |
[math]\bf[/math] | [math]false[/math] | [math]false[/math] | [math]true[/math] |
[math]\bf[/math] | [math]false[/math] | [math]true[/math] | [math]true[/math] |
[math]\bf[/math] | [math]true[/math] | [math]false[/math] | [math]true[/math] |
[math]\bf[/math] | [math]true[/math] | [math]true[/math] | [math]false[/math] |
Проблемы при работе с null
При работе с null в процессе разработки БД, во избежание непредвиденных ошибок, необъодимо заранее ознакомиться с тем, какие проблемы могут возникнуть.
Вывод логических выражений
В новой тернарной логике работают не все правила преобразований, присущие двоичной. Например, нельзя полагать, что [math](A\ \vee\ \neg\ A)[/math] всегда истинно, потому что теперь может получиться unknown.
Поэтому при каждом преобразовании троичного логического выражения, лучше сверяться с таблицами истинности.
Скалярные операции, порождающие null
Следующие операции с null порождают null, и иногда это может сбивать с толку начинающих разработчиков.
- [math]=[/math] , [math]\lt \gt [/math] , [math]\lt [/math] , [math]\lt =[/math] , [math]\gt [/math] , [math]\gt =[/math]
- [math]+[/math] , [math]−[/math] , [math]*[/math] , [math]/[/math]
- [math]\|[/math]
- [math]in[/math]
Рассмотрим несколько примеров.
select (1 + null) from Students;
Не смотря на то, что этот запрос не несет большого смысла, на его примере можно убедиться, что в арифметических операциях null «заразен».
select StudentId from Students where GroupId = null;
Это частая ошибка, сравнение с null дает unknown, а значит запрос вернет пустую таблицу.
Говоря об операции сравнения, стоит отметить, что она не транзитивна и не рефлексивна.
- [math]x\ =\ x[/math] — true или null
- [math]x\ \lt \gt \ x[/math] — true или null
- [math]x\ or\ x[/math] — true или null
- [math]x\ or\ not\ x[/math] — true или null
- [math]x\ and\ not\ x[/math] — false или null
Дубликаты и null
Так как null ≠ null, сравнения кортежей, содержащих null не обладают интуитивными свойствами, например:
- [math]R \cup R[/math] — не всегда [math]R[/math]
- [math]R \cap R[/math] — не всегда [math]R[/math]
- [math]R \bowtie R[/math] — не всегда [math]R[/math]
Неинтуитивность null
Рассмотрим запрос, для нахождения студентов не из группы ‘M34391’.
select * from Students where GroupId <> 'M34391'
Корректность запроса зависит от смысла null. Неясно, надо ли возвращать в этом запросе студента, о котором нет информации, в какой группе он учится.
Следующий запрос, хоть и выглядит странно, предполагает просто поиск всевозможных студентов
select * from Students where GroupId <> 'M34391' union select * from Students where GroupId = 'M34391'
Но из-за наличия null, этот запрос не отработает так, как предполагалось. Если GroupId студента null, то сравнение не вернет true, а значит в результате это учтено не будет.
Подробнее у работе функции where будет рассказано в следующем разделе.
Работа с null в SQL
Несмотря на множество проблем, описанных выше, в SQL существуют механизмы, позволяющие корректно обработать null.
Проверки значений
Для сравнения с null используется is null (или is not null ). Получить всех студентов с null в поле GroupId можно следующим образом:
select StudentId from Students where GroupId is null;
В общем виде синтаксис проверки значений выглядит следующим образом: значение is [ not ] < null |true|false|unknown >, например:
- x is not true
- x or x is not null
Так же в SQL существует функция coalesce(v1, v2, . ), которая принимает произвольное число аргументов и возвращает первый не не null. Если все аргументы null, то возвращает null.
Ключи и null
Можно использовать null:
- Альтернативные ключи
- Внешние ключи
- Простые
- Составные, отсутствующие целиком
Первичные ключи не могут содержать null.
Предикаты
DML
where и having считают истинным предикат только если он вернул true Зная этот факт можно, например убедиться, что false and unknown дает false. Следующий запрос вернет 1:
select 1 where not (0 = 1 and 0 = null)
DDL
C точки зрения check constraint-ов не подходит только false. Unknown превращается в true
Различимость
Два null не равны и не различимы. Это важно для distinct и group by
Например, кортежи (1, null) и (1, null) склеятся в случае distinct и не породят разные группы в случае group by , т.к. не различимыТипы столбцов
В SQL столбцы могут быть nullable (по умолчанию) и не nullable
birthday date birthday date not null
Перед созданием nullable столбца, рекомендуется дополнительно обдумать, какой конкретно смысл вкладывается в null в данном случае, не скажется ли это негативно на остальных запросах, в случае, если начать его использовать. Если есть возможность, во избежание дополнительных проблем, описанных выше, лучше объявлять столбцы not null.
Прочее
- exists
- возвращает true или ‘false’
- если внутри получились только строки, состоящие из null, то вернет так же false
- пропускают null, т.е. не учитывают его при подсчете
- при отсутствии аргументов, отличных от null, возвращают null
- исключением является count (*), что просто считает количество строк
- Помимо указаний порядка сортировки (asc или desc), можно указывать, куда ставить null-ы — в начало или в конец. Например order by year nulls first. По умолчанию null-ы складываются либо в начало, либо в конец, это нужно уточнять в документации к конкретной СУБД.