Oracle LAST_VALUE 仅在分析子句中使用 order by
Posted
技术标签:
【中文标题】Oracle LAST_VALUE 仅在分析子句中使用 order by【英文标题】:Oracle LAST_VALUE only with order by in analytic clause 【发布时间】:2019-03-09 12:09:09 【问题描述】:我有架构(Oracle 11g R2):
CREATE TABLE users (
id INT NOT NULL,
name VARCHAR(30) NOT NULL,
num int NOT NULL
);
INSERT INTO users (id, name, num) VALUES (1,'alan',5);
INSERT INTO users (id, name, num) VALUES (2,'alan',4);
INSERT INTO users (id, name, num) VALUES (3,'julia',10);
INSERT INTO users (id, name, num) VALUES (4,'maros',77);
INSERT INTO users (id, name, num) VALUES (5,'alan',1);
INSERT INTO users (id, name, num) VALUES (6,'maros',14);
INSERT INTO users (id, name, num) VALUES (7,'fero',1);
INSERT INTO users (id, name, num) VALUES (8,'matej',8);
INSERT INTO users (id, name, num) VALUES (9,'maros',55);
我执行以下查询 - 仅使用 LAST_VALUE
分析函数和 ORDER BY
分析子句:
我的假设是这个查询在一个分区上执行——整个表(因为缺少 partition by 子句)。它将在给定分区(整个表)中按名称对行进行排序,并将使用默认窗口子句RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
。
select us.*,
last_value(num) over (order by name) as lv
from users us;
但是上面执行的查询将给出与下面的完全相同的结果。我对第二个查询的假设是,该查询首先按名称对表行进行分区,然后按 num 对每个分区中的行进行排序,然后在每个分区上应用窗口子句 RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
以获得 LAST_VALUE
。
select us.*,
last_value(num) over (partition by name order by num RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) as lv
from users us;
我的一个假设显然是错误的,因为上述两个查询给出了相同的结果。看起来第一个查询订单记录也是幕后的 num。您能否指出我的假设有什么问题以及为什么这些查询返回相同的结果?
【问题讨论】:
【参考方案1】:答案很简单。无论出于何种原因,当在窗口子句中使用 logical (RANGE
) 偏移量时(显式或隐式 - 默认情况下),Oracle 选择使 LAST_VALUE
具有确定性。具体来说,在这种情况下,测量表达式的 HIGHEST 值是从由order by
排序绑定的一组行中选择的。
https://docs.oracle.com/en/database/oracle/oracle-database/12.2/sqlrf/LAST_VALUE.html#GUID-A646AF95-C8E9-4A67-87BA-87B11AEE7B79
在 Oracle 文档中该页面的底部,我们可以阅读:
当发现
的最大值ORDER BY
表达式重复时,LAST_VALUE
是 expr [...]
为什么文档在 examples 部分中这么说,而不是在函数的解释中?因为,通常情况下,文档似乎不是由合格的人编写的。
【讨论】:
the documentation doesn't seem to be written by qualified people.
... mysql(也属于 Oracle)的文档也偶尔会出现问题。更大的问题是文档非常庞大,而他们拥有的给定规模的团队只能处理这么多。
@TimBiegeleisen - 这可能是真的。但是第一次将某些内容放在示例部分(在其他任何地方都没有提及)表明缺乏资格,除了您提到的其他内容之外,这可能也是正确的。
@mathguy 为什么没有指定“分区依据”时所有行的 LV=8?不应该考虑所有行,按名称和从最后一行获取的值排序,在本例中是 name='matej' 的行,所以 LV 将是 8?
@Marko - 您指的是 OP 的第一个查询吗?如果您不指定分区并且您指定range between unbounded preceding and unbounded following
,您所说的应该会发生 - 您是否尝试过这种方式?默认的窗口子句(在 OP 的第一个查询中使用)是 range between unbounded preceding and current row
,这不是一回事。【参考方案2】:
Here 是一个 dbfiddle,以防有人想和他们一起玩。
假设您认为第二个查询返回了正确的结果。
select us.*,
last_value(num) over (partition by name
order by num
RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) as lv
from users us;
我还要指出,这更简洁地写成:
select us.*,
max(num) over (partition by name
order by num
) as lv
from users us;
这与你的问题无关,但我想指出。
现在,为什么会给出相同的结果?
select us.*,
last_value(num) over (order by name) as lv
from users us;
好吧,没有窗口子句,这相当于:
select us.*,
last_value(num) over (order by name
range between unbounded preceding and current row
) as lv
from users us;
range
在这里非常重要。它不会转到当前行。它转到name
中具有相同值的所有行。
根据我对 order by
相关文档的理解,any num
值可以从具有相同名称的行中选择。为什么? SQL(和 Oracle)中的排序不稳定。这意味着不能保证保留行的原始顺序。
在这种特殊情况下,最后一个值恰好是最大值可能是巧合。或者,出于某种原因,Oracle 可能会出于某种原因将num
添加到排序中。
【讨论】:
感谢您的回答。我已经进行了多次尝试,这似乎不是巧合,因为始终选择正确的值作为 last_value。我同意似乎由于某种原因执行了按 num 排序,但找不到任何官方信息来支持这一说法。顺便提一句。您的第二个简洁编写的查询不会返回与第一个相同的行集。您必须省略“order by”,因为这会引入默认窗口(并且在使用“max”分析函数时也没有效果) @EddGarcia 。 . .我意识到这一点。我无法解释这种行为。然而,“正确值”不是最高值;相同名称的任何值都是等效的。选择最高值似乎是 Oracle 工作方式的产物。为此,我强烈建议您使用MAX()
。
“在我的阅读中”,你说。读书,从哪里来?之后你所说的非常明智(一个非常好的假设),但你没有在任何地方读过它。事实上,Oracle 文档的说法恰恰相反。当在窗口子句中使用range
时,Oracle 选择 使函数确定性。他们通过始终从绑定的行中选择最大的值来做到这一点。
@mathguy 。 . .你有这方面的参考吗?
是的,我刚刚发布了。【参考方案3】:
来自 Oracle 杂志中的 this blog,如果您在窗口函数中使用 ORDER BY
子句而不指定其他任何内容,会发生以下情况:
一个 ORDER BY 子句,在没有任何进一步的窗口子句参数的情况下,有效地添加了一个默认的窗口子句:RANGE UNBOUNDED PRECEDING,这意味着,“当前分区中的当前行和以前的行是应该在计算。”当 ORDER BY 子句没有伴随 PARTITION 子句时,分析函数使用的整个行集都是默认的当前分区。
所以,你的第一个查询实际上是这样的:
SELECT us.*, LAST_VALUE(num) OVER (ORDER BY name RANGE UNBOUNDED PRECEDING) AS lv
FROM users us;
如果您运行上述查询,您将获得您所看到的当前行为,这将为每个名称返回一个单独的最后一个值。这与以下查询不同:
SELECT
us.*,
LAST_VALUE(num) OVER (ORDER BY name
RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS lv
FROM users us;
这只是为num的最后一个值生成值8
,它对应于matej
的值,matej
是姓名升序排序时的姓。
【讨论】:
所有这些可能都是正确的,但它甚至与 OP 的问题没有任何重叠,即:为什么第一个查询返回 出现的结果使用决胜局——就好像order by
子句是order by name, num
。这个问题有一个非常简单的答案,但不是你给出的答案。
@mathguy and it will use default windowing clause RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
...我的回答实际上确实解决了 OP 的一些疑问。他引用的窗口不是默认窗口,它解释了 OP 当前的观察结果。
错误...你让事情变得更糟了。 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
是默认的窗口子句。 RANGE UNBOUNDED PRECEDING
是相同的简写,但 Oracle 文档实际上使用简写:如果您完全省略 windowing_clause,则默认为 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
。 例如此处:docs.oracle.com/cd/E11882_01/server.112/e41084/…以上是关于Oracle LAST_VALUE 仅在分析子句中使用 order by的主要内容,如果未能解决你的问题,请参考以下文章
Oracle分析函数-first_value()和last_value()