计算日期之间差异的最有效方法

Most efficient way to compute difference between dates

我有一个很大的table,按日期分区,结构如下:

InTable:

Key Date Field1 Value
key1 09/02/2021 ABC 10
key2 09/02/2021 DEF -4
key3 09/02/2021 GHI 9
key1 10/02/2021 ABC 30
key2 10/02/2021 DEFG 6
key4 10/02/2021 LMN 2

我想输出一个table这样结构的

OutTable:

Key Date Field1 Value T Value T-1 Daily Diff
key1 10/02/2021 ABC 30 10 20
key2 10/02/2021 DEFG 6 -4 10
key3 10/02/2021 GHI 0 9 -9
key4 10/02/2021 LMN 2 0 2

我在某个日期折叠了当前值、前一天的值以及具有相同键的两者之间的差异,同时保留了任何一个日期中缺少键的所有情况(对于例如,10 月 2 日缺少 key3,9 月 2 日缺少 key4。

如果key相同,Field1通常是相同的。如果它们不同,则必须保留最近的值(如示例中的 key2)。

据我所知,可以这样做:

在 Oracle 中完成这项工作最有效、最快捷的方法是什么?

非常感谢。

我认为您需要 window 函数。如果你有所有天的数据或者想要数据中前一天的数据:

select t.*,
       lag(value, 1, 0) over (partition by key, field order by date) as prev_value,
       (value - lag(value, 1, 0) over (partition by key, field order by date)) as diff
from t;

如果值可以跳过日期,而您确实想要以前的日期,则可以使用带有 range window 框架的 window 函数:

select t.*,
       max(value) over (partition by key, field 
                        order by date
                        range between '1' day preceding and '1' day preceding
                       ) as prev_value,
       (value - 
        max(value) over (partition by key, field 
                        order by date
                        range between '1' day preceding and '1' day preceding
                       )
       ) as diff
from t;

这会将缺失值保留为 NULL。您可以使用 COALESCE() 为它们赋值。

如果要按天过滤,需要在外部查询中进行:

select t.*
from (select t.*,
             max(value) over (partition by key, field1 
                              order by day
                              range between interval '1' day preceding and interval '1' day preceding
                             ) as prev_value,
             (value - 
              max(value) over (partition by key, field1 
                               order by day
                              range between interval '1' day preceding and interval '1' day preceding
                             )
             ) as diff
      from InTable t
     ) t
where day = date '2021-02-10'
order by day asc;

Here 是一个 db<>fiddle.

如果要修剪分区,则需要在子查询中包含滞后行。你可以起诉:

where day in (date '2021-02-10', date '2021-02-09')

在子查询中。

这是你可以做的(代码后的解释):

alter session set nls_date_format = 'dd/mm/yyyy';

with
  intable (key, date_, field1, value) as (
    select 'key1', to_date('09/02/2021'), 'ABC' , 10 from dual union all
    select 'key2', to_date('09/02/2021'), 'DEF' , -4 from dual union all
    select 'key3', to_date('09/02/2021'), 'GHI' ,  9 from dual union all
    select 'key1', to_date('10/02/2021'), 'ABC' , 30 from dual union all
    select 'key2', to_date('10/02/2021'), 'DEFG',  6 from dual union all
    select 'key4', to_date('10/02/2021'), 'LMN' ,  2 from dual
  )
, ref_date (dt) as (select to_date('10/02/2021') from dual)
, prep (key, field1, new_val, old_val) as (
    select i.key, 
           min(field1) keep (dense_rank last order by date_),
           nvl(min(case i.date_ when r.dt     then value end), 0),
           nvl(min(case i.date_ when r.dt - 1 then value end), 0)
    from   intable i cross join ref_date r
    where  date_ in (r.dt - 1, r.dt)
    group  by i.key, r.dt
  )
select p.key, r.dt as date_, p.field1,
       p.new_val, p.old_val, p.new_val - p.old_val as daily_diff
from   prep p cross join ref_date r
order  by key   --  If needed
;


KEY   DATE_       FIELD1  NEW_VAL  OLD_VAL  DAILY_DIFF
----  ----------  ------  -------  -------  ----------
key1  10/02/2021  ABC          30       10          20
key2  10/02/2021  DEFG          6       -4          10
key3  10/02/2021  GHI           0        9          -9
key4  10/02/2021  LMN           2        0           2

解释:

WITH 子句中的 INTABLE 子查询只是模拟输入数据的一种简单方法(而不是创建 table 并插入其中,然后记得删除table 来自我的系统)。在实际使用中,从查询中删除 INTABLE,而是使用您实际的 table 和列名称。

请注意我首先 运行 的 ALTER SESSION 声明 - 因此我不需要为 TO_DATE 提供格式模型。另请注意,我将列名从 DATE 更改为因为 DATE 是 Oracle 中的保留关键字。 (不要在列名称周围使用双引号来解决该约束;只需使用不同的名称,就像我所做的那样。)

您还会看到我在输出中使用了不同的列名。如果需要,请更改它们,但不要创建包含空格、破折号等的列名。使用标准列名。

REF_DATE 也可能不需要;我用它来给出“报告日期”——但如果它是一个绑定变量就更好了。或者,使用绑定变量而不是我给出的硬编码值,就在该子查询中(和 keep REF_DATE WITH 子句)。

主要工作在PREP子查询中完成。事实上,优化器会将这个子查询与外部查询(在代码的末尾)结合起来;我按照我的方式编写它只是为了节省一些输入 - 这样我就不需要重复 OLD_VALNEW_VAL 的定义来计算差异。优化查询(内部 Oracle 优化的结果)将只执行一个 SELECT,并且它将使用“sort group by”(使用结果排序 ORDER BY 的外部查询,单次通过)。

查询(PREP 子查询)仅针对报告所需的两个日期进行过滤 - 索引 对 [=27= 列的帮助极大].然后查询使用标准聚合(不是分析函数或任何其他功能)——这很可能使其比替代方法更快。

不清楚为什么需要输出中的 DATE_ 列。它不基于数据中的任何内容 - 它只是报告您作为输入提供的“参考日期”。我把它包括在内,以防你真的需要它(它非常便宜 - 我再次交叉连接到标量子查询 REF_DATE),但如果你不需要它,你可以从主(外部)中删除它查询,您不需要交叉连接,也不需要 PREP 的别名和其他列的 P 限定符。