【问题标题】:Oracle MIN as analytic function - odd behavior with ORDER BY?Oracle MIN 作为分析函数 - ORDER BY 的奇怪行为?
【发布时间】:2011-11-15 16:14:35
【问题描述】:

这个特殊情况是从一个例子中提炼出来的,在这个例子中,程序员假设将两批货物装入一辆油罐车,第 1 行将首先装载。我更正了这一点以允许以任何顺序执行加载 - 但是,我发现 MIN() OVER (PARTITION BY) 在 Oracle 中允许 ORDER BY (这在 SQL Server 中是不允许的),此外,它还改变了函数的行为,导致ORDER BY 显然被添加到PARTITION BY

WITH data AS (
SELECT 1 AS SHIPMENT_ID, 1 AS LINE_NUMBER, 2 AS TARE, 3 AS GROSS FROM DUAL
UNION ALL
SELECT 1 AS SHIPMENT_ID, 2 AS LINE_NUMBER, 1 AS TARE, 2 AS GROSS FROM DUAL
)
SELECT MIN(tare) OVER (PARTITION BY shipment_id) first_tare
,MAX(gross) OVER (PARTITION BY shipment_id) last_gross
,FIRST_VALUE(tare) OVER (PARTITION BY shipment_id ORDER BY LINE_NUMBER) first_tare_incorrect
,FIRST_VALUE(gross) OVER (PARTITION BY shipment_id ORDER BY LINE_NUMBER DESC) last_gross_incorrect
,MIN(tare) OVER (PARTITION BY shipment_id ORDER BY LINE_NUMBER) first_tare_incorrect_still
,MAX(gross) OVER (PARTITION BY shipment_id ORDER BY LINE_NUMBER DESC) last_gross_incorrect_still
,MIN(tare) OVER (PARTITION BY shipment_id, LINE_NUMBER) first_tare_incorrect_still2
,MAX(gross) OVER (PARTITION BY shipment_id, LINE_NUMBER) last_gross_incorrect_still2
 FROM data

一个 SQL Server 示例(注释掉了不适用的代码):

WITH data AS (
SELECT 1 AS SHIPMENT_ID, 1 AS LINE_NUMBER, 2 AS TARE, 3 AS GROSS -- FROM DUAL
UNION ALL
SELECT 1 AS SHIPMENT_ID, 2 AS LINE_NUMBER, 1 AS TARE, 2 AS GROSS -- FROM DUAL
)
SELECT MIN(tare) OVER (PARTITION BY shipment_id) first_tare
,MAX(gross) OVER (PARTITION BY shipment_id) last_gross
-- ,FIRST_VALUE(tare) OVER (PARTITION BY shipment_id ORDER BY LINE_NUMBER) first_tare_incorrect
-- ,FIRST_VALUE(gross) OVER (PARTITION BY shipment_id ORDER BY LINE_NUMBER DESC) last_gross_incorrect
-- ,MIN(tare) OVER (PARTITION BY shipment_id ORDER BY LINE_NUMBER) first_tare_incorrect_still
-- ,MAX(gross) OVER (PARTITION BY shipment_id ORDER BY LINE_NUMBER DESC) last_gross_incorrect_still
,MIN(tare) OVER (PARTITION BY shipment_id, LINE_NUMBER) first_tare_incorrect_still2
,MAX(gross) OVER (PARTITION BY shipment_id, LINE_NUMBER) last_gross_incorrect_still2
 FROM data

所以问题是:Oracle 在做什么以及为什么这样做以及是否正确?

【问题讨论】:

  • @Martin Smith 是的,它的行为应该是相同的,ORDER BY 应该会影响行为。我不明白为什么 Oracle 不像 SQL Server 那样拒绝 ORDER BY,然后为什么它会影响行为。
  • 看看这个链接geniiius.com/blog/t-sql-enhancements-over-clause 它解释了你在 SQL Server Denali 上询问的行为,以及它为什么有意义

标签: sql sql-server oracle oracle10g analytic-functions


【解决方案1】:

如果您将ORDER BY 添加到MIN 分析函数,您会将其转换为“到目前为止的最小值”函数,而不是整体最小值。对于您分区的最后一行,结果将是相同的。但之前的行可能具有与总体最小值不同的“最小值”。

