Попытка оптимизировать запрос, который выбирает «приблизительную ближайшую запись» - PullRequest
5 голосов
/ 16 февраля 2012

У меня есть таблица, которая содержит много данных, где нам особенно важно поле date. Причиной этого является то, что объем данных увеличился примерно в 30 раз, и старые способы скоро рухнут. Запрос, который, я надеюсь, вы можете помочь мне оптимизировать, должен:

  • взять список дат (генерируется табличной функцией на основе cte)
  • получить одну запись для каждой из этих дат
    • на основе некоторого определения «ближайшего» * ​​1009 *

Например, текущая таблица содержит данные с интервалами в 5 секунд (+/- немного). Мне нужно отобрать эту таблицу и получить запись, которая находится ближе всего к 30-секундному интервалу.

То, что у меня сейчас есть, работает просто отлично. Мне просто любопытно, если есть способ оптимизировать это больше. Если бы я мог сделать это в Linq To SQL, это тоже было бы здорово. Меня даже интересуют предложения по индексам, учитывая количество значений даты (~ 2 млн. Мин строк).

declare @st  datetime ; set @st  = '2012-01-31 05:05:00';
declare @end datetime ; set @end = '2012-01-31 05:10:00';

select distinct
    log.*   -- id, 
from 
    dbo.fn_GenerateDateSteps(@st, @end, 30) as d
        inner join lotsOfLogData log on l.Id = (
            select top 1 e.[Id]
            from 
                lotsOfLogData as log  -- contains data in 5 second intervals
            where
                log.stationId = 1000 
                -- search for dates in a certain range
                AND utcTime between DateAdd(s, -10, dt) AND DateAdd(s, 5, dt)
            order by
                -- get the 'closest'. this can change a little, but will always 
                -- be based on a difference between the date
                abs(datediff(s, dt, UtcTime)) 
        )
    -- updated the query to be correct. stadionId should be inside the subquery

Структура таблицы lotsOfLogData приведена ниже. Идентификаторов станций относительно немного (может быть, 50), но много записей для каждой. Мы знаем идентификатор станции, когда запрашиваем.

create table ##lotsOfLogData (
    Id          bigint      identity(1,1) not null
,   StationId   int         not null
,   UtcTime     datetime    not null
    -- 20 other fields, used for other calculations
)

fn_GenerateDateSteps возвращает такой набор данных для заданных параметров:

[DT]
2012-01-31 05:05:00.000
2012-01-31 05:05:30.000
2012-01-31 05:06:00.000
2012-01-31 05:06:30.000  (and so on, every 30 seconds)

Я также сделал это с временной таблицей, но это оказалось немного дороже.

declare @dates table ( dt datetime, ClosestId bigint); 
insert into @dates (dt) select dt from dbo.fn_GenerateDateSteps(@st, @end, 30)
update @dates set closestId = ( -- same subquery as above )
select * from lotsOfLogData inner join @dates on Id = ClosestId

Редактировать: Исправлено

Теперь 200K + строк для работы. Я попробовал оба способа, и перекрестное применение с соответствующим индексом (id / time + includes (... все столбцы ...) работало просто отлично. Однако я закончил запросом, который начал, используя более простой (и существующий) индекс на [id + время]. Более понятный вопрос - почему я остановился на этом. Может быть, есть еще лучший способ сделать это, но я не вижу его: D

-- subtree cost (crossapply) : .0808
-- subtree cost (id based)   : .0797

-- see above query for what i ended up with

Ответы [ 2 ]

1 голос
/ 17 февраля 2012

Вы можете попробовать

  • , изменив inner join на cross apply.
  • Перемещение where log.stationid в подвыбор.

Оператор SQL

SELECT  DISTINCT log.*   -- id, 
FROM    dbo.fn_GenerateDateSteps(@st, @end, 30) AS d
        CROSS APPLY (
            SELECT  TOP 1 log.*
            FROM    lotsOfLogData AS log  -- contains data in 5 second intervals
            WHERE   -- search for dates in a certain range
                    utcTime between DATEADD(s, -10, d.dt) AND DATEADD(s, 5, d.dt)
                    AND log.stationid = 1000
            ORDER BY
                    -- get the 'closest'. this can change a little, but will always 
                    -- be based on a difference between the date
                    ABS(DATEDIFF(s, d.dt, UtcTime)) 
        ) log
1 голос
/ 17 февраля 2012

Просто некоторые мысли ... на самом деле это нельзя назвать ответом, но он был слишком большим для поля для комментариев.

Прежде всего, я бы посмотрел план выполнения запроса, если вы неЭто уже сделано.

Более эзотерично: есть ли у вас возможность представлять даты в виде примитивных значений (например, целое число, представляющее секунды / минуты с четко определенного времени)?Хотя я полагаю, что SQL Server хранит даты в виде числовых значений под капотом, операции с примитивом могут быть немного быстрее, поскольку это исключит повторные вызовы DateAdd() и DateDiff().

.(довольно старая) статья дает примеры того, как SQL Server на самом деле хранит даты.Возможно, вы могли бы оставить свои даты как DATETIME, но оперировать ими с базовой математикой.

Независимо от типа данных, я бы поэкспериментировал с кластеризованным индексом в столбце даты, так как кажется, что ваши поиски могут выиграть от физического упорядочениячто обеспечивает кластерный индекс, особенно если вы ищете в узких диапазонах.Опять же, план выполнения, вероятно, был бы поучительным.

Я также мог видеть звездную схему, используемую для представления ваших данных, с измерением даты, которое содержало обобщения даты.Затем вы можете искать против обобщения.Даже если обобщения не использовались, фактическое число дат будет уменьшено, поскольку все факты с одной и той же датой могут указывать на одну и ту же запись в измерении, поэтому дату нужно будет оценивать только один раз.

Наконец, что мастер настройки производительности SQL (я полагаю, что это в 2005 году, я знаю, что это в 2008 году) предлагает для вашего запроса?Я бы не рекомендовал слепо реализовывать его предложения, но я часто нахожу хорошие идеи в вещах, которые он рекомендует.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...