Published on

November 18, 2020

Понимание NULL в SQL Server

Любой разработчик баз данных SQL Server знает, что NULL не означает 0 или пустую строку, это означает, что значение неизвестно. Результат простого сравнения с NULL неизвестен, поэтому требуются специальные инструкции (IS NULL, COALESCE, NULLIF и т. д.), чтобы получить желаемый правильный результат в нашем коде T-SQL. Однако существуют и другие (менее популярные) тонкости, связанные с NULL, которые необходимо понимать, чтобы писать код без ошибок. В этой статье мы рассмотрим некоторые из них.

NULL в T-SQL с оператором NOT IN

Давайте начнем с примера. У нас есть две таблицы и запрос, который использует эти таблицы. Вторая таблица содержит NULL, и она используется в подзапросе с оператором “NOT IN”. Какой будет результат?

--Создание первой временной таблицы для тестирования
IF(OBJECT_ID('tempdb..#TestTableA') IS NOT NULL)
    DROP TABLE #TestTableA

CREATE TABLE #TestTableA
(
   ID INT
)

--Создание второй временной таблицы для тестирования
IF(OBJECT_ID('tempdb..#TestTableB') IS NOT NULL)
    DROP TABLE #TestTableB

CREATE TABLE #TestTableB
(
   ID INT
)

--Вставка данных
INSERT INTO #TestTableA(ID) VALUES (1),(2),(5),(9),(3)
INSERT INTO #TestTableB(ID) VALUES (2),(7),(NULL),(9)

--Проверка результата запроса
SELECT ID
FROM #TestTableA
WHERE ID NOT IN (SELECT ID FROM #TestTableB)

Однако мы получим пустой набор результатов. Объяснение следующее – поскольку у нас есть NULL (неизвестное) значение во второй таблице, мы не можем быть уверены, что значения из первой таблицы не содержатся во второй, поэтому SQL Server возвращает пустой набор результатов. Поэтому будьте осторожны при использовании NOT IN в подзапросе, который возвращает данные из столбца с возможными значениями NULL.

Как работать с NULL и агрегатными функциями

Теперь давайте разберемся, как NULL значения влияют на результат агрегатных функций. Давайте начнем с функции COUNT(). Есть ли разница между COUNT(*) и COUNT(‘имя_столбца’)?

--Создание временной таблицы для тестирования
IF(OBJECT_ID('tempdb..#TestTable') IS NOT NULL)
    DROP TABLE #TestTable
GO

CREATE TABLE #TestTable ( ID INT )

--Вставка данных для тестирования
INSERT INTO #TestTable(ID) VALUES (1),(2),(NULL),(4)

SELECT COUNT(ID) AS 'Результат Count(ID)' FROM #TestTable

SELECT COUNT(*) AS 'Результат Count(*)' FROM #TestTable 

TRUNCATE TABLE #TestTable

--Вставка только NULL значений
INSERT INTO #TestTable(ID) VALUES (NULL),(NULL),(NULL),(NULL)

SELECT COUNT(ID) AS 'Результат Count(ID) при наличии только NULL значений в столбце ID' FROM #TestTable

SELECT COUNT(*) AS 'Результат Count(*) при наличии только NULL значений в столбце ID' FROM #TestTable 

--Min , Max, AVG
TRUNCATE TABLE #TestTable

INSERT INTO #TestTable(ID) VALUES (1),(2),(NULL),(4),(5)

SELECT AVG(ID) AS Среднее FROM #TestTable

Приведенный выше код иллюстрирует это поведение. Как мы видим, NULL значения игнорируются. Более того, когда столбец содержит только NULL значения, это поведение не изменяется: COUNT(*) возвращает количество всех строк, а COUNT(ID) возвращает 0. Агрегатные функции MIN(), MAX(), AVG() также игнорируют NULL значения.

Присваивание значений переменным и NULL

Есть ли разница между использованием SET и SELECT при присваивании значения из запроса переменной в T-SQL?

DECLARE @var INT= 0

--Создание временной таблицы для тестирования
IF(OBJECT_ID('tempdb..#TestTable') IS NOT NULL)
    DROP TABLE #TestTable

CREATE TABLE #TestTable ( ID INT )

--Вставка данных для тестирования
INSERT INTO #TestTable(ID) VALUES (1),(2),(4)

--Использование SELECT
SELECT @var=ID FROM #TestTable WHERE ID=3

SELECT @var AS 'Значение переменной после SELECT'

--Использование SET
SET @var = ( SELECT ID FROM #TestTable WHERE ID=3 )

SELECT @var AS 'Значение переменной после SET'

Результат показывает, что когда запрос не возвращает результат, SET присваивает переменной значение NULL, но SELECT не присваивает никакого значения, поэтому значение переменной остается прежним – @var=0. Еще одна вещь о инициализации переменных: когда переменная объявлена и не инициализирована, ее значение равно NULL, и любая операция с этой переменной приводит к результату NULL.

NULL и оператор GROUP BY в T-SQL

При группировке с помощью столбца в операторе GROUP BY, который содержит NULL значения, они будут объединены в одну группу в вашем наборе результатов:

IF(OBJECT_ID('tempdb..#TestTable') IS NOT NULL)
    DROP TABLE #TestTable
GO 

CREATE TABLE #TestTable ( ID INT, Value INT )

INSERT INTO #TestTable(ID, Value) VALUES (1, 10),(2, 20),(NULL, 70),(NULL, 90),(4, 50),(5, 60)

SELECT ID, AVG(Value) AS Среднее, MAX(Value) As Максимум 
FROM #TestTable 
GROUP BY ID

Как мы видим, все агрегации для группы “NULL” считаются так же, как и для других групп.

NULL и операторы < > (!=) в SQL Server

Наконец, ответим на вопрос: что будет результатом, когда мы проверяем “неравенство”, и значение одного из операндов (или обоих) является NULL? Часто разработчики ошибочно думают, что результат будет “true”, потому что NULL не равен ни одному значению. Однако результат – “false”, потому что сравнение с неизвестным значением всегда возвращает “false” (но это поведение меняется, когда SET ANSI_NULLS равно OFF).

DECLARE @a INT=1,
        @b INT

SELECT @a AS a, @b AS b

IF(@a <> @b)
    SELECT 1 AS result
ELSE
    SELECT 0 AS result

Результат – 0, что означает, что результат сравнения – “false”.

Вывод

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

Click to rate this post!
[Total: 0 Average: 0]

Let's work together

Send us a message or book free introductory meeting with us using button below.