optimisasi kueri: interval waktu

10

Secara utama, saya punya dua jenis interval waktu:

presence time dan absence time

absence time dapat dari jenis yang berbeda (misalnya istirahat, absen, hari khusus dan sebagainya) dan interval waktu mungkin tumpang tindih dan / atau berpotongan.

Hal ini tidak pasti, bahwa hanya kombinasi yang masuk akal dari interval ada di data mentah, misalnya. interval kehadiran yang tumpang tindih tidak masuk akal, tetapi mungkin ada. Saya sudah mencoba mengidentifikasi interval waktu kehadiran yang dihasilkan dalam banyak cara sekarang - bagi saya, yang paling nyaman tampaknya adalah yang mengikuti.

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

lihat SQL-Fiddle untuk beberapa data demo.

Data mentah ada di berbagai tabel dalam bentuk "starttime" - "endtime"atau "starttime" - "duration".

Idenya adalah untuk mendapatkan daftar yang terurut dari setiap cap waktu dengan jumlah bergulir "bitmasked" dari interval terbuka pada setiap waktu untuk memperkirakan waktu kehadiran.

Biola bekerja dan memberikan hasil yang diperkirakan, meskipun waktu jeda dari interval yang berbeda sama. Tidak ada indeks yang digunakan dalam contoh ini.

Apakah ini cara yang tepat untuk mencapai tugas yang dipertanyakan atau apakah ada cara yang lebih elegan untuk ini?

Jika relevan untuk menjawab: jumlah data akan mencapai beberapa sepuluh ribu dataset per karyawan per tabel. sql-2012 tidak tersedia untuk menghitung jumlah bergulir para pendahulu inline secara agregat.


edit:

Hanya menjalankan kueri terhadap jumlah testdata yang lebih besar (1000, 10.000, 100.000, 1 juta) dan dapat melihat bahwa runtime meningkat secara eksponensial. Jelas sebuah bendera peringatan, bukan?

Saya mengubah kueri dan menghapus agregasi jumlah bergulir dengan pembaruan yang unik.

Saya telah menambahkan tabel tambahan:

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

dan saya pindah menghitung rolling sum ke tempat ini:

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

lihat SQL-Fiddle di sini

Runtime menurun menjadi 3 detik mengenai 1 juta entri dalam tabel "worktime".

Pertanyaan tetap sama : Apa cara paling efektif untuk menyelesaikan ini?

Nico
sumber
Saya yakin akan ada pertentangan mengenai hal ini, tetapi Anda mungkin mencoba untuk tidak melakukannya dalam CTE. Gunakan tabel temp sebagai gantinya, dan lihat apakah ini lebih cepat.
rottengeek
Hanya pertanyaan gaya: Saya belum pernah melihat orang menaruh semua nama kolom dan nama tabel mereka dalam tanda kutip ganda. Apakah ini praktik seluruh perusahaan Anda? Saya pasti merasa tidak nyaman. Itu tidak perlu dalam pandangan saya, dan dengan demikian meningkatkan kebisingan di atas sinyal ...
ErikE
@ErikE Metode di atas adalah bagian dari addon besar. Beberapa objek dibuat secara dinamis dan bergantung pada pilihan input pengguna akhir. Jadi mis. Blank bisa muncul dalam tabel- atau view-names. tanda kutip ganda di sekitar mereka tidak akan membiarkan permintaan crash ...!
Nico
@Nico di duniaku yang biasanya dilakukan dengan tanda kurung kemudian suka [this]. Saya hanya suka itu lebih baik daripada tanda kutip ganda, saya kira.
ErikE
@ErikE kurung kotak adalah tsql. standar adalah tanda kutip ganda! Lagi pula, saya mempelajarinya seperti itu dan entah bagaimana terbiasa dengan itu!
Nico

Jawaban:

3

Saya tidak bisa menjawab pertanyaan Anda tentang cara yang terbaik. Tetapi saya dapat menawarkan cara berbeda untuk menyelesaikan masalah, yang mungkin atau mungkin tidak lebih baik. Ini memiliki rencana pelaksanaan yang cukup datar, dan saya pikir itu akan berkinerja baik. (Saya ingin tahu, jadi bagikan hasilnya!)

Saya minta maaf karena menggunakan gaya sintaksis saya sendiri dan bukan gaya Anda - ini membantu permintaan sihir datang kepada saya ketika semuanya berbaris di tempat biasanya.

Kueri tersedia dalam SqlFiddle . Saya melemparkan tumpang tindih untuk EmpID 1 hanya untuk memastikan saya memiliki itu tertutup. Jika pada akhirnya Anda menemukan bahwa tumpang tindih tidak dapat terjadi dalam data keberadaan, maka Anda dapat menghapus kueri akhir dan Dense_Rankperhitungan.

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;

Catatan: kinerja kueri ini akan ditingkatkan Anda menggabungkan tiga tabel dan menambahkan kolom untuk menunjukkan seperti apa waktu: bekerja, istirahat, atau tidak ada.

Dan mengapa semua CTE, Anda bertanya? Karena masing-masing dipaksa oleh apa yang perlu saya lakukan untuk data. Ada agregat, atau saya harus meletakkan kondisi WHERE pada fungsi windowing atau menggunakannya dalam klausa di mana fungsi windowing tidak diperbolehkan.

Sekarang saya akan pergi dan melihat apakah saya tidak bisa memikirkan strategi lain untuk mencapai ini. :)

Untuk hiburan, saya sertakan di sini "diagram" yang saya buat untuk membantu memecahkan masalah:

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

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

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

Tiga set tanda hubung (dipisahkan oleh spasi) mewakili, secara berurutan: data keberadaan, data absensi, dan hasil yang diinginkan.

ErikE
sumber
Terima kasih atas pendekatan ini. Saya akan memeriksanya ketika kembali ke kantor dan memberi Anda hasil runtime dengan basis data yang lebih besar.
Nico
Runtime jelas jauh lebih tinggi dari pendekatan 1. Saya tidak punya waktu untuk memeriksa apakah indeks lebih lanjut dapat menurunkannya. Akan memeriksa sesegera mungkin!
Nico
Saya punya ide lain saya belum punya waktu untuk bekerja. Untuk apa nilainya, kueri Anda mengembalikan hasil yang salah dengan rentang yang tumpang tindih di semua tabel.
ErikE
Saya memeriksa ini lagi, melihat biola yang memiliki interval yang benar-benar tumpang tindih di ketiga tabel. itu mengembalikan hasil yang benar, seperti yang saya lihat. dapatkah Anda memberikan kasus di mana hasil yang salah dikembalikan? jangan ragu untuk menyesuaikan data demo di biola!
Nico
baiklah, saya mengerti maksud Anda. dalam hal memotong interval dalam satu tabel, hasilnya menjadi gila. akan memeriksa ini.
Nico