การปรับให้เหมาะสมของแบบสอบถาม: ช่วงเวลา


10

ในหลักฉันมีช่วงเวลาสองชนิด:

presence time และ absence time

absence time อาจเป็นประเภทที่แตกต่างกัน (เช่นตัวแบ่งการขาดวันพิเศษและอื่น ๆ ) และช่วงเวลาอาจทับซ้อนและ / หรือตัดกัน

มันเป็นความไม่แน่นอนว่ามีเพียงการรวมกันเป็นไปได้ของช่วงเวลาที่มีอยู่ในข้อมูลดิบเช่น การทับซ้อนช่วงเวลาการแสดงตนไม่สมเหตุสมผล แต่อาจมีอยู่ ฉันพยายามระบุช่วงเวลาการแสดงตนที่เกิดขึ้นในหลาย ๆ ทางในตอนนี้สำหรับฉันความสะดวกสบายที่ดีที่สุดน่าจะเป็น follwing

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

แนวคิดคือการรับรายการสั่งซื้อของทุกการประทับเวลาด้วยผลรวมการหมุน "bitmasked" ของช่วงเวลาที่เปิดในแต่ละครั้งเพื่อประเมินเวลาการแสดงตน

ซอทำงานและให้ผลลัพธ์โดยประมาณแม้ว่าช่วงเวลาที่แตกต่างกันจะเท่ากันก็ตาม ไม่มีการใช้ดัชนีในตัวอย่างนี้

นี่เป็นวิธีที่เหมาะสมในการบรรลุภารกิจที่ถูกสอบสวนหรือมีวิธีที่สง่างามกว่านี้หรือไม่?

หากเกี่ยวข้องกับการตอบ: จำนวนข้อมูลจะมากถึงหนึ่งหมื่นชุดข้อมูลต่อพนักงานต่อตาราง sql-2012 ใช้งานไม่ได้ในการคำนวณผลรวมสะสมของรุ่นก่อนหน้าแบบอินไลน์รวม


แก้ไข:

เพียงแค่เรียกใช้คิวรีกับ testdata ในปริมาณที่มากขึ้น (1,000, 10.000, 100.000, 1 ล้าน) และจะเห็นว่า runtime นั้นเพิ่มขึ้นแบบทวีคูณ เห็นได้ชัดว่ามีธงคำเตือนใช่ไหม

ฉันเปลี่ยนเคียวรีและลบการรวมของการรวมการกลิ้งโดยการอัปเดตที่แปลกประหลาด

ฉันได้เพิ่มตารางเสริม:

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 ล้านรายการใน "worktime" -table

คำถามยังคงเหมือนเดิม : วิธีที่มีประสิทธิภาพที่สุดในการแก้ปัญหานี้คืออะไร


ฉันแน่ใจว่าจะมีการโต้แย้งในเรื่องนี้ แต่คุณอาจลองไม่ทำใน CTE ใช้ตารางชั่วคราวแทนและดูว่าเร็วขึ้นหรือไม่
rottengeek

แค่คำถามสไตล์: ฉันไม่เคยเห็นใครใส่ชื่อคอลัมน์และชื่อตารางทั้งหมดในเครื่องหมายคำพูดคู่ นี่คือการปฏิบัติของ บริษัท ทั้งหมดของคุณหรือไม่? แน่นอนฉันรู้สึกไม่สบายใจ มันไม่จำเป็นในมุมมองของฉันและทำให้เสียงดังขึ้นเหนือสัญญาณ ...
ErikE

วิธีการ @ErikE ด้านบนเป็นส่วนหนึ่งของ addon ขนาดใหญ่ วัตถุบางอย่างถูกสร้างขึ้นแบบไดนามิกและขึ้นอยู่กับผู้ใช้ปลายทาง ดังนั้นเช่นช่องว่างอาจเกิดขึ้นในชื่อตารางหรือมุมมอง อัญประกาศรอบ ๆ สิ่งเหล่านั้นจะไม่ทำให้ข้อความค้นหาขัดข้อง ... !
Nico

@Nico [this]ในโลกของฉันที่ทำมักจะมีวงเล็บแล้วเช่น ฉันชอบที่ดีกว่าคำพูดสองเท่าฉันเดา
ErikE

@ErikE วงเล็บเหลี่ยมคือ tsql มาตรฐานคือคำพูดสองเท่า! อย่างไรก็ตามฉันเรียนรู้ด้วยวิธีนั้นและฉันก็คุ้นเคยกับมัน!
Nico

คำตอบ:


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 ในฟังก์ชั่นหน้าต่างหรือใช้มันในประโยคที่ฟังก์ชั่นหน้าต่างไม่ได้รับอนุญาต

ตอนนี้ฉันกำลังจะออกไปและดูว่าฉันไม่สามารถคิดกลยุทธ์อื่นเพื่อให้บรรลุเป้าหมายนี้ได้หรือไม่ :)

เพื่อความสนุกฉันได้รวม "ไดอะแกรม" ไว้ที่นี่เพื่อช่วยแก้ปัญหา:

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

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

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

เครื่องหมายขีดคั่นสามชุด (คั่นด้วยช่องว่าง) เป็นตัวแทนตามลำดับ: ข้อมูลการแสดงตนข้อมูลการขาดและผลลัพธ์ที่ต้องการ


ขอบคุณสำหรับวิธีการนี้ ฉันจะตรวจสอบเมื่อกลับมาอยู่ในสำนักงานและให้ผลลัพธ์รันไทม์ที่มีฐานข้อมูลขนาดใหญ่
Nico

รันไทม์สูงกว่าวิธีแรกอย่างแน่นอน ฉันไม่มีเวลาตรวจสอบว่าดัชนีอาจลดลงอีกหรือไม่ จะตรวจสอบโดยเร็วที่สุด!
Nico

ฉันมีความคิดอื่นที่ฉันยังไม่มีเวลาทำงาน สำหรับสิ่งที่คุ้มค่าการค้นหาของคุณจะส่งคืนผลลัพธ์ที่ไม่ถูกต้องโดยมีช่วงที่ทับซ้อนกันในตารางทั้งหมด
ErikE

ฉันตรวจสอบเรื่องนี้อีกครั้งดูซอซึ่งมีช่วงเวลาซ้อนทับกันอย่างสมบูรณ์ในทั้งสามตาราง มันคืนผลลัพธ์ที่ถูกต้องอย่างที่ฉันเห็น คุณสามารถให้กรณีที่ผลลัพธ์ที่ผิดจะถูกส่งกลับ? อย่าลังเลที่จะปรับข้อมูลการสาธิตในซอ!
Nico

เอาล่ะฉันเข้าใจแล้ว ในกรณีที่ตัดกันเป็นช่วง ๆ ในหนึ่งตารางผลลัพธ์จะบ้า จะตรวจสอบสิ่งนี้
Nico
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.