Как быстро выбрать различные даты из поля Дата / Время, SQL Server
Мне интересно, есть ли хорошо выполняющийся запрос для выбора различных дат (игнорирование времени) из таблицы с полем datetime в SQL Server.
моя проблема не в том, что сервер действительно делает это (я видел этот вопрос уже, и у нас было что-то подобное уже на месте, используя DISTINCT). Проблема в том, есть ли какой-либо трюк, чтобы сделать это быстрее. С данными, которые мы используем, наш текущий запрос возвращает ~80 различных дней для в котором есть ~40 000 строк данных (после фильтрации по другому индексированному столбцу), есть индекс в столбце даты, и запрос всегда успевает занять 5+ секунд. Что слишком медленно.
изменение структуры базы данных может быть вариант, но менее желательный.
10 ответов
каждый параметр, который включает в себя приведение или усечение или манипуляцию DATEPART в поле datetime, имеет ту же проблему: запрос должен сканировать весь набор результатов (40k), чтобы найти различные даты. Производительность может незначительно отличаться между различными implementaitons.
что вам действительно нужно, так это иметь индекс, который может вызвать ответ в мгновение ока. У вас может быть либо сохраненный вычисляемый столбец с индексом, который (требует изменения структуры таблицы), либо индексированный вид (требуется Enterprise Edition для QO, чтобы рассмотреть индекс из-из-коробки).
материализованный вычисляемый столбец:
alter table foo add date_only as convert(char(8), [datetimecolumn], 112) persisted;
create index idx_foo_date_only on foo(date_only);
индексированное представление:
create view v_foo_with_date_only
with schemabinding as
select id
, convert(char(8), [datetimecolumn], 112) as date_only
from dbo.foo;
create unique clustered index idx_v_foo on v_foo_with_date_only(date_only, id);
обновление
чтобы полностью устранить сканирование, можно использовать группу с помощью обманутого индексированного представления, например:
create view v_foo_with_date_only
with schemabinding as
select
convert(char(8), [d], 112) as date_only
, count_big(*) as [dummy]
from dbo.foo
group by convert(char(8), [d], 112)
create unique clustered index idx_v_foo on v_foo_with_date_only(date_only)
запрос select distinct date_only from foo
будет использовать это индексированное представление. По-прежнему является сканированием технически, но по уже "отдельному" индексу, поэтому только необходимые отчеты сканируются. Его Хак, я считаю, я бы не рекомендовал его для живого производственного кода.
AFAIK SQL Server не имеет возможности сканирования истинного индекса с пропуском повторов, т. е. искать топ, то искать больше, чем сверху, тогда succesively добиваться большего, чем в прошлом нашли.
я использовал следующие:
CAST(FLOOR(CAST(@date as FLOAT)) as DateTime);
это удаляет время от даты путем преобразования его в float
и усечение части "Время", которая является десятичной частью float
.
выглядит немного неуклюже, но хорошо работает на большом наборе данных (~100 000 строк), который я использую неоднократно в течение дня.
это работает для меня:
SELECT distinct(CONVERT(varchar(10), {your date column}, 111))
FROM {your table name}
самый простой способ-добавить вычисляемый столбец только для части даты и выбрать его. Вы можете сделать это в представлении, если не хотите менять таблицу.
Я не уверен, почему ваш существующий запрос займет 5s для 40 000 строк.
Я просто попробовал следующий запрос к таблице с 100 000 строк и он вернулся менее чем за 0,1 сек.
SELECT DISTINCT DATEADD(day, 0, DATEDIFF(day, 0, your_date_column))
FROM your_table
(обратите внимание, что этот запрос, вероятно, не сможет использовать какие-либо индексы в столбце даты, но он должен быть достаточно быстрым, предполагая, что вы не выполняете его десятки раз в секунду.)
обновление:
Решение ниже проверено на эффективность на 2M
таблица и принимает но 40 ms
.
простые DISTINCT
на индексированный вычисляемый столбец взял 9 seconds
.
посмотреть эту запись в моем блоге для деталей исполнения:
к сожалению, SQL Server
оптимизатор не может сделать ни Oracle SKIP SCAN
ни MySQL
' s INDEX FOR GROUP-BY
.
всегда Stream Aggregate
что занимает много времени.
вы можете построить список возможных дат, используя рекурсивный CTE
и присоединиться к нему со своим столом:
WITH rows AS (
SELECT CAST(CAST(CAST(MIN(date) AS FLOAT) AS INTEGER) AS DATETIME) AS mindate, MAX(date) AS maxdate
FROM mytable
UNION ALL
SELECT mindate + 1, maxdate
FROM rows
WHERE mindate < maxdate
)
SELECT mindate
FROM rows
WHERE EXISTS
(
SELECT NULL
FROM mytable
WHERE date >= mindate
AND date < mindate + 1
)
OPTION (MAXRECURSION 0)
это будет более эффективно, чем Stream Aggregate
каков ваш предикат в этом другом отфильтрованном столбце ? Вы пробовали, получаете ли вы улучшение от индекса в другом отфильтрованном столбце, за которым следует поле datetime ?
Я в основном предполагаю здесь, но 5 секунд, чтобы отфильтровать набор, возможно, 100000 строк до 40000, а затем сделать вид (что, по-видимому, происходит), не кажется мне необоснованным временем. Почему ты говоришь, что это слишком медленно ? Потому что это не соответствует ожиданиям ?
Если вы хотите избежать извлечения шага или переформатирования даты, что, по-видимому, является основной причиной задержки (путем принудительного полного сканирования таблицы), у вас нет альтернативы, кроме как сохранить дату только часть datetime, что, к сожалению, потребует изменения структуры базы данных.
Если вы используете SQL Server 2005 или более поздней версии, то сохраненное вычисляемое поле-это путь
Unless otherwise specified, computed columns are virtual columns that are not physically stored in the table. Their values are recalculated every time they are referenced in a query. The Database Engine uses the PERSISTED keyword in the CREATE TABLE and ALTER TABLE statements to physically store computed columns in the table. Their values are updated when any columns that are part of their calculation change. By marking a computed column as PERSISTED, you can create an index on a computed column that is deterministic but not precise.