Sql(在 Oracle 上)按天计算的老化报告
Sql (on Oracle) aging report by days
我需要帮助编写有关 Oracle 的老化报告。
报告应该是这样的:
aging file to submit total 17
aging file to submit 0-2 days 3
aging file to submit 2-4 days 4
aging file to submit 4-6 days 4
aging file to submit 6-8 days 2
aging file to submit 8-10 days 4
我可以为每个部分创建一个查询,然后合并所有结果,例如:
select 'aging file to submit total ' || count(*) from FILES_TO_SUBMIT where trunc(DUE_DATE) > trunc(sysdate) -10
union all
select 'aging file to submit 0-2 days ' || count(*) from FILES_TO_SUBMIT where trunc(DUE_DATE) <= trunc(sysdate) and trunc(DUE_DATE) >= trunc(sysdate-2)
union all
select 'aging file to submit 2-4 days ' || count(*) from FILES_TO_SUBMIT where trunc(DUE_DATE) <= trunc(sysdate-2) and trunc(DUE_DATE) >= trunc(sysdate-4) ;
我想知道是否有更好的方法使用 oracle 分析函数或任何其他查询来获得更好的性能?
示例数据:
CREATE TABLE files_to_submit(file_id int, file_name varchar(255),due_date date);
INSERT INTO FILES_TO_SUBMIT(FILE_ID,FILE_NAME,DUE_DATE) VALUES ( 1, 'file_' || 1, sysdate);
INSERT INTO FILES_TO_SUBMIT(FILE_ID,FILE_NAME,DUE_DATE) VALUES ( 2, 'file_' || 2, sysdate -5);
INSERT INTO FILES_TO_SUBMIT(FILE_ID,FILE_NAME,DUE_DATE) VALUES ( 3, 'file_' || 3, sysdate -4);
INSERT INTO FILES_TO_SUBMIT(FILE_ID,FILE_NAME,DUE_DATE) VALUES ( 4, 'file_' || 4, sysdate);
INSERT INTO FILES_TO_SUBMIT(FILE_ID,FILE_NAME,DUE_DATE) VALUES ( 5, 'file_' || 5, sysdate-3);
INSERT INTO FILES_TO_SUBMIT(FILE_ID,FILE_NAME,DUE_DATE) VALUES ( 6, 'file_' || 6, sysdate-7);
INSERT INTO FILES_TO_SUBMIT(FILE_ID,FILE_NAME,DUE_DATE) VALUES ( 7, 'file_' || 7, sysdate-10);
INSERT INTO FILES_TO_SUBMIT(FILE_ID,FILE_NAME,DUE_DATE) VALUES ( 8, 'file_' || 8, sysdate-12);
INSERT INTO FILES_TO_SUBMIT(FILE_ID,FILE_NAME,DUE_DATE) VALUES ( 9, 'file_' || 9, sysdate-3);
INSERT INTO FILES_TO_SUBMIT(FILE_ID,FILE_NAME,DUE_DATE) VALUES ( 10, 'file_' || 10, sysdate-5);
INSERT INTO FILES_TO_SUBMIT(FILE_ID,FILE_NAME,DUE_DATE) VALUES ( 11, 'file_' || 11, sysdate-6);
INSERT INTO FILES_TO_SUBMIT(FILE_ID,FILE_NAME,DUE_DATE) VALUES ( 12, 'file_' || 12, sysdate-7);
INSERT INTO FILES_TO_SUBMIT(FILE_ID,FILE_NAME,DUE_DATE) VALUES ( 13, 'file_' || 13, sysdate-5);
INSERT INTO FILES_TO_SUBMIT(FILE_ID,FILE_NAME,DUE_DATE) VALUES ( 14, 'file_' || 14, sysdate-4);
INSERT INTO FILES_TO_SUBMIT(FILE_ID,FILE_NAME,DUE_DATE) VALUES ( 15, 'file_' || 15, sysdate-2);
INSERT INTO FILES_TO_SUBMIT(FILE_ID,FILE_NAME,DUE_DATE) VALUES ( 16, 'file_' || 16, sysdate-6);
INSERT INTO FILES_TO_SUBMIT(FILE_ID,FILE_NAME,DUE_DATE) VALUES ( 17, 'file_' || 17, sysdate-6);
INSERT INTO FILES_TO_SUBMIT(FILE_ID,FILE_NAME,DUE_DATE) VALUES ( 18, 'file_' || 18, sysdate-5);
INSERT INTO FILES_TO_SUBMIT(FILE_ID,FILE_NAME,DUE_DATE) VALUES ( 19, 'file_' || 19, sysdate-10);
INSERT INTO FILES_TO_SUBMIT(FILE_ID,FILE_NAME,DUE_DATE) VALUES ( 20, 'file_' || 20, sysdate-9);
DROP TABLE files_to_submit;
我使用你的样本数据得到了不同的计数——我得到的总数是 19,而不是 17(这似乎是合适的,因为你的样本数据中只有 20 条记录中的一条超出了范围):
WITH d1 AS (
SELECT 2 AS day_cnt FROM dual
UNION ALL
SELECT 4 FROM dual
UNION ALL
SELECT 6 FROM dual
UNION ALL
SELECT 8 FROM dual
UNION ALL
SELECT 10 FROM dual
)
SELECT NVL(title, 'aging file to submit total') AS title, COUNT(DISTINCT file_id)
FROM (
SELECT 'aging file to submit ' || prev_day || '-' || day_cnt || ' days' AS title, f1.file_id
FROM (
SELECT day_cnt, NVL(LAG(day_cnt) OVER ( ORDER BY day_cnt ), 0) AS prev_day
FROM d1
) d2, files_to_submit f1
WHERE TRUNC(f1.due_date) <= TRUNC(SYSDATE - d2.prev_day)
AND TRUNC(f1.due_date) >= TRUNC(SYSDATE - d2.day_cnt)
) GROUP BY ROLLUP(title);
此外,日期范围的计数不正确(它们加起来不等于 19,也就是说)因为使用 TRUNC()
和包括两个终端案例。但我相信您可以调整以上内容以提供您想要的内容。
您可以使用这种简单的方法获取所有天数的报告(不包括总数):
select
'aging file to submit '|| trunc(dist/2)*2 ||'-'|| (trunc(dist/2)*2+2) || ' days: ' || count(*)
from (
select trunc(sysdate) - trunc(DUE_DATE) as dist
from FILES_TO_SUBMIT
--where trunc(DUE_DATE) > trunc(sysdate) -10
)
group by trunc(dist/2)
order by trunc(dist/2);
唯一重要的是天数(dist(ance) 字段)。
如果你想在同一次扫描中也有总数:
select
'aging file to submit '||
case
when trunc(dist/2) is null
then 'Total '
else trunc(dist/2)*2 ||'-'|| (trunc(dist/2)*2+2) || ' days: '
end ||
count(*)
from (
select trunc(sysdate) - trunc(DUE_DATE) as dist
from FILES_TO_SUBMIT
where trunc(DUE_DATE) > trunc(sysdate) -10
)
group by rollup(trunc(dist/2))
order by trunc(dist/2)
nulls first;
提示:如果您有数百天的历史记录,索引会很有用。 (注意:如果你的table很大,>100Milion,创建索引需要一些时间)
create index index_name on files_to_submit(due_date);
然后把条件改成:
where DUE_DATE > trunc(sysdate) - 10
这会加快 y
这种方法允许您将存储桶与主 SQL 分开维护,以防您希望它们具有不同的大小或将它们命名为不是由 SQL 生成的名称,例如'On time'、'Delinquent' 等,还提供了一个非常易读的主 SQL 块。
with aging as
(select count(*) count_per_day, (trunc(sysdate) - trunc(f.due_date)) age
from files_to_submit f
where trunc(f.due_date - 10) <= sysdate
group by (trunc(sysdate) - trunc(f.due_date))),
buckets as
(select 1 bucket_id, 0 bucket_min, 2 bucket_max, 'aging file to submit 0-2' bucket_name from dual
union select 2, 2, 4, 'aging file to submit 2-4' from dual
union select 3, 4, 6, 'aging file to submit 4-6' from dual
union select 4, 6, 8, 'aging file to submit 6-8' from dual
union select 5, 8, 10, 'aging file to submit 8-10' from dual
union select 6, null, null, 'aging file to submit total' from dual
)
select nvl(b.bucket_name, (select bucket_name from buckets where bucket_id = 6)), sum(a.count_per_day) bucket_cnt
from aging a
join buckets b on (a.age >= b.bucket_min and a.age <= b.bucket_max)
group by rollup(b.bucket_name)
order by b.bucket_name nulls first;
WITH r (
'aging file to submit ' Prefix,
Total,
Days0_2,
Days2_4,
Days4_6,
Days6_8,
Days8_10
) AS (
SELECT
SUM(Total) Total,
SUM(Days0_2) Days0_2,
SUM(Days2_4) Days2_4,
SUM(Days4_6) Days4_6,
SUM(Days6_8) Days6_8,
SUM(Days8_10) Days8_10
FROM (
SELECT
(CASE WHEN f.days <= 2 THEN f.num ELSE NULL END) AS Days0_2,
(CASE WHEN f.days >= 2 AND f.days <= 4 THEN f.num ELSE NULL END) AS Days2_4,
(CASE WHEN f.days >= 4 AND f.days <= 6 THEN f.num ELSE NULL END) Days4_6,
(CASE WHEN f.days >= 6 AND f.days <= 8 THEN f.num ELSE NULL END) AS Days6_8,
(CASE WHEN f.days >= 8 AND f.days <= 10 THEN f.num ELSE NULL END) AS Days8_10,
f.num AS Total
FROM (
SELECT
COUNT(*) AS num,
TRUNC(due_date) - TRUNC(SYSDATE) + 10 AS days
FROM FILES_TO_SUBMIT t
WHERE (TRUNC(due_date) - TRUNC(SYSDATE) + 10) >= 0
GROUP BY TRUNC(due_date) - TRUNC(SYSDATE) + 10
) f
) s
)
SELECT Prefix || 'Total' AS Label, Total AS Count FROM r
UNION ALL SELECT Prefix || '0-2 days', Days0_2 FROM r
UNION ALL SELECT Prefix || '2-4 days', Days2_4 FROM r
UNION ALL SELECT Prefix || '4-6 days', Days4_6 FROM r
UNION ALL SELECT Prefix || '6-8 days', Days6_8 FROM r
UNION ALL SELECT Prefix || '8-10 days', Days8_10 FROM r
它不会重复计算总计行的记录。由于您的日期范围重叠,因此您无法将各个计数相加以获得总数。作为此处给出的另一个查询,总共给出了 25 个,其中只有 20 个记录和 1 个超出范围。
总计的结果是您预期的结果,有 20 条记录,其中 1 条是 12 天前的。最里面的查询完成了所有繁重的工作,但它只执行一次以获取所有老化结果。其结果最多为 11 行,0-10 天。其余查询用于最终结果和漂亮的输出。
您可以通过对一个级别求和来消除一个查询级别,我只是发现通过能够 select 中间查询进行抽查更容易验证结果。
查询结果如下:
请允许我提出建议 WIDTH_BUCKET。
这会将日期范围划分为相等的大小。由于您希望将 10 天范围分为 2 天一组,因此存储桶大小将为 10 / 2 = 5。
查询:
SELECT
CASE GROUPING(bucket)
WHEN 1
THEN 'aging file to submit Total'
ELSE 'aging file to submit ' || (bucket-1)*2 || '-' || (bucket)*2 || ' days'
END AS bucket_number,
COUNT(1) AS files
FROM (
SELECT
WIDTH_BUCKET(due_date, sysdate, sysdate-10, 5) bucket
FROM
files_to_submit
WHERE
due_date >= sysdate-10
)
GROUP BY
ROLLUP(bucket)
ORDER BY
bucket NULLS FIRST;
结果:
BUCKET_NUMBER FILES
------------------------------------ ----------
aging file to submit Total 17
aging file to submit 0-2 days 2
aging file to submit 2-4 days 3
aging file to submit 4-6 days 6
aging file to submit 6-8 days 5
aging file to submit 8-10 days 1
我需要帮助编写有关 Oracle 的老化报告。 报告应该是这样的:
aging file to submit total 17
aging file to submit 0-2 days 3
aging file to submit 2-4 days 4
aging file to submit 4-6 days 4
aging file to submit 6-8 days 2
aging file to submit 8-10 days 4
我可以为每个部分创建一个查询,然后合并所有结果,例如:
select 'aging file to submit total ' || count(*) from FILES_TO_SUBMIT where trunc(DUE_DATE) > trunc(sysdate) -10
union all
select 'aging file to submit 0-2 days ' || count(*) from FILES_TO_SUBMIT where trunc(DUE_DATE) <= trunc(sysdate) and trunc(DUE_DATE) >= trunc(sysdate-2)
union all
select 'aging file to submit 2-4 days ' || count(*) from FILES_TO_SUBMIT where trunc(DUE_DATE) <= trunc(sysdate-2) and trunc(DUE_DATE) >= trunc(sysdate-4) ;
我想知道是否有更好的方法使用 oracle 分析函数或任何其他查询来获得更好的性能?
示例数据:
CREATE TABLE files_to_submit(file_id int, file_name varchar(255),due_date date);
INSERT INTO FILES_TO_SUBMIT(FILE_ID,FILE_NAME,DUE_DATE) VALUES ( 1, 'file_' || 1, sysdate);
INSERT INTO FILES_TO_SUBMIT(FILE_ID,FILE_NAME,DUE_DATE) VALUES ( 2, 'file_' || 2, sysdate -5);
INSERT INTO FILES_TO_SUBMIT(FILE_ID,FILE_NAME,DUE_DATE) VALUES ( 3, 'file_' || 3, sysdate -4);
INSERT INTO FILES_TO_SUBMIT(FILE_ID,FILE_NAME,DUE_DATE) VALUES ( 4, 'file_' || 4, sysdate);
INSERT INTO FILES_TO_SUBMIT(FILE_ID,FILE_NAME,DUE_DATE) VALUES ( 5, 'file_' || 5, sysdate-3);
INSERT INTO FILES_TO_SUBMIT(FILE_ID,FILE_NAME,DUE_DATE) VALUES ( 6, 'file_' || 6, sysdate-7);
INSERT INTO FILES_TO_SUBMIT(FILE_ID,FILE_NAME,DUE_DATE) VALUES ( 7, 'file_' || 7, sysdate-10);
INSERT INTO FILES_TO_SUBMIT(FILE_ID,FILE_NAME,DUE_DATE) VALUES ( 8, 'file_' || 8, sysdate-12);
INSERT INTO FILES_TO_SUBMIT(FILE_ID,FILE_NAME,DUE_DATE) VALUES ( 9, 'file_' || 9, sysdate-3);
INSERT INTO FILES_TO_SUBMIT(FILE_ID,FILE_NAME,DUE_DATE) VALUES ( 10, 'file_' || 10, sysdate-5);
INSERT INTO FILES_TO_SUBMIT(FILE_ID,FILE_NAME,DUE_DATE) VALUES ( 11, 'file_' || 11, sysdate-6);
INSERT INTO FILES_TO_SUBMIT(FILE_ID,FILE_NAME,DUE_DATE) VALUES ( 12, 'file_' || 12, sysdate-7);
INSERT INTO FILES_TO_SUBMIT(FILE_ID,FILE_NAME,DUE_DATE) VALUES ( 13, 'file_' || 13, sysdate-5);
INSERT INTO FILES_TO_SUBMIT(FILE_ID,FILE_NAME,DUE_DATE) VALUES ( 14, 'file_' || 14, sysdate-4);
INSERT INTO FILES_TO_SUBMIT(FILE_ID,FILE_NAME,DUE_DATE) VALUES ( 15, 'file_' || 15, sysdate-2);
INSERT INTO FILES_TO_SUBMIT(FILE_ID,FILE_NAME,DUE_DATE) VALUES ( 16, 'file_' || 16, sysdate-6);
INSERT INTO FILES_TO_SUBMIT(FILE_ID,FILE_NAME,DUE_DATE) VALUES ( 17, 'file_' || 17, sysdate-6);
INSERT INTO FILES_TO_SUBMIT(FILE_ID,FILE_NAME,DUE_DATE) VALUES ( 18, 'file_' || 18, sysdate-5);
INSERT INTO FILES_TO_SUBMIT(FILE_ID,FILE_NAME,DUE_DATE) VALUES ( 19, 'file_' || 19, sysdate-10);
INSERT INTO FILES_TO_SUBMIT(FILE_ID,FILE_NAME,DUE_DATE) VALUES ( 20, 'file_' || 20, sysdate-9);
DROP TABLE files_to_submit;
我使用你的样本数据得到了不同的计数——我得到的总数是 19,而不是 17(这似乎是合适的,因为你的样本数据中只有 20 条记录中的一条超出了范围):
WITH d1 AS (
SELECT 2 AS day_cnt FROM dual
UNION ALL
SELECT 4 FROM dual
UNION ALL
SELECT 6 FROM dual
UNION ALL
SELECT 8 FROM dual
UNION ALL
SELECT 10 FROM dual
)
SELECT NVL(title, 'aging file to submit total') AS title, COUNT(DISTINCT file_id)
FROM (
SELECT 'aging file to submit ' || prev_day || '-' || day_cnt || ' days' AS title, f1.file_id
FROM (
SELECT day_cnt, NVL(LAG(day_cnt) OVER ( ORDER BY day_cnt ), 0) AS prev_day
FROM d1
) d2, files_to_submit f1
WHERE TRUNC(f1.due_date) <= TRUNC(SYSDATE - d2.prev_day)
AND TRUNC(f1.due_date) >= TRUNC(SYSDATE - d2.day_cnt)
) GROUP BY ROLLUP(title);
此外,日期范围的计数不正确(它们加起来不等于 19,也就是说)因为使用 TRUNC()
和包括两个终端案例。但我相信您可以调整以上内容以提供您想要的内容。
您可以使用这种简单的方法获取所有天数的报告(不包括总数):
select
'aging file to submit '|| trunc(dist/2)*2 ||'-'|| (trunc(dist/2)*2+2) || ' days: ' || count(*)
from (
select trunc(sysdate) - trunc(DUE_DATE) as dist
from FILES_TO_SUBMIT
--where trunc(DUE_DATE) > trunc(sysdate) -10
)
group by trunc(dist/2)
order by trunc(dist/2);
唯一重要的是天数(dist(ance) 字段)。
如果你想在同一次扫描中也有总数:
select
'aging file to submit '||
case
when trunc(dist/2) is null
then 'Total '
else trunc(dist/2)*2 ||'-'|| (trunc(dist/2)*2+2) || ' days: '
end ||
count(*)
from (
select trunc(sysdate) - trunc(DUE_DATE) as dist
from FILES_TO_SUBMIT
where trunc(DUE_DATE) > trunc(sysdate) -10
)
group by rollup(trunc(dist/2))
order by trunc(dist/2)
nulls first;
提示:如果您有数百天的历史记录,索引会很有用。 (注意:如果你的table很大,>100Milion,创建索引需要一些时间)
create index index_name on files_to_submit(due_date);
然后把条件改成:
where DUE_DATE > trunc(sysdate) - 10
这会加快 y
这种方法允许您将存储桶与主 SQL 分开维护,以防您希望它们具有不同的大小或将它们命名为不是由 SQL 生成的名称,例如'On time'、'Delinquent' 等,还提供了一个非常易读的主 SQL 块。
with aging as
(select count(*) count_per_day, (trunc(sysdate) - trunc(f.due_date)) age
from files_to_submit f
where trunc(f.due_date - 10) <= sysdate
group by (trunc(sysdate) - trunc(f.due_date))),
buckets as
(select 1 bucket_id, 0 bucket_min, 2 bucket_max, 'aging file to submit 0-2' bucket_name from dual
union select 2, 2, 4, 'aging file to submit 2-4' from dual
union select 3, 4, 6, 'aging file to submit 4-6' from dual
union select 4, 6, 8, 'aging file to submit 6-8' from dual
union select 5, 8, 10, 'aging file to submit 8-10' from dual
union select 6, null, null, 'aging file to submit total' from dual
)
select nvl(b.bucket_name, (select bucket_name from buckets where bucket_id = 6)), sum(a.count_per_day) bucket_cnt
from aging a
join buckets b on (a.age >= b.bucket_min and a.age <= b.bucket_max)
group by rollup(b.bucket_name)
order by b.bucket_name nulls first;
WITH r (
'aging file to submit ' Prefix,
Total,
Days0_2,
Days2_4,
Days4_6,
Days6_8,
Days8_10
) AS (
SELECT
SUM(Total) Total,
SUM(Days0_2) Days0_2,
SUM(Days2_4) Days2_4,
SUM(Days4_6) Days4_6,
SUM(Days6_8) Days6_8,
SUM(Days8_10) Days8_10
FROM (
SELECT
(CASE WHEN f.days <= 2 THEN f.num ELSE NULL END) AS Days0_2,
(CASE WHEN f.days >= 2 AND f.days <= 4 THEN f.num ELSE NULL END) AS Days2_4,
(CASE WHEN f.days >= 4 AND f.days <= 6 THEN f.num ELSE NULL END) Days4_6,
(CASE WHEN f.days >= 6 AND f.days <= 8 THEN f.num ELSE NULL END) AS Days6_8,
(CASE WHEN f.days >= 8 AND f.days <= 10 THEN f.num ELSE NULL END) AS Days8_10,
f.num AS Total
FROM (
SELECT
COUNT(*) AS num,
TRUNC(due_date) - TRUNC(SYSDATE) + 10 AS days
FROM FILES_TO_SUBMIT t
WHERE (TRUNC(due_date) - TRUNC(SYSDATE) + 10) >= 0
GROUP BY TRUNC(due_date) - TRUNC(SYSDATE) + 10
) f
) s
)
SELECT Prefix || 'Total' AS Label, Total AS Count FROM r
UNION ALL SELECT Prefix || '0-2 days', Days0_2 FROM r
UNION ALL SELECT Prefix || '2-4 days', Days2_4 FROM r
UNION ALL SELECT Prefix || '4-6 days', Days4_6 FROM r
UNION ALL SELECT Prefix || '6-8 days', Days6_8 FROM r
UNION ALL SELECT Prefix || '8-10 days', Days8_10 FROM r
它不会重复计算总计行的记录。由于您的日期范围重叠,因此您无法将各个计数相加以获得总数。作为此处给出的另一个查询,总共给出了 25 个,其中只有 20 个记录和 1 个超出范围。
总计的结果是您预期的结果,有 20 条记录,其中 1 条是 12 天前的。最里面的查询完成了所有繁重的工作,但它只执行一次以获取所有老化结果。其结果最多为 11 行,0-10 天。其余查询用于最终结果和漂亮的输出。
您可以通过对一个级别求和来消除一个查询级别,我只是发现通过能够 select 中间查询进行抽查更容易验证结果。
查询结果如下:
请允许我提出建议 WIDTH_BUCKET。 这会将日期范围划分为相等的大小。由于您希望将 10 天范围分为 2 天一组,因此存储桶大小将为 10 / 2 = 5。
查询:
SELECT
CASE GROUPING(bucket)
WHEN 1
THEN 'aging file to submit Total'
ELSE 'aging file to submit ' || (bucket-1)*2 || '-' || (bucket)*2 || ' days'
END AS bucket_number,
COUNT(1) AS files
FROM (
SELECT
WIDTH_BUCKET(due_date, sysdate, sysdate-10, 5) bucket
FROM
files_to_submit
WHERE
due_date >= sysdate-10
)
GROUP BY
ROLLUP(bucket)
ORDER BY
bucket NULLS FIRST;
结果:
BUCKET_NUMBER FILES
------------------------------------ ----------
aging file to submit Total 17
aging file to submit 0-2 days 2
aging file to submit 2-4 days 3
aging file to submit 4-6 days 6
aging file to submit 6-8 days 5
aging file to submit 8-10 days 1