2009-04-23 29 views
8

Tôi đang chạy vào một khối đường trên một vấn đề lớn hơn.Kiểm tra phạm vi thời gian chồng lên nhau, vấn đề về người gác [SQL]

Là một phần của truy vấn lớn, tôi cần giải quyết vấn đề "người gác đêm". Tôi có một bảng với sự thay đổi lịch trình như vậy:

ID | Start   | End 
1 | 2009-1-1 06:00 | 2009-1-1 14:00 
2 | 2009-1-1 10:00 | 2009-1-1 18:00 
3 | 2009-2-1 20:00 | 2009-2-2 04:00 
4 | 2009-2-2 06:00 | 2009-2-2 14:00 

Là một phần của một truy vấn, tôi cần phải xác định nếu có ít nhất 1 người gác trong một căn phòng bất cứ lúc nào cho một khoảng thời gian nhất định. Vì vậy, nếu tôi đã chỉ định phạm vi 2009-1-1 06:00 đến 2009-1-1 12:00, kết quả là đúng, bởi vì ca 1 và 2 hợp nhất để bao gồm khoảng thời gian này - trên thực tế, bất kỳ số ca nào cũng có thể được xích để giữ đồng hồ. Tuy nhiên, nếu tôi đã kiểm tra 2009-2-1 22:00 đến 2009-1-2 10:00, kết quả là sai vì có khoảng nghỉ giữa 4 và 6 giờ sáng sáng hôm sau.

Tôi muốn triển khai trong LINQ này hoặc dưới dạng hàm do người dùng xác định trong SQL Server (2005), vì cả hai trường hợp này chỉ là một phần của logic của truy vấn lớn hơn. xác định các yếu tố cần chú ý. Tập dữ liệu thực sự bao gồm khoảng một trăm bản ghi thay đổi giao nhau trong một khoảng thời gian nhất định, nhưng không phải lúc nào cũng bao trùm toàn bộ phạm vi.

Gần nhất tôi đã tìm thấy là How to group ranged values using SQL Server cho phạm vi số, tuy nhiên tùy thuộc vào từng phạm vi kết thúc ngay trước khi phạm vi tiếp theo bắt đầu. Nếu tôi có thể xây dựng cùng một cái nhìn thống nhất của đồng hồ, chỉ xem xét các đồng hồ chồng lên nhau, thì nó sẽ là tầm thường để kiểm tra xem một thời gian cụ thể có được bao phủ hay không. Một cái nhìn thống nhất sẽ trông như thế này:

Start   | End 
2009-1-1 06:00 | 2009-1-1 18:00 
2009-2-1 20:00 | 2009-2-2 04:00 
2009-2-2 06:00 | 2009-2-2 14:00 

Lưu ý: toàn bộ điều này sẽ là tương đối dễ thực hiện bằng cách chỉ kéo tất cả các dữ liệu và chạy một số vòng tay vào nó, tuy nhiên đó là hệ thống hiện tại, và thay nó chậm vì số ca và số khoảng thời gian phải được kiểm tra.

+0

Phạm vi ngày không nên trong "Vì vậy, nếu tôi đã chỉ định phạm vi 2009-1-1 12:00 đến 2009-1-1 06:00" được đảo ngược thành "2009-1-1 06:00 đến 2009-1-1 12:00 "? – Sung

+2

