Самый эффективный способ получить диапазоны дат
-
16-10-2019 - |
Вопрос
Какой наиболее эффективный способ получить диапазоны дат с помощью такой структуры таблицы?
create table SomeDateTable
(
id int identity(1, 1) not null,
StartDate datetime not null,
EndDate datetime not null
)
go
Скажем, вам нужен диапазон для обоих StartDate
и EndDate
.Другими словами, если StartDate
попадает между @StartDateBegin
и @StartDateEnd
, и EndDate
попадает между @EndDateBegin
и @EndDateEnd
, тогда сделай что-нибудь.
Я знаю, что есть несколько способов сделать это, но какой из них наиболее рекомендуется?
Решение
В целом решить эту проблему сложно, но есть несколько вещей, которые мы можем сделать, чтобы помочь оптимизатору выбрать план.Этот скрипт создает таблицу из 10 000 строк с известным псевдослучайным распределением строк, чтобы проиллюстрировать:
CREATE TABLE dbo.SomeDateTable
(
Id INTEGER IDENTITY(1, 1) PRIMARY KEY NOT NULL,
StartDate DATETIME NOT NULL,
EndDate DATETIME NOT NULL
);
GO
SET STATISTICS XML OFF
SET NOCOUNT ON;
DECLARE
@i INTEGER = 1,
@s FLOAT = RAND(20120104),
@e FLOAT = RAND();
WHILE @i <= 10000
BEGIN
INSERT dbo.SomeDateTable
(
StartDate,
EndDate
)
VALUES
(
DATEADD(DAY, @s * 365, {d '2009-01-01'}),
DATEADD(DAY, @s * 365 + @e * 14, {d '2009-01-01'})
)
SELECT
@s = RAND(),
@e = RAND(),
@i += 1
END
Первый вопрос — как индексировать эту таблицу.Один из вариантов — предоставить два индекса на DATETIME
столбцы, поэтому оптимизатор может, по крайней мере, выбрать, следует ли искать StartDate
или EndDate
.
CREATE INDEX nc1 ON dbo.SomeDateTable (StartDate, EndDate)
CREATE INDEX nc2 ON dbo.SomeDateTable (EndDate, StartDate)
Естественно, неравенства в обоих случаях StartDate
и EndDate
это означает, что только один столбец в каждом индексе может поддерживать поиск в примере запроса, но это лучшее, что мы можем сделать.Мы могли бы рассмотреть возможность сделать второй столбец в каждом индексе INCLUDE
а не ключ, но у нас могут быть другие запросы, которые могут выполнять поиск равенства в ведущем столбце и поиск неравенства во втором столбце.Кроме того, таким образом мы можем получить лучшую статистику.В любом случае...
DECLARE
@StartDateBegin DATETIME = {d '2009-08-01'},
@StartDateEnd DATETIME = {d '2009-10-15'},
@EndDateBegin DATETIME = {d '2009-08-05'},
@EndDateEnd DATETIME = {d '2009-10-22'}
SELECT
COUNT_BIG(*)
FROM dbo.SomeDateTable AS sdt
WHERE
sdt.StartDate BETWEEN @StartDateBegin AND @StartDateEnd
AND sdt.EndDate BETWEEN @EndDateBegin AND @EndDateEnd
В этом запросе используются переменные, поэтому, как правило, оптимизатор будет предполагать избирательность и распределение, что приведет к предполагаемой оценке мощности 81 ряд.Фактически запрос выдает 2076 строк, и это несоответствие может быть важным в более сложном примере.
В SQL Server 2008 SP1 CU5 или более поздней версии (или R2 RTM CU1) мы можем воспользоваться преимуществами Оптимизация внедрения параметров чтобы получить более точные оценки, просто добавив OPTION (RECOMPILE)
к SELECT
запрос выше.Это вызывает компиляцию непосредственно перед выполнением пакета, позволяя SQL Server «видеть» реальные значения параметров и оптимизировать их.Благодаря этому изменению оценка улучшается до 468 строк (хотя вам нужно проверить план выполнения, чтобы увидеть это).Эта оценка лучше, чем 81 строка, но все же не так уж и близко.Расширения моделирования, включенные флаг трассировки 2301 может помочь в некоторых случаях, но не с этим запросом.
Проблема заключается в том, что строки, определенные в результате поиска по двум диапазонам, перекрываются.Одно из упрощающих допущений, сделанных в компоненте оценки стоимости и мощности оптимизатора, заключается в том, что предикаты независимы (поэтому, если оба имеют избирательность 50 %, предполагается, что результат применения обоих соответствует 50 % из 50 % = 25 % строк). ).Если такого рода корреляция является проблемой, мы часто можем обойти ее с помощью многостолбцовой и/или отфильтрованной статистики.При наличии двух диапазонов с неизвестными начальной и конечной точками это становится непрактичным.Здесь нам иногда приходится прибегать к переписыванию запроса в форме, которая дает лучшую оценку:
SELECT COUNT(*) FROM
(
SELECT
sdt.Id
FROM dbo.SomeDateTable AS sdt
WHERE
sdt.StartDate BETWEEN @StartDateBegin AND @StartDateEnd
INTERSECT
SELECT
sdt.Id
FROM dbo.SomeDateTable AS sdt
WHERE
sdt.EndDate BETWEEN @EndDateBegin AND @EndDateEnd
) AS intersected (id)
OPTION (RECOMPILE)
Эта форма дает оценку времени выполнения в 2110 строк (по сравнению с фактическими 2076).Если только у вас не включен TF 2301, в этом случае более продвинутые методы моделирования распознают трюк и дадут точно такую же оценку, как и раньше:468 рядов.
Однажды SQL Server может получить встроенную поддержку интервалов.Если это будет иметь хорошую статистическую поддержку, разработчики, возможно, будут меньше бояться настройки таких планов запросов.
Другие советы
Я не знаю решение, которое быстро для всех распределений данных, но если все ваши диапазоны короткие, мы обычно можем ускорить его. Если, например, диапазоны короче, чем один день, вместо этого запроса:
SELECT TaskId ,
TaskDescription ,
StartedAt ,
FinishedAt
FROM dbo.Tasks
WHERE '20101203' BETWEEN StartedAt AND FinishedAt
Мы можем добавить еще одно условие:
SELECT TaskId ,
TaskDescription ,
StartedAt ,
FinishedAt
FROM dbo.Tasks
WHERE '20101203' BETWEEN StartedAt AND FinishedAt
AND StartedAt >= '20101202'
AND FinishedAt <= '20101204' ;
В результате вместо сканирования всей таблицы запрос сканирует только диапазон всего два дня, что быстрее. Если диапазоны могут быть длиннее, мы можем хранить их как последовательности более коротких. Подробности здесь: Настройка запросов SQL с помощью ограничений