Это проблема, с которой я периодически сталкиваюсь и пока не нашел хорошего решения.
Предположим, следующая структура таблицы
CREATE TABLE T
(
A INT PRIMARY KEY,
B CHAR(1000) NULL,
C CHAR(1000) NULL
)
а также требование , чтобы определить , является ли любым из столбцов обнуляемых B
или C
фактически содержит какое - либо NULL
значение (и если да , какой из (ы)).
Также предположим, что таблица содержит миллионы строк (и что нет статистики по столбцам, на которую можно было бы взглянуть, поскольку меня интересует более общее решение для этого класса запросов).
Я могу придумать несколько способов приблизиться к этому, но у всех есть недостатки.
Два отдельных EXISTS
заявления. Это имеет преимущество в том, что позволяет запросам прекратить сканирование сразу же после NULL
обнаружения. Но если оба столбца на самом деле не содержат NULL
s, то получится два полных сканирования.
Единый совокупный запрос
SELECT
MAX(CASE WHEN B IS NULL THEN 1 ELSE 0 END) AS B,
MAX(CASE WHEN C IS NULL THEN 1 ELSE 0 END) AS C
FROM T
Это может обрабатывать оба столбца одновременно, поэтому наихудший случай - одно полное сканирование. Недостатком является то, что, даже если он встречает a NULL
в обоих столбцах на очень раннем этапе запроса, он все равно будет сканировать всю оставшуюся таблицу.
Пользовательские переменные
Я могу придумать третий способ сделать это
BEGIN TRY
DECLARE @B INT, @C INT, @D INT
SELECT
@B = CASE WHEN B IS NULL THEN 1 ELSE @B END,
@C = CASE WHEN C IS NULL THEN 1 ELSE @C END,
/*Divide by zero error if both @B and @C are 1.
Might happen next row as no guarantee of order of
assignments*/
@D = 1 / (2 - (@B + @C))
FROM T
OPTION (MAXDOP 1)
END TRY
BEGIN CATCH
IF ERROR_NUMBER() = 8134 /*Divide by zero*/
BEGIN
SELECT 'B,C both contain NULLs'
RETURN;
END
ELSE
RETURN;
END CATCH
SELECT ISNULL(@B,0),
ISNULL(@C,0)
но это не подходит для производственного кода, так как правильное поведение для запроса совокупной конкатенации не определено. и в любом случае прекращение сканирования с выдачей ошибки - довольно ужасное решение.
Есть ли другой вариант, который сочетает в себе сильные стороны вышеуказанных подходов?
редактировать
Просто чтобы обновить это с результатами, которые я получаю с точки зрения чтения для ответов, представленных до сих пор (используя тестовые данные @ ypercube)
+----------+------------+------+---------+----------+----------------------+----------+------------------+
| | 2 * EXISTS | CASE | Kejser | Kejser | Kejser | ypercube | 8kb |
+----------+------------+------+---------+----------+----------------------+----------+------------------+
| | | | | MAXDOP 1 | HASH GROUP, MAXDOP 1 | | |
| No Nulls | 15208 | 7604 | 8343 | 7604 | 7604 | 15208 | 8346 (8343+3) |
| One Null | 7613 | 7604 | 8343 | 7604 | 7604 | 7620 | 7630 (25+7602+3) |
| Two Null | 23 | 7604 | 8343 | 7604 | 7604 | 30 | 30 (18+12) |
+----------+------------+------+---------+----------+----------------------+----------+------------------+
Для ответа @ Thomas я изменился TOP 3
на, TOP 2
чтобы потенциально позволить ему выйти раньше. Я получил параллельный план по умолчанию для этого ответа, поэтому также попробовал его с MAXDOP 1
подсказкой, чтобы сделать число операций чтения более сопоставимым с другими планами. Я был несколько удивлен результатами, так как в моем предыдущем тесте я видел короткое замыкание запроса без чтения всей таблицы.
План для моих тестовых данных, что короткие замыкания ниже
План данных ypercube:
Таким образом, он добавляет блокирующий оператор сортировки в план. Я также попытался с HASH GROUP
подсказкой, но это все равно заканчивается чтением всех строк
Таким образом, ключ, кажется, состоит в том, чтобы заставить hash match (flow distinct)
оператора разрешить этот план короткому замыканию, так как другие альтернативы будут блокировать и потреблять все строки в любом случае. Я не думаю, что есть подсказка, чтобы форсировать это конкретно, но, по-видимому, «в общем, оптимизатор выбирает Flow Distinct, где он определяет, что требуется меньше выходных строк, чем есть различные значения во входном наборе». ,
Данные @ ypercube имеют только 1 строку в каждом столбце со NULL
значениями (количество элементов в таблице = 30300), и предполагаемые строки, входящие и выходящие из оператора, являются обоими 1
. Сделав предикат немного более непрозрачным для оптимизатора, он сгенерировал план с оператором Flow Distinct.
SELECT TOP 2 *
FROM (SELECT DISTINCT
CASE WHEN b IS NULL THEN NULL ELSE 'foo' END AS b
, CASE WHEN c IS NULL THEN NULL ELSE 'bar' END AS c
FROM test T
WHERE LEFT(b,1) + LEFT(c,1) IS NULL
) AS DT
Редактировать 2
Последнее, что пришло мне в голову, это то, что запрос, приведенный выше, все равно может завершить обработку большего количества строк, чем необходимо, в случае, если первая строка, с которой он сталкивается, NULL
имеет значения NULL в столбце B
и C
. Он продолжит сканирование, а не завершит работу немедленно. Одним из способов избежать этого было бы отключение строк при сканировании. Итак, моя последняя поправка к ответу Томаса Кейзера ниже
SELECT DISTINCT TOP 2 NullExists
FROM test T
CROSS APPLY (VALUES(CASE WHEN b IS NULL THEN 'b' END),
(CASE WHEN c IS NULL THEN 'c' END)) V(NullExists)
WHERE NullExists IS NOT NULL
Вероятно, было бы лучше, если бы предикат был, WHERE (b IS NULL OR c IS NULL) AND NullExists IS NOT NULL
но в сравнении с предыдущими тестовыми данными, что один не дает мне план с Flow Distinct, в то время как NullExists IS NOT NULL
тот дает (план ниже).
источник
TOP 3
может быть просто ,TOP 2
как в настоящее время она будет сканировать до тех пор, пока не найдет каждый из следующих(NOT_NULL,NULL)
,(NULL,NOT_NULL)
,(NULL,NULL)
. Любые 2 из этих 3 были бы достаточны - и если он найдет(NULL,NULL)
первое, то и второе не понадобится. Кроме того , к короткому замыканию план необходимо реализовать отчетливый черезhash match (flow distinct)
оператора , а неhash match (aggregate)
илиdistinct sort
Как я понимаю вопрос, вы хотите знать, существует ли значение NULL в каком-либо из значений столбцов, а не возвращать строки, в которых B или C имеют значение NULL. Если это так, то почему бы и нет:
На моем тестовом стенде с SQL 2008 R2 и миллионом строк я получил следующие результаты в мс на вкладке Статистика клиента:
Если вы добавите подсказку nolock, результаты будут еще быстрее:
Для справки я использовал SQL-генератор Red-gate для генерации данных. Из моего миллиона строк, 9,886 строк имели нулевое значение B, а 10,019 имели нулевое значение C.
В этой серии тестов каждая строка в столбце B имеет значение:
Перед каждым тестом (оба сета) я бегал
CHECKPOINT
иDBCC DROPCLEANBUFFERS
.Вот результаты, когда в таблице нет нулей. Обратите внимание, что решение 2 существует, предоставляемое ypercube, практически идентично моему с точки зрения чтения и времени выполнения. Я (мы) полагаю, что это связано с преимуществами редакции Enterprise / Developer, использующей Advanced Scanning . Если вы использовали только стандартную версию или ниже, решение Kejser вполне может быть самым быстрым решением.
источник
IF
Разрешены ли заявления?Это должно позволить вам подтвердить существование B или C за один проход через таблицу:
источник
Протестировано в SQL-Fiddle в версиях: 2008 R2 и 2012 с 30K строк.
EXISTS
запрос показывает огромное преимущество в эффективности , когда он находит NULLS рано - который , как ожидается.EXISTS
запросом - во всех случаях в 2012 году, что я не могу объяснить.CASE
запросом Мартина .Запросы и сроки. Сроки, где сделано:
B
имеющей одинNULL
в маломid
.NULL
на малых идентификаторах.Здесь мы идем (есть проблема с планами, я попробую позже. Перейдите по ссылкам на данный момент):
Запрос с 2 подзапросами EXISTS
Единый общий запрос Мартина Смита
Запрос Томаса Кейзера
Мое предложение (1)
Это требует некоторой полировки на выходе, но эффективность аналогична
EXISTS
запросу. Я думал, что будет лучше, когда нет нулей, но тестирование показывает, что это не так.Предложение (2)
Попытка упростить логику:
Похоже, что в 2008R2 он работает лучше, чем в предыдущем предложении, но хуже в 2012 году (возможно, второе
INSERT
можно переписать, используяIF
ответ @ 8kb):источник
Когда вы используете EXISTS, SQL Server знает, что вы делаете проверку существования. Когда он находит первое совпадающее значение, он возвращает ИСТИНА и прекращает поиск.
когда вы объединяете 2 столбца, и если любой равен нулю, результат будет нулевым
например
так проверьте этот код
источник
Как насчет:
Если это сработает (я не проверял), то получится таблица из одной строки с 2 столбцами, каждый из которых имеет значение ИСТИНА или ЛОЖЬ. Я не проверял эффективность.
источник
T.B is null
это обрабатывается как логический результат,EXISTS(SELECT true)
иEXISTS(SELECT false)
оба возвращают true. Этот пример MySQL указывает на то, что оба столбца содержат NULL, хотя ни один из них не является фактически