【发布时间】:2018-08-25 01:45:54
【问题描述】:
我有一个项目表(库存)和一个库存先前状态表(历史)。我想计算指定日期范围内特定库存(inventory.itemcode 'A' 和 'B')的数量(总和),以及它的变化(差异)。
我能够在 Oracle PL/SQL 过程中执行此操作(以及在 SQLfiddle 中复制它,抽象出许多东西),但这是一个非常昂贵的查询,30 天大约需要 20 分钟,60000库存记录,120000条历史记录。
从查询的角度来看,我可以看出它很昂贵 - 第一个循环是天数,第二个循环是整个库存,第三个循环是该特定库存的整个历史记录。
我怎样才能使这个查询更快?我的猜测是使用聚合函数来减少循环量,依赖于 Oracle 的智能 SQL 算法——但我不太确定如何将所有这些变成一个大查询——或者这是否可能。
我可以添加新的字段以使该表更好/使计算更快,但我无法删除任何字段。
提前致谢,请告诉我如何澄清或改进我的问题。我是 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;
【问题讨论】:
标签: sql oracle plsql query-optimization