2013-02-25 44 views
11

Tôi cần tham gia bảng A và bảng B để tạo bảng C.Tham gia phạm vi ngày trùng lặp

Cờ trạng thái lưu trữ bảng A và bảng B cho ID. Các cờ trạng thái (A_Flag và B_Flag) có thể thay đổi theo thời gian, vì vậy một ID có thể chứa nhiều hàng, biểu thị lịch sử trạng thái của ID. Các cờ cho một ID cụ thể có thể thay đổi độc lập với nhau, có thể dẫn đến một hàng trong Bảng A thuộc nhiều hàng trong Bảng B và ngược lại.

Bảng kết quả (Bảng C) cần phải là danh sách phạm vi ngày duy nhất bao gồm mọi ngày trong vòng đời ID (01/01/2008-18/08/2008) và giá trị A_Flag và B_Flag cho mỗi phạm vi ngày .

Các bảng thực tế chứa hàng trăm ID với mỗi ID có số lượng hàng khác nhau trên mỗi bảng.

Tôi có quyền truy cập vào các công cụ SQL và SAS để đạt được kết quả cuối cùng.

Source - Table A 
ID Start   End  A_Flag 
1 01/01/2008 23/03/2008 1 
1 23/03/2008 15/06/2008 0 
1 15/06/2008 18/08/2008 1 

Source - Table B 
ID Start   End  B_Flag 
1 19/01/2008 17/02/2008 1 
1 17/02/2008 15/06/2008 0 
1 15/06/2008 18/08/2008 1 

Result - Table C 
ID Start   End A_Flag B_Flag 
1 01/01/2008 19/01/2008 1 0 
1 19/01/2008 17/02/2008 1 1 
1 17/02/2008 23/03/2008 1 0 
1 23/03/2008 15/06/2008 0 0 
1 15/06/2008 18/08/2008 1 1 
+0

Tôi không thể nghĩ ra cách để thực hiện điều này chỉ với SQL chuẩn và tôi không biết SAS. Nếu tôi biết mùi vị nào, tôi sẽ có thể viết một quy trình có thể hoạt động. – pahoughton

+0

Một số câu trả lời có giải pháp đúng và giống như một câu tôi đã sử dụng trong quá khứ: bạn cần phải xác định tất cả các ngày mà tại đó điều gì đó xảy ra và từ những xác định này trong phạm vi ngày này. Sau đó, tham gia từ tập hợp đầy đủ các phạm vi này trở lại các bảng ban đầu của bạn để xác định các thuộc tính hợp lệ cho phạm vi ngày cụ thể đó. Bạn không cần hàm LAG cho điều này, nhưng một biểu thức bảng chung (với mệnh đề) rất tiện dụng ở đây. –

Trả lời

3

tôi sẽ giải quyết việc này trong SQL, giả sử rằng bạn có một chức năng gọi là lag (SQL Server 2012, Oracle, Postgres, DB2). Bạn có thể nhận được hiệu ứng tương tự với truy vấn con tương quan.

Ý tưởng là nhận tất cả các khoảng thời gian khác nhau. Sau đó, tham gia trở lại các bảng ban đầu để có được những lá cờ.

Tôi gặp sự cố khi tải lên mã nhưng có thể tận dụng tối đa mã. Tuy nhiên, nó bắt đầu với kết thúc bắt đầu, mà bạn tạo ra bằng cách thực hiện một số union (không phải union all) trong bốn ngày trong một cột: chọn a.bắt đầu làm giá trị. Điều này sau đó được kết hợp với a.end, b.start và b.end.

with driver as (
    select thedate as start, lag(thedate) over (order by thedate) as end 
    from startends 
    )  

select startdate, enddate, a.flag, b.flag 
from driver left outer join 
    a 
    on a.start >= driver.start and a.end <= driver.end left outer join 
    b 
    on b.start >= driver.start and b.end <= driver.end 
+0

Gordon, cảm ơn vì điều này. Tôi chỉ có quyền truy cập vào cú pháp SQL qua PROC SQL (một thủ tục trong SAS), do đó, một truy vấn con tương quan là có thể nhưng không phải là hàm lag. Làm thế nào con ong này có thể thực hiện bằng cách sử dụng truy vấn phụ tương quan? – geebees

+0