EMP 表为例,您可以看到该部门迄今为止的最低工资最终收敛于该部门的总体最低工资。您可以看到,任何给定部门的“到目前为止的最小值”值都会随着遇到较低的值而减小。

SQL> ed
Wrote file afiedt.buf

  1  select ename,
  2         deptno,
  3         sal,
  4         min(sal) over (partition by deptno order by ename) min_so_far,
  5         min(sal) over (partition by deptno) min_overall
  6    from emp
  7*  order by deptno, ename
SQL> /

ENAME          DEPTNO        SAL MIN_SO_FAR MIN_OVERALL
---------- ---------- ---------- ---------- -----------
CLARK              10       2450       2450        1300
KING               10       5000       2450        1300
MILLER             10       1300       1300        1300
ADAMS              20       1110       1110         800
FORD               20       3000       1110         800
JONES              20       2975       1110         800
SCOTT              20       3000       1110         800
smith              20        800        800         800
ALLEN              30       1600       1600         950
BLAKE              30       2850       1600         950
MARTIN             30       1250       1250         950
SM0                30        950        950         950
TURNER             30       1500        950         950
WARD               30       1250        950         950
BAR
PAV

16 rows selected.

当然,当您尝试计算个人最好成绩等可以在未来时期用作比较的事情时,使用这种形式的分析函数会更有意义。如果您要跟踪一个人的高尔夫球成绩、英里时间或体重的下降情况,那么展示个人最好成绩可能是一种激励。

SQL> ed
Wrote file afiedt.buf

  1  with golf_scores as
  2  (  select 1 golfer_id, 80 score, sysdate dt from dual union all
  3     select 1, 82, sysdate+1 dt from dual union all
  4     select 1, 72, sysdate+2 dt from dual union all
  5     select 1, 75, sysdate+3 dt from dual union all
  6     select 1, 71, sysdate+4 dt from dual union all
  7     select 2, 74, sysdate from dual )
  8  select golfer_id,
  9         score,
 10         dt,
 11         (case when score=personal_best
 12               then 'New personal best'
 13               else null
 14           end) msg
 15    from (
 16  select golfer_id,
 17         score,
 18         dt,
 19         min(score) over (partition by golfer_id
 20                              order by dt) personal_best
 21    from golf_scores
 22*        )
SQL> /

 GOLFER_ID      SCORE DT        MSG
---------- ---------- --------- -----------------
         1         80 12-SEP-11 New personal best
         1         82 13-SEP-11
         1         72 14-SEP-11 New personal best
         1         75 15-SEP-11
         1         71 16-SEP-11 New personal best
         2         74 12-SEP-11 New personal best

6 rows selected.

【讨论】:

  • 我的第一个脚本似乎没有这样做。其他 MIN 和 MAX 永远不会同时匹配整体 MIN 和 MAX。
  • @Cade - 我相信您的第一个脚本可以做到这一点。 FIRST_TARE 为您提供每个 SHIPMENT_ID 的最小值 TARE,这就是您想要的。 FIRST_TARE_INCORRECT_STILL 为您提供迄今为止遇到的每个 SHIPMENT_ID 的最小值 TARE。第 1 行的“min so far”是 2,因为这是它看到的唯一值。第 2 行的“到目前为止的最小值”为 1。FIRST_TARE_INCORRECT_STILL2 为每个 SHIPMENT_ID, LINE_NUMBER 提供了最小值 TARE。因为这可能是唯一的,所以只会返回该行的TARE
  • 而 DESC 只是让它变得更加混乱,因为它翻转并且实际上是相反的?我想这是有道理的。
  • @Cade - 对。颠倒排序顺序会使MAX 返回迄今为止最大的GROSS SHIPMENT_ID 从最大的LINE_NUMBER 开始并向后工作。这很难理解,但甲骨文似乎正在按照您的要求行事。
  • 好吧,我不会再要求它这样做了!
猜你喜欢
  • 1970-01-01
  • 2020-01-22
  • 2014-10-12
  • 2018-02-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-07-06
  • 1970-01-01
相关资源
最近更新 更多