如果 x 是日期,`(ORDER BY x RANGE BETWEEN n PRECEDING...)` 的含义是什么?

Len*_*art 5 db2 window-functions db2-10.5 db2-luw

在另一个线程中:

/sf/ask/2643176161/

OP 想要过去 365 天的滑动平均值。ROWS BETWEEN ...如果可以保证每天恰好发生一次,则使用会很好,但这里的情况并非如此。RANGE BETWEEN ...看起来很合适,但我不清楚它在 DB2 中的含义。不确定 db2 没有INTERVAL类型是否重要,但用标记的持续时间模仿它。

文档说:(https://www.ibm.com/support/knowledgecenter/SSEPGG_10.5.0/com.ibm.db2.luw.sql.ref.doc/doc/r0023461.html

无符号常量 PRECEDING

指定当前行之前的行数范围或行数。如果指定了 ROWS,则 unsigned-constant 必须为零或表示行数的正整数。如果指定了 RANGE,则 unsigned-constant 的数据类型必须与 window-order-clause 的 sort-key-expression 的类型相当。sort-key-expression 只能有一个,并且 sort-key-expression 的数据类型必须允许减法。如果 group-bound1 是 CURRENT ROW 或 unsigned-constant FOLLOWING,则不能在 group-bound2 中指定该子句。

无符号常量如下

指定当前行之后的行数范围或行数。如果指定了 ROWS,则 unsigned-constant 必须为零或表示行数的正整数。如果指定了 RANGE,则 unsigned-constant 的数据类型必须与 window-order-clause 的 sort-key-expression 的类型相当。sort-key-expression 只能有一个,并且 sort-key-expression 的数据类型必须允许添加。

DB2 允许以下结构:

values current_date - 1
Run Code Online (Sandbox Code Playgroud)

默认单位日期是天,所以这意味着:

values current_date - 1 day
Run Code Online (Sandbox Code Playgroud)

鉴于此,我希望此示例能够正常工作:

create table test 
( d date not null
, x decimal(3,0) not null);

insert into test (d,x) 
values ('2016-01-01',10),('2016-01-07',20),('2016-01-12',30);
Run Code Online (Sandbox Code Playgroud)

我希望查询:

select d, avg(x) over (order by d 
                       range between 30 preceding 
                                 and current row) 
from test 
order by d;
Run Code Online (Sandbox Code Playgroud)

返回:

2016-01-01-00.00.00  10
2016-01-07-00.00.00  15
2016-01-12-00.00.00  20
Run Code Online (Sandbox Code Playgroud)

或者可能会产生错误,但结果是:

2016-01-01-00.00.00  10
2016-01-07-00.00.00  20
2016-01-12-00.00.00  25
Run Code Online (Sandbox Code Playgroud)

我还尝试在查询中添加日期:

select d, avg(x) over (order by d 
                       range between 30 days preceding

select d, avg(x) over (order by d 
                       range between (cast 30 as day) preceding
Run Code Online (Sandbox Code Playgroud)

以防万一,但这两种尝试都会导致:

 SQL0104N  An unexpected token "day" was found following "y ...
 SQL0104N  An unexpected token "cast(30 as day)" was found following "r ...
Run Code Online (Sandbox Code Playgroud)

我首先怀疑这个单位比天小,但是将前面的部分增加到 300 会返回相同的结果。也许更令人惊讶的是,将其增加到 500 会将结果更改为:

 select d, avg(x) over (order by d 
                        range between 500 preceding 
                                  and current row) 
 from test 
 order by d

2016-01-01-00.00.00  10
2016-01-07-00.00.00  20
2016-01-12-00.00.00  30
Run Code Online (Sandbox Code Playgroud)

鉴于查询:

 select d, avg(x) over (order by d 
                        range between n preceding 
                                  and current row) 
Run Code Online (Sandbox Code Playgroud)

对于 300 <= n <= 399,最后一行的结果是 25,对于 n<300 或 n>399,最后一行的结果是 30。

我不知道 avg 函数看到了哪些行,我最好的猜测是在框架子句中有一些隐含的日期转换,但不知道如何证明或反驳这个假设。有人可以对此有所了解吗?

mus*_*cio 4

首先,该表达式values current_date - 1仅在 Oracle 兼容模式生效时才有效——它模仿 Oracle 的日期时间算术,其中默认间隔以(可能是小数)天数表示。

我认为,无论 Oracle 兼容性如何,范围边界都应该与整数进行比较,并且将DATE值与整数进行比较可能会产生意外的结果。如果将DATEs 转换为自过去某个时刻以来的天数,则可以使用整数比较。您可以使用JULIAN_DAY(),例如:

select d, avg(x) over (order by julian_day(d) 
                   range between 30 preceding 
                             and current row) 
from test 
order by d
Run Code Online (Sandbox Code Playgroud)

这会产生您期望的结果:

D          2                                
---------- ---------------------------------
01/01/2016   10.0000000000000000000000000000
01/07/2016   15.0000000000000000000000000000
01/12/2016   20.0000000000000000000000000000

  3 record(s) selected.
Run Code Online (Sandbox Code Playgroud)

在 10.5 的第一个修复包中,允许使用日期范围,但结果不可预测。在最近的修复包中,不再允许这样做,因此可以通过使用最近的修复包来避免问题中的大部分混乱。

  • Oracle 不将 DATE 视为“数值”。“日期”就是“日期”。Oracle 所做的是将“- 1”视为“-间隔 '1' 天”。 (3认同)