Интересная задача для аналитических запросов Oracle

У меня довольно большой опыт работы с аналитическими функциями Oracle, но этот меня поставил в тупик. Я пну себя, если будет очевидное решение :)

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

Таблица, для которой предназначен этот журнал, называется BOND_PAYMENTS и представляет собой связи между PAYMENTS и BONDS; в нем хранится сумма денег (AMOUNT), которая была выделена для конкретной облигации (обозначенной BOND_NUMBER) из определенного платежа (обозначенного PAYMENT_ID). Кроме того, он записывает, какой аспект облигации он был назначен (BOP_DOMAIN), который может быть «BON», «PET» или каким-либо другим кодом. Таблица BOND_PAYMENTS имеет суррогатный ключ (BOP_ID).

Следовательно, моя таблица журнала обычно будет иметь 1 или несколько записей для каждого BOP_ID - сначала INSert, за которым, возможно, следуют некоторые UPDates, а затем, возможно, DELete.

Вот таблица ЖУРНАЛА:

CREATE TABLE JOURNAL
( JN_DATE_TIME  DATE         NOT NULL,
  JN_OPERATION  VARCHAR2(3)  NOT NULL,
  BOP_ID        NUMBER(9)    NOT NULL,
  PAYMENT_ID    NUMBER(9)    NOT NULL,
  BOND_NUMBER   VARCHAR2(20) NOT NULL,
  BOP_DOMAIN    VARCHAR2(10) NOT NULL,
  AMOUNT        NUMBER(14,2) NOT NULL
);

Вот несколько примеров данных:

INSERT INTO JOURNAL VALUES (TO_DATE('01/01/2010','DD/MM/YYYY'),'INS',1242043,1003700,'9995/10','BON',1800);
INSERT INTO JOURNAL VALUES (TO_DATE('03/01/2010','DD/MM/YYYY'),'INS',1242046,1003700,'9998/10','BON',1700);
INSERT INTO JOURNAL VALUES (TO_DATE('04/01/2010','DD/MM/YYYY'),'INS',1242048,1003700,'9999/10','BON',1800);
INSERT INTO JOURNAL VALUES (TO_DATE('05/01/2010','DD/MM/YYYY'),'INS',1242052,1003700,'10003/10','BON',1600);
INSERT INTO JOURNAL VALUES (TO_DATE('08/01/2010','DD/MM/YYYY'),'INS',1242058,1003700,'9998/10','BON',100);
INSERT INTO JOURNAL VALUES (TO_DATE('09/01/2010','DD/MM/YYYY'),'UPD',1242058,1003700,'9998/10','PET',100);
INSERT INTO JOURNAL VALUES (TO_DATE('01/01/2010','DD/MM/YYYY'),'INS',2242043,1003701,'8995/10','BON',1800);
INSERT INTO JOURNAL VALUES (TO_DATE('02/01/2010','DD/MM/YYYY'),'INS',2242046,1003701,'8998/10','BON',1700);
INSERT INTO JOURNAL VALUES (TO_DATE('03/01/2010','DD/MM/YYYY'),'INS',2242048,1003701,'8999/10','BON',1800);
INSERT INTO JOURNAL VALUES (TO_DATE('04/01/2010','DD/MM/YYYY'),'INS',2242058,1003701,'8998/10','BON',100);
INSERT INTO JOURNAL VALUES (TO_DATE('05/01/2010','DD/MM/YYYY'),'UPD',2242046,1003701,'8998/10','BON',1500);
INSERT INTO JOURNAL VALUES (TO_DATE('06/01/2010','DD/MM/YYYY'),'INS',2242052,1003701,'9003/10','BON',1600);
INSERT INTO JOURNAL VALUES (TO_DATE('07/01/2010','DD/MM/YYYY'),'UPD',2242058,1003701,'8998/10','PET',200);