Nếu bạn có một cơ sở dữ liệu cơ bản như Oracle (hỗ trợ trễ), bạn có thể thiết lập chế độ chuyển tiếp và đưa vào cú pháp Oracle. Đây có phải là một khả năng? –

+0

SAS không có chức năng trễ, tôi nghĩ rằng tôi sẽ phải thử nghiệm với nó để đạt được kết quả mong muốn của tôi – geebees

0

Một giải pháp SAS có thể thực hiện điều này là thực hiện tham gia một phần và sau đó tạo các hàng bổ sung cần thiết trong bước dữ liệu. Điều này sẽ làm việc giả sử tableA có tất cả các hồ sơ có thể; nếu đó không phải là trường hợp (nếu tableB có thể bắt đầu trước khi tableA), một số logic bổ sung có thể cần thiết để xem xét khả năng đó (nếu first.id và bắt đầu gt b_start). Cũng có thể có thêm logic cần thiết cho các vấn đề không có trong dữ liệu ví dụ - Tôi không có nhiều thời gian sáng nay và không gỡ lỗi cho bất kỳ thứ gì ngoài các trường hợp dữ liệu mẫu, nhưng khái niệm phải hiển nhiên.

data tableA; 
informat start end DDMMYY10.; 
format start end DATE9.; 
input ID Start   End  A_Flag; 
datalines; 
1 01/01/2008 23/03/2008 1 
1 23/03/2008 15/06/2008 0 
1 15/06/2008 18/08/2008 1 
;;;; 
run; 

data tableB; 
informat start end DDMMYY10.; 
format start end DATE9.; 
input ID Start   End  B_Flag; 
datalines; 
1 19/01/2008 17/02/2008 1 
1 17/02/2008 15/06/2008 0 
1 15/06/2008 18/08/2008 1 
;;;; 
run; 


proc sql; 
create table c_temp as 
    select * from tableA A 
     left join (select id, start as b_start, end as b_end, b_flag from tableB) B 
    on A.Id = B.id 
    where (A.start le B.b_start and A.end gt B.b_start) or (A.start lt B.b_end and A.end ge B.b_end) 
    order by A.ID, A.start, B.b_start; 
quit; 

data tableC; 
set c_temp; 
by id start; 
retain b_flag_ret; 
format start_fin end_fin DATE9.; 
if first.id then b_flag_ret=0; 
do until (start=end); 
    if (start lt b_start) and first.start then do; 
     start_fin=start; 
     end_fin=b_start; 
     a_flag_fin=a_flag; 
     b_flag_fin=b_flag_ret; 
     output; 
     start=b_start; 
    end;  
    else do; *start=b_start; 
      start_fin=ifn(start ge b_start, start, b_start); 
      end_fin = ifn(b_end le end, b_end, end); 
      a_flag_fin=a_flag; 
      b_flag_fin=b_flag; 
      output; 
      start=end; *leave the loop as there will be a later row that matches; 
    end; 
end; 
run; 
0

Loại xử lý tuần tự này có thay đổi và bù trừ là một trong những tình huống mà bước DATA SAS chiếu sáng. Không phải là câu trả lời này là đơn giản, nhưng nó là đơn giản hơn hơn bằng cách sử dụng SQL, có thể được thực hiện, nhưng không được thiết kế với quy trình xử lý tuần tự này.

Hơn nữa, các giải pháp dựa trên bước DATA có xu hướng rất hiệu quả. Điều này chạy theo thời gian O (n log n) trong lý thuyết, nhưng gần với O (n) trong thực tế, và trong không gian liên tục.

Hai bước DATA đầu tiên chỉ tải dữ liệu, được sửa đổi một chút từ câu trả lời của Joe, để có nhiều ID (nếu không cú pháp là MUCH dễ dàng hơn) và thêm một số trường hợp góc, nghĩa là ID không thể xác định trạng thái ban đầu.

data tableA; 
informat start end DDMMYY10.; 
format start end DATE9.; 
input ID Start   End  A_Flag; 
datalines; 
1 01/01/2008 23/03/2008 1 
2 23/03/2008 15/06/2008 0 
2 15/06/2008 18/08/2008 1 
;;;; 
run; 

