Сумма за интервал диапазона в случае SQL - PullRequest
0 голосов
/ 19 мая 2018

Я пытаюсь получить средние расходы для каждой даты после даты начала каждого клиента (это для целей анализа валютных частот, частоты и денежного анализа).Ниже приведен элемент денежного_значения, для которого я хочу получить сумму всех транзакций после начальной даты клиента, деленную на количество дней, в которые он приобрел.Я использую Oracle 12c.

У меня есть следующее, которое работает, но включает полный диапазон дат.

RFM AS (
SELECT SRC_USER_ID,
  COUNT(distinct PICKUP_DATE) -1 as frequency,
  (MAX(PICKUP_DATE) - MIN(PICKUP_DATE)) as recency,
  (TO_DATE ('2018/05/12', 'yyyy/mm/dd') - MIN(PICKUP_DATE)) as T,
  (CASE WHEN COUNT(distinct PICKUP_DATE)-1=0 THEN 0 ELSE
         SUM(PRICE_TOTAL)/COUNT(distinct PICKUP_DATE) END) AS monetary_value
FROM TRANSACTIONS
group by SRC_USER_ID

Я понимаю, что мне нужно использовать функцию агрегирования окон (https://ss64.com/ora/syntax-analytic-aggregate.html). Однако, когда я пытаюсь описать ниже, это не работает.

RFM AS (
SELECT SRC_USER_ID,
  COUNT(distinct PICKUP_DATE) -1 as frequency,
  (MAX(PICKUP_DATE) - MIN(PICKUP_DATE)) as recency,
  (TO_DATE ('2018/05/12', 'yyyy/mm/dd') - MIN(PICKUP_DATE)) as T,
  (CASE WHEN COUNT(distinct PICKUP_DATE)-1=0 THEN 0 ELSE
    SUM(PRICE_TOTAL) OVER (ORDER BY PICKUP_DATE) RANGE INTERVAL '1' DAY FOLLOWING UNBOUNDED/COUNT(distinct PICKUP_DATE) END) AS monetary_value
FROM TRANSACTIONS
group by SRC_USER_ID

Любая помощь будет принята с благодарностью.

1 Ответ

0 голосов
/ 19 мая 2018

При изучении аналитических функций, вероятно, будет хорошей идеей взглянуть на примеры в документации и oracle-base .Вот небольшая тестовая таблица с 3 столбцами, имена которых похожи на те, что указаны в вашем запросе.(Примечание. Даты и цены являются случайными значениями.)

create table transactions
as
select
  mod( level, 3 ) + 1 as srcuserid
, to_date( trunc( dbms_random.value( 2451925, 2458258 ) ), 'J' ) pickupdate
, round( dbms_random.value() * 10000, 2 ) pricetotal
from dual
connect by level <= 12 ;

select * from transactions order by srcuserid, pickupdate ;

SRCUSERID  PICKUPDATE  PRICETOTAL  
1          27-JUL-03   9447.05     
1          04-APR-05   9595.6      
1          28-SEP-07   408.09      
1          16-AUG-13   5643.33     
2          20-JAN-01   6253.87     
2          26-OCT-05   5981.7      
2          16-DEC-08   8138.03     
2          20-JUL-17   49.67       
3          08-AUG-03   7411.74     
3          29-OCT-06   2218.95     
3          11-FEB-10   111.07      
3          26-JUL-17   600.15  

12 rows selected. 

Для разработки запроса попробуйте использовать аналитические функции, которые рассчитывают значения для всех столбцов (при необходимости).Избегайте использования GROUP BY для этого, так как в этой ситуации возникнет ошибка «не выражение GROUP BY».Кроме того, вы обнаружите, что результирующий набор содержит строку для каждой строки в исходной таблице.Вы можете использовать DISTINCT здесь, так как мы имеем дело только с агрегатами.

select distinct -- without "distinct", you'll get a multiple identical rows "per window"
  srcuserid
, count( pickupdate ) over ( partition by srcuserid ) as frequency
, max( pickupdate ) over ( partition by srcuserid )   as max_date
, min( pickupdate ) over ( partition by srcuserid )   as min_date
, sum( pricetotal ) over ( partition by srcuserid )   as sum_pricetotal
from transactions 
-- group by srcuserid  -- ORA-00979: not a GROUP BY expression
;

SRCUSERID  FREQUENCY  MAX_DATE   MIN_DATE   SUM_PRICETOTAL  
2          4          20-JUL-17  20-JAN-01  20423.27        
3          4          26-JUL-17  08-AUG-03  10341.91        
1          4          16-AUG-13  27-JUL-03  25094.07 

Как только это (своего рода) сработает, используйте запрос в виде встроенного представления и добавьте некоторые завершающие штрихи к внешнему SELECT.Обратите внимание, что в заключительном запросе здесь также используется first_value (), что может быть для вас способом найти первую запись «окна» как бы.

select
  srcuserid
, count_ - 1          as frequency
, max_date - min_date as recency
, trunc( sysdate - min_date )  as T
, case
    when count_ - 1 = 0 then 0
    else round( ( sum_pricetotal - firstpricetotal ) / ( count_ - 1 ), 2 ) 
  end as monetary_value 
from (
  select distinct
    srcuserid
  , count( pickupdate ) over ( partition by srcuserid ) as count_
  , max( pickupdate ) over ( partition by srcuserid )   as max_date
  , min( pickupdate ) over ( partition by srcuserid )   as min_date
  , sum( pricetotal ) over ( partition by srcuserid )   as sum_pricetotal
-- first_value(): find the first ie oldest "pricetotal" for each client
  , first_value( pricetotal ) over ( 
      partition by srcuserid order by pickupdate )      as firstpricetotal
  from transactions
) 
;

-- result
SRCUSERID  FREQUENCY  RECENCY  T     MONETARY_VALUE  
2          3          6025     6328  4723.13         
3          3          5101     5398  976.72          
1          3          3673     5410  5215.67 

См. Также: dbfiddle здесь.

...