2009-06-12 13 views
6

Potrzebuję napisać raport, który generuje zbiorcze sumy w stosunku do tabeli z zakresami dat dla każdego rekordu.Jak iterować w zakresie dat w 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 

Co chcę się jest w zasadzie tak:

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 

Mam twardy czas na zastanawianie się, w jaki sposób iteracyjne nad zakres dat. Jestem pewien, że jest to jakiś prosty kursor, który można by stworzyć, ale jestem w błędzie. Korzystnie w PL/SQL

UPDATE:

skończyło się na przykładzie here aby osiągnąć to, co chciałem zrobić. Tworzy to funkcję, która generuje tabelę dat.

Odpowiedz

14

Będziesz potrzebować jakiegoś kalendarza, aby przejrzeć zakres dat. Zbudowałem go przy pomocy sztuczki connect by level. Następnie można przystąpić do kalendarza z danymi (cross dołączyć ponieważ chcesz wiersz, nawet gdy nie ma opcji na dany dzień):

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

To zrobiło dokładnie to, co chciałem ... lepiej nawet niż artykuł, o którym wspomniałem powyżej. Dzięki! –

+0

+1 - twoje rozwiązanie jest bardziej wydajne niż moje poniżej, z dodatkowym krokiem do utworzenia tabeli z połączeniem z lewej strony. Nie wiem, jak by to było w przypadku indeksowania tabeli. –

0

Ten typ kwerendy jest najlepiej obsługiwany jeśli masz drugą „narzędzia” tabela, której można użyć do niemal każdego zapytania, w którym należy przekształcić zakresy w konkretne segmenty. Tabela narzędzi nie jest niczym więcej niż listą liczb:

CREATE TABLE Iterator (Counter NUMBER); 

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

JEŚLI założymy, że chcesz wyświetlić 30 dni, np.

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; 

Chodzi o to, że tworzysz produkt kartezjański między tabeli iteratora i stolika z zakresu, a następnie odfiltrować wszystkie przypadki, gdzie warunki nie są spełnione range. Możesz go używać w wielu miejscach i jest to jeden z najlepszych przykładów, dla których lepiej jest modelować dane za pomocą zakresów w przeciwieństwie do dyskretnych interwałów - ponieważ zawsze możesz z łatwością przekształcić je w dyskretne interwały za pomocą tej techniki.

edit: I naprawdę nie powinno się używać MIĘDZY dla zakresu dat zapytań - Zmieniłem go do> = <

4

tylko jako dodatek do innych technik, jednym ze sposobów I iteracyjne nad datami jest następujący:

/* 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

Jednym z rozwiązań, których używam jest przekształcenie zakresu dat w zakres liczb całkowitych, który można wykorzystać w pętli for, a następnie konwersję z powrotem na datę, aby zrobić z nim coś. Nie można wykonać dowolną dołącza lub coś w ten sposób, ale jest to o wiele mniejsze rozwiązanie tych już pisał:

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

działa dobrze :) Uwaga: powinno to być: business_date varchar2 (10); zamiast varchar2 (8); – HeyMan

3

Oto odpowiedź na podstawie odpowiedzi powyżej: Wykorzystuje datę rozpoczęcia i zakończenia:

Wymienia wszystkie dni od dnia 07.01.2013 do 31.03.2013. Łatwo dostosować do dowolnego zakresu dat.

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; 
Powiązane problemy