data tableB; 
informat start end DDMMYY10.; 
format start end DATE9.; 
input ID Start   End  B_Flag; 
datalines; 
1 19/01/2008 17/02/2008 1 
2 17/02/2008 15/06/2008 0 
4 15/06/2008 18/08/2008 1 
;;;; 
run; 

Bước dữ liệu tiếp theo tìm thấy sửa đổi đầu tiên cho mỗi id và cờ và đặt giá trị ban đầu ngược lại với id và cờ.

/* Get initial state by inverting first change */ 
data firstA; 
    set tableA; 
    by id; 
    if first.id; 
    A_Flag = ~A_Flag; 
run; 

data firstB; 
    set tableB; 
    by id; 
    if first.id; 
    B_Flag = ~B_Flag; 
run; 
data first; 
    merge firstA firstB; 
    by id; 
run; 

Bước dữ liệu tiếp theo kết hợp bảng "đầu tiên" nhân tạo với hai cột còn lại, giữ trạng thái cuối cùng đã biết và loại bỏ hàng đầu tiên nhân tạo.

data tableAB (drop=lastA lastB); 
    set first tableA tableB; 
    by id start; 
    retain lastA lastB lastStart; 
    if A_flag = . and ~first.id then A_flag = lastA; 
    else lastA = A_flag; 
    if B_flag = . and ~first.id then B_flag = lastB; 
    else lastB = B_flag; 
    if ~first.id; /* drop artificial first row per id */ 
run; 

Các bước trên thực hiện hầu hết mọi thứ. Lỗi duy nhất là ngày kết thúc sẽ sai, vì chúng được sao chép từ hàng gốc. Để khắc phục điều đó, hãy sao chép lần bắt đầu tiếp theo vào cuối mỗi hàng, trừ khi đó là hàng cuối cùng. Cách dễ nhất là sắp xếp từng id bằng cách bắt đầu ngược lại, xem lại một bản ghi, sau đó sắp xếp tăng dần ở cuối.

/* sort descending to ... */ 
proc sort data=tableAB; 
    by id descending start; 
run; 
/* ... copy next start to this row's "end" field if not final */ 
data tableAB(drop=nextStart); 
    set tableAB; 
    by id descending start; 
    nextStart=lag(start); 
    if ~first.id then end=nextStart; 
run; 

proc sort data=tableAB; 
    by id start; 
run; 
3

Sự cố bạn đặt ra có thể được giải quyết trong một câu lệnh SQL không có phần mở rộng không chuẩn.

Điều quan trọng nhất cần lưu ý là ngày trong cặp bắt đầu từng đại diện cho điểm bắt đầu hoặc kết thúc tiềm năng của một khoảng thời gian trong đó cặp cờ sẽ là đúng. Nó thực sự không quan trọng rằng một ngày là một "bắt đầu" và khác và "kết thúc"; bất kỳ ngày nào là dấu phân cách thời gian thực hiện cả hai: nó kết thúc một khoảng thời gian trước và bắt đầu một khoảng thời gian khác. Xây dựng một tập hợp các khoảng thời gian tối thiểu, và nối chúng với các bảng để tìm các cờ thu được trong mỗi khoảng thời gian.

Tôi đã thêm ví dụ của bạn (và một giải pháp) vào trang Canonical SQL của tôi. Xem ở đó để có một cuộc thảo luận chi tiết. Trong sự công bằng cho SO, đây là truy vấn chính nó

with D (ID, bound) as (
    select ID 
     , case T when 's' then StartDate else EndDate end as bound 
    from (
    select ID, StartDate, EndDate from so.A 
    UNION 
    select ID, StartDate, EndDate from so.B 
    ) as U 
    cross join (select 's' as T union select 'e') as T 
) 
select P.*, a.Flag as A_Flag, b.Flag as B_Flag 
from (
    select s.ID, s.bound as StartDate, min(e.bound) as EndDate 
    from D as s join D as e 
    on s.ID = e.ID 
    and s.bound < e.bound 
    group by s.ID, s.bound 
) as P 
left join so.A as a 
on P.ID = a.ID 
and a.StartDate <= P.StartDate and P.EndDate <= a.EndDate 
left join so.B as b 
on P.ID = b.ID 
and b.StartDate <= P.StartDate and P.EndDate <= b.EndDate 
order by P.ID, P.StartDate, P.EndDate 
Các vấn đề liên quan