Я видел много случаев, когда вам нужно проецировать «недостающие данные». Например. у вас есть временной ряд (например, журнал доступа), и вы хотите показать количество посещений в день за последние 30 дней (панель инструментов аналитики). Если вы сделаете a, select count(...) from ... group by day
вы получите счет за каждый день, но результат будет содержать только строку за каждый день, когда у вас был хотя бы один доступ. С другой стороны, если вы сначала проецируете таблицу дней из таблицы чисел ( select dateadd(day, -number, today) as day from numbers
), а затем оставляете объединение со счетчиками (или внешним применением, как вам нравится), то вы получите результат с 0 для количества дней, которые вы не было доступа. Это только один пример. Конечно, можно утверждать, что уровень представления вашей информационной панели может обрабатывать пропущенные дни и просто отображать 0, но некоторые инструменты (например, SSRS) просто не смогут справиться с этим.
Другие примеры, которые я видел, использовали аналогичные трюки с временными рядами (дата / время +/- число) для выполнения всех видов оконных вычислений. В общем, всякий раз, когда в императивном языке вы используете цикл for с хорошо известным числом итераций, декларативный и заданный характер SQL может использовать прием, основанный на таблице чисел.
Кстати, я чувствую необходимость подчеркнуть тот факт, что даже при использовании таблицы чисел это выглядит как императивное процедурное исполнение, не впадайте в заблуждение, полагая, что это обязательно. Позвольте мне привести пример:
int x;
for (int i=0;i<1000000;++i)
x = i;
printf("%d",x);
Эта программа выдаст 999999, что в значительной степени гарантировано.
Давайте попробуем то же самое в SQL Server, используя таблицу чисел. Сначала создайте таблицу из 1 000 000 номеров:
create table numbers (number int not null primary key);
go
declare @i int = 0
, @j int = 0;
set nocount on;
begin transaction
while @i < 1000
begin
set @j = 0;
while @j < 1000
begin
insert into numbers (number)
values (@j*1000+@i);
set @j += 1;
end
commit;
raiserror (N'Inserted %d*1000', 0, 0, @i)
begin transaction;
set @i += 1;
end
commit
go
Теперь давайте сделаем цикл for:
declare @x int;
select @x = number
from numbers with(nolock);
select @x as [@x];
Результат:
@x
-----------
88698
Если у вас сейчас есть момент WTF (в конце концов, number
это кластерный первичный ключ!), Этот трюк называется сканированием порядка распределения, и я не вставил @j*1000+@i
его случайно ... Вы могли бы также рискнуть и сказать, что результат состоит в том, что параллелизм и это иногда может быть правильным ответом.
Под этим мостом много троллей, и я упомянул некоторые из них в « Булевых операторах на SQL Server», и функции T-SQL не предполагают определенного порядка выполнения.