оптимизация запроса: временные интервалы

10

В основном у меня есть два вида временных интервалов:

presence time и absence time

absence time могут быть разных типов (например, перерывы, пропуски, особый день и т. д.), и временные интервалы могут перекрываться и / или пересекаться.

Это не обязательно, что только правдоподобные комбинации интервалов существуют в исходных данных, например. перекрывающиеся интервалы присутствия не имеют смысла, но могут существовать. Я попытался определить итоговые интервалы времени присутствия разными способами - для меня наиболее удобным представляется следующий.

;with "timestamps"
as
(
    select
        "id" = row_number() over ( order by "empId", "timestamp", "opening", "type" )
        , "empId"
        , "timestamp"
        , "type"
        , "opening"
    from
    (
        select "empId", "timestamp", "type", case when "types" = 'starttime' then 1 else -1 end as "opening" from
        ( select "empId", "starttime", "endtime", 1 as "type" from "worktime" ) as data
        unpivot ( "timestamp" for "types" in ( "starttime", "endtime" ) ) as pvt
        union all
        select "empId", "timestamp", "type", case when "types" = 'starttime' then 1 else -1 end as "opening" from
        ( select "empId", "starttime", "endtime", 2 as "type" from "break" ) as data
        unpivot ( "timestamp" for "types" in ( "starttime", "endtime" ) ) as pvt
        union all
        select "empId", "timestamp", "type", case when "types" = 'starttime' then 1 else -1 end as "opening" from
        ( select "empId", "starttime", "endtime", 3 as "type" from "absence" ) as data
        unpivot ( "timestamp" for "types" in ( "starttime", "endtime" ) ) as pvt
    ) as data
)
select 
      T1."empId"
    , "starttime"   = T1."timestamp"
    , "endtime"     = T2."timestamp"
from 
    "timestamps" as T1
    left join "timestamps" as T2
        on T2."empId" = T1."empId"
        and T2."id" = T1."id" + 1
    left join "timestamps" as RS
        on RS."empId" = T2."empId"
        and RS."id" <= T1."id"      
group by
    T1."empId", T1."timestamp", T2."timestamp"
having
    (sum( power( 2, RS."type" ) * RS."opening" ) = 2)
order by 
    T1."empId", T1."timestamp";

см. SQL-Fiddle для некоторых демонстрационных данных.

Необработанные данные существуют в разных таблицах в форме "starttime" - "endtime"или "starttime" - "duration".

Идея заключалась в том, чтобы получить упорядоченный список каждой временной метки с «битовой маской» скользящей суммы открытых интервалов в каждый момент времени для оценки времени присутствия.

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

Это правильный путь для достижения поставленной задачи или есть более элегантный способ для этого?

Если уместно ответить: объем данных будет составлять до нескольких десятков тысяч наборов данных на одного сотрудника на таблицу. sql-2012 не доступен для расчета скользящей суммы встроенных предшественников в совокупности.


редактировать:

Просто выполнил запрос по большому количеству тестовых данных (1000, 10.000, 100.000, 1 миллион) и увидел, что время выполнения увеличивается в геометрической прогрессии. Очевидно, предупреждающий флаг, верно?

Я изменил запрос и удалил агрегацию скользящей суммы с помощью причудливого обновления.

Я добавил вспомогательную таблицу:

create table timestamps
(
  "id" int
  , "empId" int
  , "timestamp" datetime
  , "type" int
  , "opening" int
  , "rolSum" int
)

create nonclustered index "idx" on "timestamps" ( "rolSum" ) include ( "id", "empId", "timestamp" )

и я переместил расчет суммы проката в это место:

declare @rolSum int = 0
update "timestamps" set @rolSum = "rolSum" = @rolSum + power( 2, "type" ) * "opening" from "timestamps"

см. SQL-скрипку здесь

Время выполнения сократилось до 3 секунд, что соответствует 1 миллиону записей в таблице «рабочее время».

Вопрос остается тем же : какой самый эффективный способ решить это?

Нико
источник
Я уверен, что будет спор по этому вопросу, но вы можете попытаться не делать это в CTE. Вместо этого используйте временные таблицы и посмотрите, быстрее ли это.
Rottengeek
Просто вопрос стиля: я никогда не видел, чтобы кто-нибудь поместил все имена своих столбцов и таблиц в двойные кавычки. Это практика всей вашей компании? Я определенно нахожу это неудобным. Это не является необходимым, на мой взгляд, и, следовательно, увеличивает шум по сигналу ...
ErikE
@ErikE Выше метод является частью огромного дополнения. Некоторые из объектов создаются динамически и зависят от выбора конечного пользователя. Так, например, пробелы могут появляться в именах таблиц или представлений. двойные кавычки вокруг них не позволят сбою запроса ...!
Нико
@ Нико в моем мире это обычно делается в квадратных скобках, а потом нравится [this]. Думаю, мне это нравится лучше, чем двойные кавычки.
ErikE
@ErikE квадратные скобки это ЦКЛ. Стандарт это двойные кавычки! во всяком случае, я узнал это таким образом, и так или иначе я привык к этому!
Нико

