Как быстро выбрать DISTINCT даты из поля даты / времени, SQL Server - PullRequest
7 голосов
/ 20 августа 2009

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

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

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

Ответы [ 10 ]

9 голосов
/ 20 августа 2009

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

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

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

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

5 голосов
/ 10 октября 2011

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

SELECT distinct(CONVERT(varchar(10), {your date column}, 111)) 
FROM {your table name}
5 голосов
/ 20 августа 2009

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

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

Обновление

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

3 голосов
/ 20 августа 2009

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

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

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

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

3 голосов
/ 20 августа 2009

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

2 голосов
/ 20 августа 2009

Обновление:

Решение, приведенное ниже, проверено на эффективность на 2M столе и занимает, но 40 ms.

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

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


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

1 голос
/ 23 февраля 2011

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

SELECT
DISTINCT DATE_FORMAT(your_date_column,'%Y-%m-%d') AS date
FROM ...
0 голосов
/ 20 августа 2009

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

0 голосов
/ 20 августа 2009

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

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

0 голосов
/ 20 августа 2009

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

Если вы используете 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. 
...