2009-08-20 8 views
7

Мне интересно, есть ли эффективный запрос для выбора отдельных дат (игнорирования времени) из таблицы с полем datetime в SQL Server.Как быстро выбрать даты DISTINCT из поля Date/Time, SQL Server

Моя проблема не в том, что сервер действительно делает это (я уже видел this question, и у нас было что-то подобное уже с использованием DISTINCT). Проблема заключается в том, есть ли какой-либо трюк, чтобы сделать это быстрее. Используя данные, которые мы используем, наш текущий запрос возвращает ~ 80 отдельных дней, для которых существует ~ 40 000 строк данных (после фильтрации по другому индексированному столбцу), есть индекс в столбце даты, и запрос всегда удается принять 5 секунд. Это слишком медленно.

Изменение структуры базы данных может быть вариантом, но менее желательным.

ответ

6

Каждая опция, которая включает манипуляции CAST или TRUNCATE или DATEPART в поле datetime, имеет ту же проблему: запрос должен сканировать весь набор результатов (40k), чтобы найти отдельные даты. Производительность может незначительно отличаться между различными реализациями.

Что вам действительно нужно, это иметь индекс, который может дать ответ в мигающем режиме. Вы можете либо иметь постоянный вычисленный столбец с индексом, который (требует изменения структуры таблицы), либо индексированный вид (requires Enterprise Edition for QO to consider the index из коробки).

сохранялось вычисляемый столбец:

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); 

Обновление

Для того, чтобы полностью исключить одно сканирование можно было использовать GROUP BY обманом индексированного представления, как это:

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

+0

Есть ли способ использовать 'SKIP SCAN' в' SQL Server'? Я просто попробовал ваше решение на таблице «2M», и это стало еще хуже («DISTINCT CAST (...)» в поле «DATETIME» заняло «850 мс» с «Агрегатом совпадения хэшей», «ДАТА DISTINCT» «1800 мс» с «Сводным агрегатом»). «Oracle» и «MySQL» просто перепрыгивают через отдельные поля в индексе, «SQL Server» этого не делает. – Quassnoi

+0

Вам нужно будет выбрать отдельную дату_значения после создания индекса на нем. –

+0

'@ Remus': я создал индекс, и оптимизатор его использовал. – Quassnoi

9

Я использовал следующие:

CAST(FLOOR(CAST(@date as FLOAT)) as DateTime); 

Это удаляет время от даты путем преобразования его в float и усечения от «времени» часть, которая десятичное в float.

Выглядит немного неуклюже, но хорошо работает на большом наборе данных (~ 100 000 строк). Я использую несколько раз в течение дня.

3

Самый простой способ - добавить вычисленный столбец только для части даты и выбрать его. Вы можете сделать это в представлении, если вы не хотите менять таблицу.

2

Update:

Решение ниже профпригодность на 2M столе и берет, но 40 ms.

Простая DISTINCT на индексированной вычисленной колонке взято 9 seconds.

Посмотреть эту запись в моем блоге подробности производительности:


К сожалению, SQL Server 'оптимизатор s может сделать ни Oracle, ни SKIP SCANMySQL' 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

+0

Построение таблицы дат и затем частично присоединиться к оригиналу является отличное решение. ИМХО дополнительные накладные расходы сохраненного столбца с индексом или индексированным представлением имеют смысл только в том случае, если вам приходилось делать эту операцию очень часто (произвольное угадывание: как пара сто раз в день). Я всегда предпочел бы сначала попытаться найти лучший запрос, чем добавить дополнительную сложность/накладные расходы в структуру БД. –

0

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

Если вы используете SQL Server 2005 или более поздней версии затем PERSISTED вычисленное поле путь

 
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. 
+1

Основной причиной задержки является сканирование и сортировка для создания отдельных. Если в скалярной операции не возникает что-то * extreamly * complex, задержки в базе данных всегда связаны с доступом к данным, а не с скалярными операциями. –

+0

Это основная причина задержки, потому что она заставляет полностью сканировать таблицу - извините, должно было сделать это ясно – Cruachan

0

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

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

3

Я не уверен, почему ваш существующий запрос займет более 5 секунд для 40 000 строк.

Я просто попробовал следующий запрос к таблице со 100 000 строк, и она вернулась менее чем за 0,1 с.

SELECT DISTINCT DATEADD(day, 0, DATEDIFF(day, 0, your_date_column)) 
FROM your_table 

(Обратите внимание, что этот запрос, вероятно, не сможет воспользоваться любыми индексами на колонке даты, но она должна быть достаточно быстро, при условии, что вы не выполняете его десятки раз в секунду.)

+0

простой и понятный, это должен быть правильный ответ –

0

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

1

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

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

Не уверен эффективность, но это определенно самый красивый способ сделать это. – ylnor

5

Это работает для меня:

SELECT distinct(CONVERT(varchar(10), {your date column}, 111)) 
FROM {your table name}