row_number() 在某些情况下得到错误的结果

row_number() gets wrong result in certain conditions

我有一个 SQL 查询在 SQL 服务器上完美运行,但它在 Oracle 上失败了,在我看来,它不应该。

这是重现它的例子:

CREATE TABLE TEST
   ( TEST_ID     NUMBER(37,0) NOT NULL,
     TEST_NAME   VARCHAR2(50 BYTE), 
     TEST_GROUP  VARCHAR2(20 BYTE), 
     CONSTRAINT TEST_PK PRIMARY KEY (TEST_ID) );

INSERT INTO TEST (TEST_ID, TEST_NAME) VALUES (1, 'TEST 1');
INSERT INTO TEST (TEST_ID, TEST_NAME, TEST_GROUP) VALUES (2, 'TEST 2', 'A');
INSERT INTO TEST (TEST_ID, TEST_NAME, TEST_GROUP) VALUES (3, 'TEST 3', 'B');
INSERT INTO TEST (TEST_ID, TEST_NAME, TEST_GROUP) VALUES (4, 'TEST 4', 'A');

本次查询returns预期信息:

SELECT TEST_GROUP, COUNT(R$), MIN(R$) R$_A, MAX(R$) R$_Z 
  FROM (
          SELECT MAIN.*, ROW_NUMBER() OVER (ORDER BY TEST_GROUP, TEST_ID) R$
            FROM ( SELECT TEST_ID, TEST_NAME, TEST_GROUP 
                   FROM TEST 
                   GROUP BY TEST_ID, TEST_NAME, TEST_GROUP 
                 ) MAIN
       ) MAIN
GROUP BY TEST_GROUP

它returns三个TEST_GROUPS计算正确。

TEST_GROUP     COUNT(R$)    R$_A    R$_Z
-------------- --------- ------- -------
A                      2       1       2
B                      1       3       3
(null)                 1       4       4

解释计划:

OPERATION                OBJECT_NAME     CARDINALITY     COST 

SELECT STATEMENT                                   4        3       
SORT (GROUP BY NOSORT)                             4        3               
VIEW                                               4        3                       
WINDOW (NOSORT)                                    4        3                               
SORT (GROUP BY)                                    4        3                                       
TABLE ACCESS (FULL)      TEST                      4        3 

Other XML 
{info} 

info type="db_version" 
12.1.0.1 

info type="parse_schema" 
"BABTEC" 

info type="dynamic_sampling" 
2 

info type="plan_hash" 
1486410247 

info type="plan_hash_2" 
1249517352 

{hint} 

FULL(@"SEL5DD26A" "TEST"@"SEL") 
NO_ACCESS(@"SEL" "MAIN"@"SEL") 
OUTLINE(@"SEL") 
OUTLINE(@"SEL") 
OUTLINE_LEAF(@"SEL") 
MERGE(@"SEL") 
OUTLINE_LEAF(@"SEL5DD26A") 
ALL_ROWS 
DB_VERSION('12.1.0.1') 
OPTIMIZER_FEATURES_ENABLE('12.1.0.1') 
IGNORE_OPTIM_EMBEDDED_HINTS 

但是,如果我们更改 ROW_NUMBER 中的排序(通过将默认的 ASC 更改为 DESC),它不会:

SELECT TEST_GROUP, COUNT(R$), MIN(R$) R$_A, MAX(R$) R$_Z 
FROM (
        SELECT MAIN.*, ROW_NUMBER() OVER (ORDER BY TEST_GROUP **DESC**, TEST_ID) R$
        FROM ( SELECT TEST_ID, TEST_NAME, TEST_GROUP 
               FROM TEST 
               GROUP BY TEST_ID, TEST_NAME, TEST_GROUP 
             ) MAIN
     ) MAIN
GROUP BY TEST_GROUP;

只有returns一个组。

TEST_GROUP     COUNT(R$)    R$_A    R$_Z
-------------- --------- ------- -------
A                      4       1       4

解释计划:

OPERATION                OBJECT_NAME     CARDINALITY     COST 

SELECT STATEMENT                                   4        3       
HASH(GROUP BY)                                     4        3               
VIEW                                               4        3                       
WINDOW (NOSORT)                                    4        3                               
SORT (GROUP BY)                                    4        3                                       
TABLE ACCESS (FULL)      TEST                      4        3 

Other XML 
{info} 

info type="db_version" 
12.1.0.1 

