У меня есть таблица предметов (инвентарь) и таблица предыдущих состояний инвентаря (истории). Я хочу вычислить сумму (суммы) определенного запаса (инвентаризационный код 'A' и 'B') для указанного диапазона дат, а также его изменения (дифференциалы).
Мне удалось сделать это в процедуре Oracle PL / SQL (а также реплицировать ее в SQLfiddle , абстрагируя многие вещи), но это удивительно дорогой запрос, занимающий около 20 минут. 30 дней, 60000 инвентарных записей и 120000 исторических записей.
Из одного взгляда на запрос я могу сказать, что это дорого - первый цикл - дни, второй цикл - весь инвентарь, а третий цикл - вся история для этого конкретного инвентаря.
Как я могу сделать этот запрос быстрее? Я предполагаю использовать агрегатные функции, чтобы уменьшить количество циклов, полагаясь на умные алгоритмы SQL Oracle - но я не совсем уверен, как сделать все это в одном большом запросе - или, если это вообще возможно.
Я могу добавить новые поля, которые могут улучшить эту таблицу / ускорить вычисления, но я не могу удалить какие-либо поля.
Заранее спасибо и, пожалуйста, дайте мне знать, как я могу уточнить или улучшить мой вопрос. Я новичок в PL / SQL, поэтому дайте мне знать, если я сделал какие-либо ошибки новичка.
Код:
procedure do
is
start_date number;
end_date number;
start_date_date date;
end_date_date date;
currentDate varchar2(10);
-- assoc array of itemcode[itemid]
type code_id_table_type is table of varchar2(10) index by PLS_INTEGER;
code_id_table code_id_table_type;
elem varchar2(10) default ' ';
-- positive
dateA number default 0;
dateB number default 0;
-- negative
dateAn number default 0;
dateBn number default 0;
-- running tally
summA number default 0;
summB number default 0;
begin
log('DAY | sum(A B) | diff+(A B) | diff-(A B) ' || CHR(10));
-- passed in START date and END date. these literals will be used below.
start_date_date := to_date('15-JUN-18', 'DD-MON-YY');
end_date_date := to_date('25-JUN-18', 'DD-MON-YY');
start_date := to_number(to_char(start_date_date, 'j'));
end_date := to_number(to_char(end_date_date, 'j'));
-- compute previous items
SELECT COUNT(*) INTO summA FROM
(
SELECT DISTINCT t1.itemid, t1.itemcode, t1.changedate
FROM histories t1,
(SELECT itemid, max(changedate) as changedate
FROM histories
WHERE changedate < start_date_date
GROUP BY itemid) t2
WHERE t1.itemid = t2.itemid
AND t1.changedate = t2.changedate
AND t1.itemcode = 'A'
);
SELECT COUNT(*) INTO summB FROM
(
SELECT DISTINCT t1.itemid, t1.itemcode, t1.changedate
FROM histories t1,
(SELECT itemid, max(changedate) as changedate
FROM histories
WHERE changedate < start_date_date
GROUP BY itemid) t2
WHERE t1.itemid = t2.itemid
AND t1.changedate = t2.changedate
AND t1.itemcode = 'B'
);
-- compute a itemcode(itemid) array for all inventory as of given date
for outerrec in (
SELECT itemid, itemcode
FROM inventory
)
loop
for innerrec in (
SELECT itemid, itemcode, changedate
FROM histories
WHERE itemid = outerrec.itemid
AND changedate < start_date_date
AND ROWNUM = 1
ORDER BY changedate DESC
)
loop
code_id_table(innerrec.itemid) := innerrec.itemcode;
end loop;
end loop;
-- compute differentials for every day.
for daterec in start_date..end_date
loop
-- date iterator
currentdate := To_char(To_date(daterec, 'j'), 'DD-MON-YY');
-- reset counts
dateA := 0;
dateB := 0;
dateAn := 0;
dateBn := 0;
for outerrec in (
SELECT itemid, itemcode
FROM inventory
)
loop
-- get the last change of the day
for innerrec in (
SELECT itemid, itemcode, changedate
FROM histories
WHERE itemid = outerrec.itemid
AND changedate >= to_date(currentdate)
AND changedate < to_date(currentdate)+1
AND ROWNUM = 1
ORDER BY changedate DESC
)
loop
-- check existence in code table
if (code_id_table.exists(innerrec.itemid)) then
-- check if the code was lost that day
if (code_id_table(innerrec.itemid) = 'A') then
dateAn := dateAn + 1;
elsif (code_id_table(innerrec.itemid) = 'B') then
dateBn := dateBn + 1;
end if;
-- check if the code was gained that day
if (innerrec.itemcode = 'A') then
dateA := dateA + 1;
elsif (innerrec.itemcode = 'B') then
dateB := dateB + 1;
end if;
else
-- new item, code is gained
if (innerrec.itemcode = 'A') then
dateA := dateA + 1;
elsif (innerrec.itemcode = 'B') then
dateB := dateB + 1;
end if;
end if;
-- update code table
code_id_table(innerrec.itemid) := innerrec.itemcode;
end loop;
end loop;
-- compute sums
summA := summA + (nvl(dateA, 0) + (0 - nvl(dateAn, 0)));
summB := summB + (nvl(dateB, 0) + (0 - nvl(dateBn, 0)));
-- output results
log(To_char(To_date(currentdate), 'YYYY-MM-DD') || ' (' || summA || ' ' || summB || ') (+' || dateA || ' +' || dateB || ') (-' || dateAn || ' -' || dateBn || ') ' || CHR(10));
end loop;
SQLfiddle