sorgu optimizasyonu: zaman aralıkları


10

Genelde iki çeşit zaman aralığım var:

presence time ve absence time

absence time farklı tiplerde olabilir (örn. molalar, devamsızlıklar, özel gün vb.) ve zaman aralıkları çakışabilir ve / veya kesişebilir.

Öyle değil aralıklarla yalnızca uyumlu kombinasyonları, örneğin çiğ verilerde var olduğunu, kesin. örtüşen mevcudiyet aralıkları bir anlam ifade etmiyor, ama var olabilir. Sonuçta ortaya çıkan mevcudiyet zaman aralıklarını birçok yönden tanımlamaya çalıştım - benim için en rahat olan şu sıradan biri gibi görünüyor.

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

bazı demo verileri için bkz. SQL-Fiddle .

Ham veriler farklı tablolarda "starttime" - "endtime"veya biçiminde bulunur "starttime" - "duration".

Fikir, mevcudiyet süresini tahmin etmek için her seferinde açık aralıkların "bitmasked" yuvarlanma toplamı ile her zaman damgasının sıralı bir listesini almaktı.

Keman çalışır ve farklı aralıklardaki başlangıçlar eşit olsa bile tahmini sonuçlar verir. Bu örnekte hiçbir indeks kullanılmamıştır.

Bu, sorgulanan göreve ulaşmanın doğru yolu mu yoksa bunun için daha zarif bir yol var mı?

Yanıtlama ile ilgili ise: veri miktarı, tablo başına çalışan başına on bine kadar veri kümesine kadar olacaktır. sql-2012 toplamda satır içi öncüllerin yuvarlanan toplamını hesaplamak için kullanılamaz.


Düzenle:

Sorguyu büyük miktarda test verisine (1000, 10.000, 100.000, 1 milyon) karşı yürüttüm ve çalışma zamanının katlanarak arttığını görebilirsiniz. Açıkçası bir uyarı bayrağı, değil mi?

Sorguyu değiştirdim ve ilginç bir güncellemeyle haddeleme toplamının toplanmasını kaldırdım.

Yardımcı bir tablo ekledim:

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

ve haddeleme toplamını hesaplayarak bu yere taşıdım:

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

SQL-Fiddle'ı buradan görebilirsiniz

"Çalışma zamanı" tablosundaki 1 milyon giriş ile ilgili çalışma süresi 3 saniyeye düştü.

Soru aynı kalır : Bunu çözmenin en etkili yolu nedir?


Eminim bu konuda çekişme olacaktır, ancak bunu bir CTE'de yapmamaya çalışabilirsiniz . Bunun yerine geçici tablolar kullanın ve daha hızlı olup olmadığına bakın.
rottengeek

Sadece bir stil sorusu: Hiç kimsenin tüm sütun adlarını ve tablo adlarını çift tırnak içine aldığını görmedim. Bu tüm şirketinizin uygulaması mı? Kesinlikle rahatsız edici buluyorum. Benim görüşüme göre gerekli değildir ve bu nedenle sinyal üzerindeki gürültüyü arttırır ...
ErikE

@ErikE Above yöntemi büyük bir eklentinin bir parçasıdır. Nesnelerin bazıları dinamik olarak oluşturulur ve son kullanıcı giriş seçimine bağlıdır. Örneğin, boşluklar tablo veya görünüm adlarında ortaya çıkabilir. bunların etrafında çift tırnak sorgu çökmesine izin vermez ...!
Nico

@Nico benim dünyamda genellikle köşeli parantez ile yapılır [this]. Bunu çift tırnaktan daha iyi seviyorum sanırım.
ErikE

@ErikE köşeli parantez tsql'dir. standart çift ​​tırnak olduğunu! Her neyse, bu şekilde öğrendim ve bir şekilde alışkınım!
Nico

Yanıtlar:


3

Sorunuza kesinlikle en iyi şekilde cevap veremem. Ama problemi çözmenin farklı bir yolunu sunabilirim , ki bu daha iyi olabilir veya olmayabilir. Oldukça düz bir yürütme planı var ve bence iyi performans gösterecek. (Bilmek istekli olduğum için sonuçları paylaş!)

Senin yerine kendi sözdizim tarzımı kullandığım için özür dilerim - her şey her zamanki yerinde sıralandığında sorgu sihirbazının bana gelmesine yardımcı olur.

Sorgu bir SqlFiddle içinde kullanılabilir . Sadece bunu kapsadığımdan emin olmak için EmpID 1 için bir çakışma içine attım. Sonunda, durum verilerinde çakışmaların meydana gelemeyeceğini fark ederseniz, son sorguyu ve Dense_Rankhesaplamaları kaldırabilirsiniz .

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;

Not: Bu sorgunun performansı iyileştirilir, üç tabloyu birleştirirsiniz ve ne tür bir zaman olduğunu belirtmek için bir sütun eklersiniz: iş, ara veya yokluk.

Ve neden tüm CTE'ler soruyorsunuz? Çünkü her biri veriye yapmam gereken şey tarafından zorlanıyor. Bir toplama var, ya da bir pencere fonksiyonuna WHERE koşulu koymam ya da pencere fonksiyonlarına izin verilmeyen bir cümlede kullanmam gerekiyor.

Şimdi gideceğim ve bunu başarmak için başka bir strateji düşünüp düşünemeyeceğimi göreceğim. :)

Eğlence için buraya sorunu çözmeye yardımcı olmak için yaptığım bir "diyagram" ekliyorum:

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

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

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

Üç çizgi kümesi (boşluklarla ayrılmış) sırayla temsil eder: varlık verileri, yokluk verileri ve istenen sonuç.


Bu yaklaşım için teşekkürler. Ofise döndüğünüzde kontrol edeceğim ve size daha büyük veri tabanıyla çalışma zamanı sonuçları vereceğim.
Nico

Çalışma süresi kesinlikle 1. yaklaşımdan çok daha yüksektir. Başka endekslerin henüz azaltıp azaltamayacağını kontrol etmek için zamanım yoktu. En kısa sürede kontrol edecek!
Nico

Çalışmak için vaktim olmadığım başka bir fikrim var. Değeri ne olursa olsun, sorgunuz tüm tablolarda çakışan aralıklarla yanlış sonuçlar döndürüyor.
ErikE

Bunu tekrar kontrol ettim, üç tabloda da tamamen çakışan aralıklar olan bu kemanı görün . Gördüğüm gibi doğru sonuçları döndürüyor. Yanlış sonuçların döndürüldüğü bir durum sunabilir misiniz? keman demo verileri ayarlamak için çekinmeyin!
Nico

tamam, ne demek istediğimi anladım. bir tabloda kesişen aralıkların olması durumunda sonuçlar çıldırdı. kontrol edecek.
Nico
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.