Теперь мне нужно извлечь полный набор данных из этой таблицы журнала, но в несколько другом формате. Основное требование - мы не хотим, чтобы таблица журнала больше записывала BOP_DOMAIN - это просто не требуется.

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

Я не могу просто взять SUM(amount) over(partition by payment_id, bond_number), потому что отдельный BOP_ID может обновляться несколько раз; поэтому в любой момент времени должна использоваться только последняя сумма, записанная для этого BOP_ID.

Учитывая приведенные выше образцы данных, вот иллюстрация того, что я ожидал бы получить:

SELECT jn_date_time,
       jn_operation,
       bop_id,
       payment_id,
       bond_number,
       bop_domain,
       amount,
       ? as running_total
FROM   JOURNAL
ORDER BY jn_date_time;

образец данных и ожидаемые результаты

Здесь я воспроизвел слева образцы данных для двух образцов платежей. Справа у меня есть «Промежуточный итог», который является ожидаемым результатом. Рядом с ним (красным) показана логика расчета промежуточной суммы для каждой строки.

«Промежуточный итог» - это моментальный снимок на момент записи журнала общей суммы для этой комбинации PAYMENT_ID и BOND_NUMBER. Помните, что конкретный BOP_ID может обновляться несколько раз; общая сумма должна учитывать только самую последнюю запись для этого BOP_ID.

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


person Jeffrey Kemp    schedule 02.12.2011    source источник


Ответы (2)


Попробуй это

WITH inner AS  
  (SELECT jn_date_time,
     jn_operation,
     bop_id,
     payment_id,
     bond_number,
     bop_domain,
     amount,
     amount - coalesce(lag(amount) over (partition by bop_id order by jn_date_time), 0)      
        as delta_bop_amount
  FROM   JOURNAL)
SELECT inner.*, 
sum(delta_bop_amount) 
  over (partition by payment_id, bond_number order by jn_date_time)  as running_total
FROM inner
ORDER BY bond_number, payment_id

Это вернет тот же ответ для ваших примеров.

Вам нужно два прохода - аналитическая функция во внутреннем запросе определяет, насколько каждая запись изменяет итоговое значение для каждого BOP_ID. INS - это прямое добавление, UPD должен вычесть самое последнее значение и добавить новое.

Затем при втором проходе выполняется подсчет суммы залога / платежа.

Я предполагаю, что вы хотели рассматривать облигацию / платеж как естественный ключ для текущей суммы и что для любой комбинации облигация / платеж может быть несколько BOP_ID.

person wrschneider    schedule 02.12.2011
comment
Очень красиво :) Я вижу, что ты там делаешь. Сначала вы рассчитываете, насколько изменилась сумма по сравнению с предыдущей соответствующей записью (через LAG), а затем просто рассчитываете текущую сумму по дельтам. - person Jeffrey Kemp; 02.12.2011

SELECT a.*,   
lag(amount,1) over (PARTITION BY bond_number ORDER BY 
payment_id,jn_date_time)recent_amount,   
amount + nvl(lag(amount,1) over (PARTITION BY bond_number ORDER BY  
payment_id,jn_date_time),0) running_total  
FROM JOURNAL a  
ORDER BY payment_id,jn_date_time  

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

Я только что использовал аналитическую функцию запаздывания, чтобы получить самое последнее значение суммы на комбинацию bond_number / payment_id, а затем добавил это последнее значение суммы к сумме, чтобы получить текущую сумму ... ПРОСТО !!!

person Allen James    schedule 04.09.2015
comment
Хорошая попытка, но он неверно сообщает 200 долларов для bop_id = 1242058 для облигации 9998/10 - должно отображаться 1800 долларов. Причина в том, что ваша промежуточная сумма просто добавляет сумму из предыдущей строки, отсортированную по payment_id / jn_date_time, тогда как она должна учитывать ВСЕ изменения сумм для этого платежа за историю. Взгляните на ответ @ wrschneider, который дает правильные результаты. - person Jeffrey Kemp; 07.09.2015