info type="parse_schema" 
"BABTEC" 

info type="dynamic_sampling" 
2 

info type="plan_hash" 
1128091058 

info type="plan_hash_2" 
3776505473 

{hint} 

FULL(@"SEL5DD26A" "TEST"@"SEL") 
NO_ACCESS(@"SEL" "MAIN"@"SEL") 
OUTLINE(@"SEL") 
OUTLINE(@"SEL") 
OUTLINE_LEAF(@"SEL") 
MERGE(@"SEL") 
OUTLINE_LEAF(@"SEL5DD26A") 
ALL_ROWS 
DB_VERSION('12.1.0.1') 
OPTIMIZER_FEATURES_ENABLE('12.1.0.1') 
IGNORE_OPTIM_EMBEDDED_HINTS 

请注意,要重现问题,要求最内部的查询具有 GROUP BY 表达式。如果不是,结果就是我们所期望的:

SELECT TEST_GROUP, COUNT(R$), MIN(R$) R$_A, MAX(R$) R$_Z 
FROM (
          SELECT MAIN.*, ROW_NUMBER() OVER (ORDER BY TEST_GROUP DESC, TEST_ID) R$
          FROM ( SELECT TEST_ID, TEST_NAME, TEST_GROUP 
                 FROM TEST ) MAIN
     ) MAIN
GROUP BY TEST_GROUP;

TEST_GROUP     COUNT(R$)    R$_A    R$_Z
----------------------------------------
(null)                 1       1       1
B                      1       2       2
A                      2       3       4

我们正在使用 Oracle Database 12c 版本 12.1.0.1.0 - 64 位

这个问题有一个解决方法,就是在 GROUP BY 之后添加一个 ORDER BY 子句,但这只在 Oracle 中有效,在 SQLServer 中失败。查询将是:

SELECT TEST_GROUP, COUNT(R$), MIN(R$) R$_A, MAX(R$) R$_Z 
FROM (
         SELECT MAIN.*, ROW_NUMBER() OVER (ORDER BY TEST_GROUP DESC, TEST_ID) R$
         FROM ( SELECT TEST_ID, TEST_NAME, TEST_GROUP 
                FROM TEST 
                GROUP BY TEST_ID, TEST_NAME, TEST_GROUP 
                ORDER BY TEST_GROUP DESC ) MAIN
     ) MAIN
GROUP BY TEST_GROUP;

TEST_GROUP     COUNT(R$)    R$_A    R$_Z
-------------- --------- ------- -------
(null)                 1       1       1
B                      1       2       2
A                      2       3       4

任何帮助将不胜感激

这好像是bug 18353141。如果设置 NLS_SORT 并且 NLS_COMP 设置为二进制,则它在 11.2.0.4 和 12.1.0.1 中可重现:

alter session set NLS_SORT=spanish;
alter session set NLS_COMP=binary;

-- your second query

T  COUNT(R$)       R$_A       R$_Z
- ---------- ---------- ----------
A          4          1          4

将排序更改为语言修复它:

alter session set NLS_SORT=spanish;
alter session set NLS_COMP=linguistic;

-- your second query

T  COUNT(R$)       R$_A       R$_Z
- ---------- ---------- ----------
           1          1          1
B          1          2          2
A          2          3          4

也可以修改query,使解析的order-by和group-by不同;例如这只是连接空值(在 DESC 之前):

alter session set NLS_SORT=spanish;
alter session set NLS_COMP=binary;

SELECT TEST_GROUP, COUNT(R$), MIN(R$) R$_A, MAX(R$) R$_Z 
  FROM (
        SELECT MAIN.*, ROW_NUMBER() OVER (ORDER BY TEST_GROUP||null DESC, TEST_ID) R$
-------------------------------------------------------------^^^^^^
          FROM ( SELECT TEST_ID, TEST_NAME, TEST_GROUP 
                   FROM TEST 
               GROUP BY TEST_ID, TEST_NAME, TEST_GROUP 
               ) MAIN
       ) MAIN
GROUP BY TEST_GROUP;

T  COUNT(R$)       R$_A       R$_Z
- ---------- ---------- ----------
           1          1          1
B          1          2          2
A          2          3          4

但听起来您希望同一个查询在 SQL Server 和 Oracle 中都有效,因此您需要找到一种对两者都有效的修改方法。

它已在 12.1.0.2 补丁集中修复,如果您无法应用补丁集,可以为 12.1.0.1 提供单独的补丁。