@David Bạn có thể muốn tải xuống sách điện tử này: "Phát triển các ứng dụng cơ sở dữ liệu định hướng theo thời gian trong SQL" (http://www.cs.arizona.edu/people/rts/tdbbook.pdf). Nó có rất nhiều thông tin tốt về các truy vấn SQL phức tạp trên các bảng có phạm vi ngày. –

+0

+1 - Tôi đang làm việc trên một vấn đề tương tự, xác định các điểm tách rời và chồng chéo – spencer7593

Trả lời

2

Dưới đây là một cách để làm phẳng phạm vi ngày như thế này

Start   | End 
2009-1-1 06:00 | 2009-1-1 18:00 
2009-2-1 20:00 | 2009-2-2 04:00 
2009-2-2 06:00 | 2009-2-2 14:00 

Bạn cần phải so sánh trước ngày tiếp theo trong mỗi hàng và xem liệu

  • Current hàng của Bắt đầu ngày nằm giữa phạm vi ngày của hàng trước đó.
  • Hàng hiện tại của Kết thúc ngày nằm giữa phạm vi ngày của hàng tiếp theo.

alt text

Sử dụng mã ở trên, thực hiện UDF cũng đơn giản như sau.

create function fnThereIsWatchmenBetween(@from datetime, @to datetime) 
returns bit 
as 
begin 
    declare @_Result bit 

    declare @FlattenedDateRange table (
     Start datetime, 
     [End] datetime 
    ) 

    insert @FlattenedDateRange(Start, [End]) 
    select distinct 
      Start = 
       case 
        when Pv.Start is null then Curr.Start 
        when Curr.Start between Pv.Start and Pv.[End] then Pv.Start 
        else Curr.Start 
       end, 
      [End] = 
       case 
        when Curr.[End] between Nx.Start and Nx.[End] then Nx.[End] 
        else Curr.[End] 
       end 
    from shift Curr 
      left join shift Pv on Pv.ID = Curr.ID - 1 --; prev 
      left join shift Nx on Nx.ID = Curr.ID + 1 --; next 

    if exists( select 1 
       from FlattenedDateRange R 
       where @from between R.Start and R.[End] 
         and @to between R.Start and R.[End]) begin 
     set @_Result = 1 --; There is/are watchman/men during specified date range 
    end 
    else begin 
     set @_Result = 0 --; There is NO watchman 
    end 

    return @_Result 
end 
+0

thì khi nào, ví dụ, chúng ta có thay đổi: 12-2 và 4-6 và chúng ta kiểm tra 1-6. điều đó sẽ thất bại, vì 2-4 trống. – nlucaroni

+0

Mã đã được cập nhật. – Sung

+0

Rất đẹp.Trớ trêu thay, vì đây là một phần của một vấn đề lớn hơn, phức tạp hơn, tôi đã tìm ra cách làm toàn bộ nhanh hơn bằng cách tạo ra một loại bảng tra cứu được hỗ trợ bởi bộ kích hoạt, di chuyển phần lớn chi phí CPU đến khi các bản ghi được tạo ra (ít thường xuyên hơn khi chúng cần được đọc và phân tích). Tuy nhiên, tôi phải đối phó với những loại chồng chéo này ở đây, vì vậy điều này sẽ rất hữu ích sớm thôi. – David

1

Khoảng thời gian không được bảo vệ hiển nhiên bắt đầu vào cuối thời gian đã xem hoặc ở đầu toàn bộ phạm vi thời gian mà bạn đang kiểm tra. Vì vậy, bạn cần một truy vấn chọn tất cả các phần tử từ tập hợp này mà không có sự dịch chuyển chồng chéo. Truy vấn sẽ trông giống như:

select 1 
from shifts s1 where not exists 
    (select 1 from shifts s2 
    where s2.start<=s1.end and s2.end > s1.end 
    ) 
    and s1.end>=start_of_range and s1.end< end_of_range 
union 
select 1 
where not exists 
    (select 1 from shifts s2 
     where s2.start<=start_of_range and s2.end > start_of_range 
    ) 

Nếu điều này không trống, thì bạn có khoảng thời gian không có bảo vệ. Tôi nghi ngờ nó sẽ chạy trong thời gian bậc hai, vì vậy nó có thể chậm hơn "sắp xếp, lấy và vòng lặp".

+0

Tôi đang gặp sự cố khi thực hiện tác vụ này. Tôi giả định rằng bạn có ý định để một nơi nào đó lọc cho thời gian kết thúc là tốt, tuy nhiên thậm chí thêm rằng, hoặc chỉ đảm bảo rằng chỉ có thay đổi hợp lệ được thông qua (bằng cách thay đổi một lần xem), kết quả dường như không liên quan đến phạm vi cung cấp - giống nhau kết quả được đưa ra bất kể nếu phạm vi là bên trong hoặc bên ngoài sự thay đổi. – David

+0

Phải, truy vấn phụ hàng đầu hoàn toàn bỏ qua phạm vi - kiểm tra thêm –

0

Một cách là tạo bảng tạm thời với hàng cho mỗi giá trị thời gian cần được kiểm tra (là chức năng của độ phân giải thay đổi).

Nếu đó là phút, nó sẽ có 60 * 24 = 1440 hàng trong một ngày; khoảng 10 nghìn hàng trong một tuần.

Sau đó, SQL là tương đối đơn giản:

SELECT COUNT (1)
TỪ #minutes m
LEFT JOIN thay đổi s ON m.checktime GIỮA s.start_time VÀ s.end_time
HAVING COUNT địa chỉ (1) = 0

Điều này cũng có thể cho thấy có bao nhiêu ca được bao gồm trong cùng một thời điểm.

Thời gian thực hiện sẽ không đáng kể với các tỷ lệ bạn đã mô tả.

0

Tôi đang xem phạm vi ngày và nghĩ rằng tôi sẽ truy cập lại câu hỏi này. Tôi có thể rơi phẳng trên khuôn mặt của tôi ở đây, nhưng có vẻ như hai điều kiện này sẽ là đủ

(1) Shift is not at beginning of range and has no left neighbour 

OR 

(2) Shift is not at end of range and has no right neighbour. 

Đánh giá cao điều này có thể không hiệu quả nhất.

CREATE TABLE times 
(
TimeID int, 
StartTime Time, 
EndTime Time 
) 

INSERT INTO times 
VALUES 
(1,'10:00:00','11:00:00'), 
(2,'11:00:00','12:00:00'), 
(3,'13:00:00','14:00:00'), 
(4,'14:30:00','15:00:00'), 
(5,'15:00:00','16:00:00'), 
(6,'16:00:00','17:00:00') 

declare @start_of_range time ='09:30:00' 
declare @end_of_range time = '17:30:00' 



select timeID,StartTime,EndTime 
from times s1 where 
-- No left neighbour and not at beginning of range 
    not exists 
    (select 1 from times s2 
    where s2.startTime < s1.startTime and s2.endTime >= s1.startTime 
    ) 
    and s1.StartTime>@start_of_range 
    or 
-- No right neighbour and not at end of range 
    not exists 
    (select 1 from times s2 
    where s2.startTime <= s1.endTime and s2.endTime > s1.endTime 
    ) 
    and s1.EndTime<@end_of_range 

quả thiết

timeID StartTime EndTime 
1 10:00:00.0000000 11:00:00.0000000 
2 11:00:00.0000000 12:00:00.0000000 
3 13:00:00.0000000 14:00:00.0000000 
4 14:30:00.0000000 15:00:00.0000000 
6 16:00:00.0000000 17:00:00.0000000 

Trên thực tế nó chỉ cần thiết để kiểm tra hoặc những người hàng xóm bên phải hoặc những người hàng xóm bên trái, miễn là bạn đảm bảo rằng khi bắt đầu và kết thúc của loạt được chọn, vì vậy bạn có thể giới thiệu sự bắt đầu của loạt như một khoảng dummy và chỉ cần kiểm tra những người hàng xóm ngay như sau: -

select * from 
(
select timeID,StartTime,EndTime 
from times union select 0,@start_of_range,@start_of_range) s1 
where 
    not exists 
    (select 1 from times s2 
    where s2.startTime<=s1.endTime and s2.endTime > s1.endTime 
    ) 
    and s1.EndTime<@end_of_range 

quả thiết

timeID StartTime EndTime 
0 09:30:00.0000000 09:30:00.0000000 
2 11:00:00.0000000 12:00:00.0000000 
3 13:00:00.0000000 14:00:00.0000000 
6 16:00:00.0000000 17:00:00.0000000 
Các vấn đề liên quan