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


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-Fiddle тут

Час виконання скоротився до 3 секунд щодо 1 мільйона записів у таблиці "робочий час".

Питання залишається тим самим : який найефективніший спосіб вирішити це?


Я впевнений, що щодо цього будуть суперечки, але ви можете спробувати не робити цього в CTE. Замість цього використовуйте тимчасові таблиці та дивіться, чи швидше це.
rottengeek

Питання про стиль: я ніколи не бачив, щоб хтось клав усі подвійні назви своїх стовпців та назви таблиць у подвійні лапки. Це практика всієї вашої компанії? Я напевно вважаю це незручним. На мій погляд, це не потрібно, і, таким чином, збільшується шум над сигналом ...
ErikE

@ErikE Вищеописаний метод є частиною величезного доповнення. Деякі об'єкти створюються динамічно і залежать від вибору введення кінцевим користувачем. Так, наприклад, пробіли можуть з'являтись у назвах таблиць або переглядів. подвійні лапки навколо них не дозволять запиту збій ...!
Ніко

@Nico в моєму світі, як правило, це робиться з квадратними дужками [this]. Мені це подобається краще, ніж подвійні цитати, напевно.
ErikE

Квадратні дужки @ErikE - tsql. стандарт - це подвійні котирування! у будь-якому випадку, я навчився цього саме так і так якось звик до цього!
Ніко

Відповіді:


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 для функції вікна або використовувати його в пункті, коли функції вікна не дозволені.

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

Для розваги я включаю сюди "схему", яку я створив, щоб допомогти вирішити проблему:

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

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

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

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


Дякую за такий підхід. Я перевірю це, коли повернусь до офісу, і дам вам результати виконання з більшою базою даних.
Ніко

Час виконання визначено набагато вище, ніж 1-й підхід. У мене не було часу перевіряти, чи не можуть її знизити подальші показники. Перевіримо якнайшвидше!
Ніко

У мене є ще одна ідея, що я не встиг працювати. Оскільки це варте, ваш запит повертає неправильні результати із діапазонами, що перекриваються у всіх таблицях.
ErikE

Я перевірив це ще раз, дивіться цю загадку, яка повністю перекриває інтервали у всіх трьох таблицях. вона повертає правильні результати, як я бачу. ви могли б надати випадок, коли повертаються неправильні результати? не соромтесь коригувати демо-дані у скрипці!
Ніко

добре, я зрозумів вашу думку. у випадку перетину інтервалів в одній таблиці результати прийшли з розуму. перевіримо це.
Ніко
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.