tối ưu hóa truy vấn: khoảng thời gian


10

Trong chính, tôi đã có hai loại khoảng thời gian:

presence timeabsence time

absence time có thể có nhiều loại khác nhau (ví dụ: nghỉ, vắng mặt, ngày đặc biệt, v.v.) và khoảng thời gian có thể chồng chéo và / hoặc giao nhau.

Đó là không chắc chắn, rằng chỉ có sự kết hợp hợp lý của chu kỳ tồn tại trong dữ liệu thô, ví dụ. sự hiện diện chồng chéo không có ý nghĩa, nhưng có thể tồn tại. Bây giờ tôi đã cố gắng xác định các khoảng thời gian hiện diện theo nhiều cách - đối với tôi, sự thoải mái nhất dường như là theo dõi.

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

xem SQL-Fiddle để biết một số dữ liệu demo.

Dữ liệu thô tồn tại trong các bảng khác nhau ở dạng "starttime" - "endtime"hoặc "starttime" - "duration".

Ý tưởng là để có được một danh sách theo thứ tự của mỗi dấu thời gian với tổng số lần mở "bitmasked" mỗi lần để ước tính thời gian hiện diện.

Fiddle hoạt động và cho kết quả ước tính, ngay cả khi thời gian bắt đầu của các khoảng khác nhau là bằng nhau. Không có chỉ số được sử dụng trong ví dụ này.

Đây có phải là cách đúng đắn để đạt được nhiệm vụ nghi vấn hay có cách nào thanh lịch hơn cho việc này?

Nếu có liên quan để trả lời: lượng dữ liệu sẽ lên tới vài nghìn bộ dữ liệu cho mỗi nhân viên mỗi bảng. sql-2012 không có sẵn để tính tổng của các tiền thân nội tuyến trong tổng hợp.


biên tập:

Chỉ cần thực hiện truy vấn đối với số lượng testdata lớn hơn (1000, 10.000, 100.000, 1 triệu) và có thể thấy thời gian chạy tăng theo cấp số nhân. Rõ ràng là một lá cờ cảnh báo, phải không?

Tôi đã thay đổi truy vấn và loại bỏ tổng hợp cán tổng bằng một bản cập nhật kỳ quặc.

Tôi đã thêm một bảng phụ trợ:

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

và tôi đã di chuyển tính toán tổng số đến nơi này:

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

xem SQL-Fiddle tại đây

Thời gian chạy giảm xuống còn 3 giây liên quan đến 1 triệu mục trong "thời gian làm việc".

Câu hỏi vẫn giữ nguyên : Cách hiệu quả nhất để giải quyết vấn đề này là gì?


Tôi chắc chắn sẽ có tranh cãi về vấn đề này, nhưng bạn có thể thử không làm điều đó trong CTE. Sử dụng bảng tạm thời thay thế, và xem nếu nó nhanh hơn.
rottengeek

Chỉ là một câu hỏi về phong cách: Tôi chưa bao giờ thấy ai đặt tất cả các tên cột và tên bảng của họ trong dấu ngoặc kép. Đây có phải là thực tế của toàn bộ công ty của bạn? Tôi chắc chắn thấy khó chịu. Theo quan điểm của tôi thì không cần thiết và do đó làm tăng nhiễu qua tín hiệu ...
ErikE

@ErikE Phương pháp trên là một phần của một addon khổng lồ. Một số đối tượng được tạo động và phụ thuộc vào lựa chọn đầu vào của người dùng cuối. Vì vậy, ví dụ như khoảng trống có thể xuất hiện trong tên bảng hoặc tên xem. trích dẫn kép xung quanh những người sẽ không để cho truy vấn sụp đổ ...!
Nico

@Nico trong thế giới của tôi thường được thực hiện với dấu ngoặc vuông thì thích [this]. Tôi chỉ thích điều đó tốt hơn so với trích dẫn kép, tôi đoán.
ErikE

@ErikE dấu ngoặc vuông là tsql. tiêu chuẩn là báo giá kép! Dù sao, tôi đã học nó theo cách đó và vì vậy bằng cách nào đó đã quen với nó!
Nico

Câu trả lời:


3

Tôi không thể trả lời câu hỏi của bạn theo cách hoàn toàn tốt nhất. Nhưng tôi có thể cung cấp một khác nhau cách giải quyết vấn đề, mà có thể hoặc không thể được tốt hơn. Nó có một kế hoạch thực hiện hợp lý bằng phẳng, và tôi nghĩ nó sẽ hoạt động tốt. (Tôi rất muốn biết vì vậy hãy chia sẻ kết quả!)

Tôi xin lỗi vì đã sử dụng kiểu cú pháp của riêng tôi thay vì của bạn - nó giúp thuật sĩ truy vấn đến với tôi khi mọi thứ xếp hàng ở vị trí thông thường.

Các truy vấn có sẵn trong một SqlFiddle . Tôi đã ném vào một chồng chéo cho EmpID 1 chỉ để chắc chắn rằng tôi đã được bảo hiểm. Nếu cuối cùng bạn thấy rằng sự trùng lặp không thể xảy ra trong dữ liệu hiện diện, thì bạn có thể xóa truy vấn cuối cùng và các Dense_Rankphép tính.

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;

Lưu ý: hiệu suất của truy vấn này sẽ được cải thiện khi bạn kết hợp ba bảng và thêm một cột để cho biết đó là loại thời gian nào: làm việc, nghỉ hoặc vắng mặt.

Và tại sao tất cả các CTE, bạn yêu cầu? Bởi vì mỗi người bị ép buộc bởi những gì tôi cần làm với dữ liệu. Có một tổng hợp, hoặc tôi cần đặt một điều kiện WHERE trên một chức năng cửa sổ hoặc sử dụng nó trong một mệnh đề mà các chức năng cửa sổ không được phép.

Bây giờ tôi sẽ đi ra ngoài và xem liệu tôi không thể nghĩ ra một chiến lược khác để thực hiện điều này. :)

Để giải trí tôi bao gồm ở đây một "sơ đồ" tôi đã thực hiện để giúp giải quyết vấn đề:

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

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

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

Ba bộ dấu gạch ngang (cách nhau bởi khoảng trắng) thể hiện theo thứ tự: dữ liệu hiện diện, dữ liệu vắng mặt và kết quả mong muốn.


Cảm ơn vì cách tiếp cận này. Tôi sẽ kiểm tra nó khi quay lại văn phòng và cung cấp cho bạn kết quả thời gian chạy với cơ sở dữ liệu lớn hơn.
Nico

Thời gian chạy chắc chắn cao hơn nhiều so với cách tiếp cận thứ nhất. Tôi không có thời gian để kiểm tra nếu các chỉ số tiếp theo có thể làm giảm nó. Sẽ kiểm tra sớm nhất có thể!
Nico

Tôi có một ý tưởng khác là tôi không có thời gian để làm việc. Đối với giá trị của nó, truy vấn của bạn trả về kết quả sai với các phạm vi chồng chéo trong tất cả các bảng.
ErikE

Tôi đã kiểm tra lại lần nữa, xem fiddle này có các khoảng hoàn toàn chồng chéo trong cả ba bảng. Nó trả về kết quả chính xác, như tôi có thể thấy. bạn có thể cung cấp một trường hợp trong đó kết quả sai được trả lại? cảm thấy tự do để điều chỉnh dữ liệu demo trong fiddle!
Nico

được rồi, tôi đã nhận được quan điểm của bạn trong trường hợp các khoảng giao nhau trong một bảng, kết quả trở nên điên rồ. sẽ kiểm tra cái này
Nico
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.