Ответы:

3

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

Я прошу прощения за использование моего собственного стиля синтаксиса вместо вашего - он помогает магии запросов приходить ко мне, когда все выстраивается в привычное место.

Запрос доступен в SqlFiddle . Я добавил перекрытие для EmpID 1 только для того, чтобы быть уверенным, что это покрыто. Если в конечном итоге вы обнаружите, что в данных присутствия не может быть перекрытий, вы можете удалить окончательный запрос и Dense_Rankвычисления.

WITH Points AS (
  SELECT DISTINCT
    T.EmpID,
    P.TimePoint
  FROM
    (
      SELECT * FROM dbo.WorkTime
      UNION SELECT * FROM dbo.BreakTime
      UNION SELECT * FROM dbo.Absence
    ) T
    CROSS APPLY (VALUES (StartTime), (EndTime)) P (TimePoint)
), Groups AS (
  SELECT
    P.EmpID,
    P.TimePoint,
    Grp =
      Row_Number()
      OVER (PARTITION BY P.EmpID ORDER BY P.TimePoint, X.Which) / 2
  FROM
    Points P
    CROSS JOIN (VALUES (1), (2)) X (Which)
), Ranges AS (
  SELECT
    G.EmpID,
    StartTime = Min(G.TimePoint),
    EndTime = Max(G.TimePoint)
  FROM Groups G
  GROUP BY
    G.EmpID,
    G.Grp
  HAVING Count(*) = 2
), Presences AS (
  SELECT
    R.*,
    P.Present,
    Grp =
       Dense_Rank() OVER (PARTITION BY R.EmpID ORDER BY R.StartTime)
       - Dense_Rank() OVER (PARTITION BY R.EmpID, P.Present ORDER BY R.StartTime)
  FROM
    Ranges R
    CROSS APPLY (
      SELECT
        CASE WHEN EXISTS (
          SELECT *
          FROM dbo.WorkTime W
          WHERE
            R.EmpID = W.EmpID
            AND R.StartTime < W.EndTime
            AND W.StartTime < R.EndTime
        ) AND NOT EXISTS (
          SELECT *
          FROM dbo.BreakTime B
          WHERE
            R.EmpID = B.EmpID
            AND R.StartTime < B.EndTime
            AND B.StartTime < R.EndTime
        ) AND NOT EXISTS (
          SELECT *
          FROM dbo.Absence A
          WHERE
            R.EmpID = A.EmpID
            AND R.StartTime < A.EndTime
            AND A.StartTime < R.EndTime
        ) THEN 1 ELSE 0 END
    ) P (Present)
)
SELECT
  EmpID,
  StartTime = Min(StartTime),
  EndTime = Max(EndTime)
FROM Presences
WHERE Present = 1
GROUP BY
  EmpID,
  Grp
ORDER BY
  EmpID,
  StartTime;

Примечание: производительность этого запроса будет улучшена, если вы объедините три таблицы и добавите столбец, чтобы указать, сколько времени это было: работа, перерыв или отсутствие.

И почему все CTE, спросите вы? Потому что каждый вынужден делать то, что мне нужно сделать с данными. Есть агрегат, или мне нужно поместить условие WHERE в оконную функцию или использовать его в предложении, где оконные функции не разрешены.

Теперь я собираюсь уйти и посмотреть, не могу ли я придумать другую стратегию для достижения этой цели. :)

Для развлечения я привожу здесь «диаграмму», которую я сделал, чтобы помочь решить проблему:

------------
   -----------------
                ---------------
                           -----------

    ---    ------   ------       ------------

----   ----      ---      -------

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

ErikE
источник
Спасибо за такой подход. Я проверю это, когда вернусь в офис, и дам вам результаты времени выполнения с большей базой данных.
Нико
Время выполнения определенно намного выше, чем 1-й подход. У меня не было времени, чтобы проверить, могут ли дальнейшие индексы его уменьшить. Проверим как можно скорее!
Нико
У меня есть другая идея, у меня не было времени, чтобы начать работать. Во всяком случае, ваш запрос возвращает неправильные результаты с перекрывающимися диапазонами во всех таблицах.
ErikE
Я проверил это снова, вижу эту скрипку, которая имеет полностью перекрывающиеся интервалы во всех трех таблицах. как я вижу, он возвращает правильные результаты. Не могли бы вы привести случай, когда возвращаются неправильные результаты? не стесняйтесь настраивать демонстрационные данные в скрипке!
Нико
хорошо, я понял вашу точку зрения в случае пересечения интервалов в одной таблице результаты сводились на нет. проверим это.
Нико