Sql 计算日期差的最有效方法

Sql 计算日期差的最有效方法,sql,oracle,oracle12c,database-performance,Sql,Oracle,Oracle12c,Database Performance,我有一个按日期划分的大表,其结构如下: 表格: 钥匙 日期 字段1 价值 关键1 09/02/2021 基础知识 10 键2 09/02/2021 DEF -4 键3 09/02/2021 GHI 9 关键1 10/02/2021 基础知识 30 键2 10/02/2021 德福 6. 关键4 10/02/2021 LMN 2. 我想你需要窗口功能。如果您拥有所有日期的数据,或希望数据中包含前一日期的数据: select t.*, lag(value, 1, 0) over (pa

我有一个按日期划分的大表,其结构如下:

表格:

钥匙 日期 字段1 价值 关键1 09/02/2021 基础知识 10 键2 09/02/2021 DEF -4 键3 09/02/2021 GHI 9 关键1 10/02/2021 基础知识 30 键2 10/02/2021 德福 6. 关键4 10/02/2021 LMN 2.
我想你需要窗口功能。如果您拥有所有日期的数据,或希望数据中包含前一日期的数据:

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;
如果值可以跳过日期,并且您确实想要上一个日期,则可以使用带有
范围的窗口函数
窗口框架:

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;
他是一把小提琴

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

where day in (date '2021-02-10', date '2021-02-09')
在子查询中。

以下是您可以执行的操作(代码后的解释):

说明:

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

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

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

REF_DATE
可能也不需要;我用它来表示“报告日期”——但如果它是一个绑定变量则更好。或者,使用bind变量代替我给出的硬编码值,就在该子查询中(以及
WITH
子句中的
保留
REF\u DATE

主要工作在
PREP
子查询中完成。事实上,优化器将把这个子查询与外部查询结合起来(在代码末尾);我这样写只是为了省去一些打字——这样我就不需要重复
OLD_VAL
NEW_VAL
的定义来计算差异。优化的查询(内部Oracle优化的结果)将只执行一次
SELECT
,并且它将使用“sortgroupby”(使用外部查询的
ORDER by
的结果排序,一次通过)

查询(
PREP
子查询)只过滤报告所需的两个日期-这将通过列
DATE\uu
上的索引得到极大帮助。然后,查询使用标准聚合(而不是分析函数或任何其他功能),这很可能会使其比备选方案更快


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

您的值列似乎已切换。T-1有当前值,T有以前的值。嗨,戈登,谢谢你指出,但我认为列值是正确的。例如,对于键1,30是10/2的值,10是9/2的值。嗨,戈登,谢谢你的回答。如果我在InTable上运行查询,我认为它还将返回对应于9/2的记录,所有记录的T-1值都设置为0(如果没有合并,则为null)。这当然可以通过过滤输出值来解决,但我不完全确定它是否比UNIONALL更有效。另外,如果一个键在第二天消失,如示例中的键3,我认为它将在10/2没有记录,对吗?谢谢。@悲伤。第一个查询获取记录的早期版本,而不考虑日期。第二个查询坚持日期在前一天。下面是第二个查询的SQLFIDLE:。我不确定“1天前”是否是有效的oracle语法。如您所见,key3记录在10/02丢失,因此输出不等于所需的输出输出表。此外,我还必须在外部查询中过滤结果,以获得OutTable,我认为这在性能方面不是很好。再次谢谢你。@悲伤。如果要筛选,则需要使用子查询。感谢您的编辑和SQLFIDLE,尽管输出中缺少key3值,因为它在10/2上不可用。如果它在前一天可用,有没有办法让它在10月2日可用?谢谢你非常详细的回答@mathguy。我在这里创建了sqlfiddle,只是为了其他对您的解决方案感兴趣的人。我还有最后一个疑问。这里有一个使用pivot子句的解决方案,您认为它与您的解决方案在性能方面是等效的,还是一个解决方案与另一个解决方案相比在性能方面是有利的?我不确定此解决方案是否必须在field1上执行额外的nvl,因此速度可能会慢一些,但我不知道它是否使用了比普通聚合更有效的计划。@悲伤-旋转是聚合的一种形式;我编写查询的方式基本上与Oracle引入
pivot
操作符之前的透视方式相同,而且很可能
pivot
仍在实现中
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