2009-06-12 40 views
6

Tôi cần phải viết một báo cáo tạo tổng số tóm tắt dựa vào bảng có phạm vi ngày cho mỗi bản ghi.Cách lặp qua một phạm vi ngày trong PL/SQL

table data: 
option start_date end_date 
opt1  6/12/2009 6/19/2009 
opt1  6/3/2009  6/13/2009 
opt2  6/5/2009  6/6/2009 

Những gì tôi muốn ra về cơ bản là thế này:

date  option count 
6/1/2009 opt1  0 
6/1/2009 opt2  0 
6/2/2009 opt1  0 
6/2/2009 opt2  0 
6/3/2009 opt1  0 
6/3/2009 opt2  1 

Tôi gặp một thời gian khó khăn để tìm hiểu làm thế nào để lặp qua một phạm vi ngày. Tôi chắc chắn đây là một số con trỏ đơn giản có thể được tạo ra cho điều này nhưng tôi thua lỗ. Tốt nhất trong PL/SQL

UPDATE:

tôi đã kết thúc bằng cách sử dụng ví dụ here để hoàn thành những gì tôi muốn làm. Điều này tạo ra một hàm tạo ra một bảng ngày tháng.

Trả lời

14

Bạn sẽ cần một số loại lịch để lặp qua một phạm vi ngày. Tôi đã xây dựng một cái bằng cách sử dụng thủ thuật connect by level. Sau đó bạn có thể tham gia vào lịch với dữ liệu của bạn (cross tham gia kể từ khi bạn muốn có một hàng ngay cả khi không có tùy chọn cho ngày hôm đó):

SQL> WITH calendar AS (
    2  SELECT to_date(:begin_date, 'mm/dd/yyyy') + ROWNUM - 1 c_date 
    3  FROM dual 
    4  CONNECT BY LEVEL <= to_date(:end_date, 'mm/dd/yyyy') 
          - to_date(:begin_date, 'mm/dd/yyyy') + 1 
    5 ) 
    6 SELECT c_date "date", d_option "option", COUNT(one_day) 
    7 FROM (SELECT c.c_date, d.d_option, 
    8     CASE 
    9      WHEN c.c_date BETWEEN d.start_date AND d.end_date THEN 
10      1 
11     END one_day 
12    FROM DATA d, calendar c) 
13 GROUP BY c_date, d_option 
14 ORDER BY 1,2; 

date  option COUNT(ONE_DAY) 
----------- ------ -------------- 
01/06/2009 opt1    0 
01/06/2009 opt2    0 
02/06/2009 opt1    0 
02/06/2009 opt2    0 
03/06/2009 opt1    1 
03/06/2009 opt2    0 
04/06/2009 opt1    1 
04/06/2009 opt2    0 
05/06/2009 opt1    1 
05/06/2009 opt2    1 
06/06/2009 opt1    1 
06/06/2009 opt2    1 

12 rows selected 
+0

Điều này đã làm chính xác những gì tôi muốn ... tốt hơn cả bài viết tôi tham khảo ở trên. Cảm ơn! –

+0

+1 - giải pháp của bạn hiệu quả hơn tôi dưới đây với bước bổ sung để tạo bảng cơ sở bên trái. Bạn không chắc chắn nó sẽ như thế nào trong trường hợp bảng được lập chỉ mục. –

0

Đây là loại truy vấn được xử lý tốt nhất nếu bạn có một "tiện ích" thứ hai mà bạn có thể sử dụng cho bất kỳ truy vấn nào mà bạn cần phải chuyển đổi phạm vi thành các nhóm cụ thể. Bảng tiện ích không có gì khác ngoài danh sách các số:

CREATE TABLE Iterator (Counter NUMBER); 

COUNTER 
------- 
     0 
     1 
     2 
     3 
... 
    100 (or however many rows you want to include) 

NẾU chúng tôi cho rằng bạn muốn hiển thị 30 ngày, ví dụ:

SELECT TO_DATE('6/1/2009', 'MM/DD/YYYY') + i.counter thedate 
     , i.My_option 
     , count(y.My_option) 
    FROM (SELECT DISTINCT 
        i2.Counter 
       , y.My_option 
      FROM iterator i2 
       , YourTable y 
      WHERE i2.Counter < 5 
     ) i 
      LEFT OUTER JOIN yourtable y 
          ON TO_DATE('6/1/2009', 'MM/DD/YYYY') + i.counter 
           >= y.start_date 
          AND TO_DATE('6/1/2009', 'MM/DD/YYYY') + i.counter 
           < y.end_date 
          AND y.My_option = i.My_option 
GROUP BY TO_DATE('6/1/2009', 'MM/DD/YYYY') + i.counter 
     , i.My_option 
ORDER BY 1 
     , 2; 

Ý tưởng là bạn tạo sản phẩm Descartes giữa bảng lặp và bảng với dải ô, sau đó lọc ra tất cả các trường hợp không đáp ứng điều kiện phạm vi của bạn. Bạn có thể sử dụng nó ở nhiều nơi, và là một trong những ví dụ tốt nhất tại sao tốt hơn là mô hình dữ liệu của bạn với các phạm vi thay vì khoảng thời gian rời rạc - bởi vì bạn luôn có thể chuyển đổi dễ dàng thành các khoảng rời rạc bằng kỹ thuật này.

chỉnh sửa: Tôi thực sự không nên sử dụng cho các truy vấn GIỮA phạm vi ngày - Tôi đã thay đổi nó để> = <

4

Cũng giống như một sự bổ sung cho các kỹ thuật khác, một trong những cách tôi lặp qua ngày như sau:

/* List of days for the past year, starting with today at midnight */ 
SELECT TRUNC(SYSDATE) + 1 - LEVEL AS today, 
     TRUNC(SYSDATE) + 2 - LEVEL AS tomorrow 
FROM DUAL 
CONNECT BY LEVEL <= 365 
8

Một giải pháp mà tôi sử dụng cho việc này là chuyển đổi phạm vi ngày thành phạm vi số nguyên mà bạn có thể sử dụng trong vòng lặp for, sau đó chuyển đổi lại thành ngày để thực hiện. Bạn không thể làm bất kỳ tham gia hoặc bất cứ điều gì theo cách này, nhưng đó là một giải pháp nhỏ hơn nhiều mà những người đã đăng:

declare 
    start_date number; 
    end_date number; 
    business_date varchar2(8); 
begin 
    start_date := to_number(to_char(to_date('2013-04-25', 'yyyy-MM-dd'), 'j')); 
    end_date := to_number(to_char(to_date('2013-05-31', 'yyyy-MM-dd'), 'j')); 
    for cur_r in start_date..end_date loop 
    business_date := to_char(to_date(cur_r, 'j'), 'yyyy-MM-dd'); 
    dbms_output.put_line(business_date); 
    end loop; 
end; 
+0

hoạt động tốt :) Lưu ý nó phải là: business_date varchar2 (10); thay vì varchar2 (8); – HeyMan

3

Dưới đây là một câu trả lời dựa trên một câu trả lời ở trên: Nó sử dụng một sự khởi đầu và ngày kết thúc:

Danh sách tất cả các ngày từ ngày 7 tháng 1 năm 2013 đến ngày 31 tháng 7 năm 2013. Dễ dàng thích ứng với bất kỳ phạm vi ngày nào.

SELECT to_date('07/01/2013', 'mm/dd/yyyy') + LEVEL - 1 AS today 
FROM dual 
CONNECT BY LEVEL <= to_date('07/31/2013', 'mm/dd/yyyy') - to_date('07/01/2013', 'mm/dd/yyyy') + 1; 
Các vấn đề liên quan