嵌套 select 中的 Oracle 11g order by 和 rownum 不适用于内部联接

Oracle 11g order by and rownum in nested select does not work with inner join

我想要 return 两列,其中第二列是通过外国 ID 链接的用户名。我担心在州内可能存在如此多条不同的记录,所以我想按州排序(自定义优先级)并找到第一个。

这是编辑前的工作查询。

select entry.ID,
    (select username.USERNAME from USER_CREDENTIALS username
     inner join CREDENTIALS cred on username.ID = cred.ID
     where cred.SUBJECT_ID=entry.SUBJECTID
    )
from ENTRY entry
where entry.ID in (1, 2, 5);

我的最终目标是确保内部 select return 最多有一条记录(null 中没有记录结果很好)。所以在这里我使用 rownumORDER BY 状态*自定义优先级)关于 rownumORDER By 在描述 here 的同一查询中的问题(来源sqlandplsql.com)。我最终得到了这样的结果:

select entry.ID,
       (select * from
           (select username.USERNAME from USER_CREDENTIALS username
            inner join CREDENTIALS cred on username.ID = cred.ID
            where cred.SUBJECT_ID=entry.SUBJECTID
            order by case when cred.STATE = 'A' then 1 else 2 end
           )
       where rownum=1
       )
from ENTRY entry
where entry.ID in (1, 2, 5);

抛出异常:

[42000][904] ORA-00904: "ENTRY"."SUBJECTID": invalid identifier

我怀疑只有这样的属性只对第一级嵌套可见select。但是,我需要通过另一个不符合 "ENTRY"."SUBJECTID" 可见性的 select 使用 rownum-ORDER BY 组合。

如何重写队列以消除错误并使其 rownum-ORDER BY 安全?

Oracle(通常)不允许在多于一层的嵌套中使用关联子句。相反,您可以使用 keep:

select e.ID,
       (select max(uc.USERNAME) keep (dense_rank first order by (case when c.STATE = 'A' then 1 else 2 end))
        from USER_CREDENTIALS uc join
             CREDENTIALS c 
             on uc.ID = c.ID
        where c.SUBJECTID = e.SUBJECTID
       )
from ENTRY e
where e.ID in (1, 2, 5);

keep 是 Oracle 用于“第一个”聚合函数的奇特语法。

您不能引用已删除多个子查询的标识符。在您的情况下,您在嵌套两层的子查询中引用 ENTRY.SUBJECTID 并且看不到标识符。相反,您需要将过滤器移动到外部子查询:

选项 1

select e.ID,
       (  select USERNAME
          from   (
            select c.SUBJECT_ID,
                   u.USERNAME
            from   USER_CREDENTIALS u
                   inner join CREDENTIALS c
                   on ( u.ID = c.ID )
            order by case when STATE = 'A' then 1 else 2 end
          ) t
          WHERE t.SUBJECT_ID = e.SUBJECTID
          AND   rownum=1
       ) AS username
from   ENTRY e
where  e.ID in (1, 2, 5);

选项 2

或者使用ROW_NUMBER解析函数:

select e.ID,
       (  select USERNAME
          from   (
            select c.SUBJECT_ID,
                   u.USERNAME,
                   ROW_NUMBER() OVER (
                     PARTITION BY c.SUBJECT_ID
                     ORDER BY case when STATE = 'A' then 1 else 2 end
                   ) As rn
            from   USER_CREDENTIALS u
                   inner join CREDENTIALS c
                   on ( u.ID = c.ID )
          ) t
          WHERE t.SUBJECT_ID = e.SUBJECTID
          AND   rn=1
       ) AS username
from   ENTRY e
where  e.ID in (1, 2, 5);

其中,对于示例数据:

CREATE TABLE entry ( id, subjectid ) AS
SELECT LEVEL, LEVEL FROM DUAL CONNECT BY LEVEL <= 5;

CREATE TABLE credentials ( id, subject_id ) AS
SELECT LEVEL, LEVEL FROM DUAL CONNECT BY LEVEL <= 5;

CREATE TABLE user_credentials ( id, username, state ) AS
SELECT LEVEL, 'NameA' || LEVEL, 'A' FROM DUAL CONNECT BY LEVEL <= 4 UNION ALL
SELECT LEVEL, 'NameB' || LEVEL, 'B' FROM DUAL CONNECT BY LEVEL <= 5;

双输出:

ID | USERNAME
-: | :-------
 1 | NameA1  
 2 | NameA2  
 5 | NameB5  

db<>fiddle here

如果您使用的是 Oracle 12,则可以使用 FETCH FIRST ROW ONLY 语法:

select e.ID,
       (  select u.USERNAME
          from   USER_CREDENTIALS u
                 inner join CREDENTIALS c
                 on ( u.ID = c.ID )
          WHERE t.SUBJECT_ID=e.SUBJECTID
          order by case when STATE = 'A' then 1 else 2 end
          FETCH FIRST ROW ONLY
       )
from   ENTRY e
where  e.ID in (1, 2, 5);

(与ROW_NUMBER解析解相同EXPLAIN PLAN

db<>fiddle here