Что не так с этим промежуточным итогом (накопительным столбцом) в Oracle, выбранном с помощью оконной функции? - PullRequest
2 голосов
/ 23 мая 2019

У меня есть запрос, содержащий 0 или 1 в столбце.В демонстрационных целях я заменил 1 на 77, чтобы его было легче увидеть:

select dates.d the_date
, case TO_CHAR(dates.d, 'd') when '7' then 0 when '1' then 0 
    else 77 
  end as is_workday
from (SELECT (to_date('01.01.2019','dd.mm.yyyy') + (LEVEL -1)) 
      AS d FROM DUAL connect by level <=( 
      to_date('31.12.2020','dd.mm.yyyy')-(to_date('01.01.2019','dd.mm.yyyy')))
     ) as dates

Результатом является просто последовательный столбец даты и маркер, если эта дата является рабочим или нет.(в реальной жизни я также делаю расчет выходных, но это не проблема):

| THE_DATE            | IS_WORKDAY |
| 2019-01-01 00:00:00 | 77         |
| 2019-01-02 00:00:00 | 77         |
| 2019-01-03 00:00:00 | 77         |
| 2019-01-04 00:00:00 | 77         |
| 2019-01-05 00:00:00 |  0         |
| 2019-01-06 00:00:00 |  0         |
| 2019-01-07 00:00:00 | 77         |
| 2019-01-08 00:00:00 | 77         |
| 2019-01-09 00:00:00 | 77         |
| 2019-01-10 00:00:00 | 77         |
....

Я хочу добавить промежуточную сумму к is_workday, что означает совокупное значение.Я уверен, что оконные функции Oracles созданы для этого.

SELECT x.the_date
, x.is_workday
, sum(x.is_workday) over (
    partition by x.the_date          -- define the window
    order by x.the_date asc          -- order inside window
    rows between unbounded preceding -- sum to top
    and current row                  -- sum ending here
  ) as workdays_cumul
FROM (
  select dates.d the_date
  , case TO_CHAR(dates.d, 'd') when '7' then 0 when '1' then 0 
      else 77 
    end as is_workday
  from (SELECT (to_date('01.01.2019','dd.mm.yyyy') + (LEVEL -1)) 
        AS d FROM DUAL connect by level <=( 
        to_date('31.12.2020','dd.mm.yyyy')-(to_date('01.01.2019','dd.mm.yyyy')))
       ) as dates
) x
order by x.the_date
;

Но я должен кое-что здесь упустить, потому что я не получаю промежуточную сумму, а просто само значение.

| THE_DATE            | IS_WORKDAY | WORKDAYS_CUMUL |
| 2019-01-01 00:00:00 |  77        |  77            |
| 2019-01-02 00:00:00 |  77        |  77            |
| 2019-01-03 00:00:00 |  77        |  77            |
| 2019-01-04 00:00:00 |  77        |  77            |
| 2019-01-05 00:00:00 |   0        |   0            |
| 2019-01-06 00:00:00 |   0        |   0            |
| 2019-01-07 00:00:00 |  77        |  77            |
| 2019-01-08 00:00:00 |  77        |  77            |
....

Очевидно, это должно быть:

| THE_DATE            | IS_WORKDAY | WORKDAYS_CUMUL |
| 2019-01-01 00:00:00 |  77        |   77           |
| 2019-01-02 00:00:00 |  77        |  154           |
| 2019-01-03 00:00:00 |  77        |  231           |
...

Я думал, что это будет примерно так:

  • sum(x.is_workday) - сделать сумму по 77 -значениям
  • partition by x.the_date - сделать окна / секции / детали по одной строке в каждом (в моем случае)
  • order by x.the_date asc - упорядочить эти строки по дате
  • rows between unbounded preceding - сумма между самой первой строкой...
  • and current row - ... и текущая строка.

Чего мне здесь не хватает?

1 Ответ

2 голосов
/ 23 мая 2019

Удалите предложение PARTITION BY вашего запроса, так как область, в которой вы хотите создать окно - это весь запрос, а не каждая отдельная дата:

SELECT the_date
,      is_workday
,      sum(is_workday) over (
         ORDER BY the_date asc            -- order inside window
         ROWS BETWEEN unbounded preceding -- sum to top
                  AND current row         -- sum ending here
       ) as workdays_cumul
FROM (
  select d the_date
  ,      CASE
         WHEN d - TRUNC( d, 'IW' ) IN ( 5, 6 )
         THEN 0
         ELSE 1 
         END AS is_workday
  FROM (
    SELECT DATE '2019-01-01' + LEVEL -1 AS d
    FROM   DUAL
    connect by level <= DATE '2020-12-31' - DATE '2019-01-01'
  )
)
order by the_date;

Вы также можете использовать литералы даты, необходимо удалитьAS в псевдониме таблицы (и фактически не нуждаются в псевдонимах таблицы) и может использовать разницу между датой и началом недели ISO (d - TRUNC( d, 'IW' )) в качестве метода нахождения субботы и воскресенья, которые не зависят отNLS_TERRITORY параметр сеанса.

Вы также можете удалить предложение ROWS BETWEEN, так как ROWS BETWEEN unbounded preceding AND current row является поведением по умолчанию.Однако, если это именно то поведение, которое вам требуется, вы можете просто оставить его, чтобы продемонстрировать, что это окно - ваш ожидаемый результат.

Вывод :

THE_DATE  | IS_WORKDAY | WORKDAYS_CUMUL
:-------- | ---------: | -------------:
01-JAN-19 |          1 |              1
02-JAN-19 |          1 |              2
03-JAN-19 |          1 |              3
04-JAN-19 |          1 |              4
05-JAN-19 |          0 |              4
06-JAN-19 |          0 |              4
07-JAN-19 |          1 |              5
08-JAN-19 |          1 |              6
09-JAN-19 |          1 |              7
...
24-DEC-20 |          1 |            518
25-DEC-20 |          1 |            519
26-DEC-20 |          0 |            519
27-DEC-20 |          0 |            519
28-DEC-20 |          1 |            520
29-DEC-20 |          1 |            521
30-DEC-20 |          1 |            522

дБ <> скрипка здесь

...