Как быстро выбрать различные даты из поля Дата / Время, 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


я использовал этот

SELECT
DISTINCT DATE_FORMAT(your_date_column,'%Y-%m-%d') AS date
FROM ...

каков ваш предикат в этом другом отфильтрованном столбце ? Вы пробовали, получаете ли вы улучшение от индекса в другом отфильтрованном столбце, за которым следует поле datetime ?

Я в основном предполагаю здесь, но 5 секунд, чтобы отфильтровать набор, возможно, 100000 строк до 40000, а затем сделать вид (что, по-видимому, происходит), не кажется мне необоснованным временем. Почему ты говоришь, что это слишком медленно ? Потому что это не соответствует ожиданиям ?


просто преобразуйте дату:dateadd(dd,0, datediff(dd,0,[Some_Column]))


Если вы хотите избежать извлечения шага или переформатирования даты, что, по-видимому, является основной причиной задержки (путем принудительного полного сканирования таблицы), у вас нет альтернативы, кроме как сохранить дату только часть 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.