连接条件中的 Rownum

Posted

技术标签:

【中文标题】连接条件中的 Rownum【英文标题】:Rownum in the join condition 【发布时间】:2011-07-06 22:48:03 【问题描述】:

最近我修复了一些错误:连接条件中有rownum。

类似这样:在 t1.id=t2.id 和 rownum

当我进一步研究这一点时,我意识到我不明白 Oracle 如何在“左连接”条件下评估 rownum。 让我们创建两个示例表:主表和明细表。

create table MASTER
(
  ID   NUMBER not null,
  NAME VARCHAR2(100)
)
;
alter table MASTER
  add constraint PK_MASTER primary key (ID);

prompt Creating DETAIL...
create table DETAIL
(
  ID            NUMBER not null,
  REF_MASTER_ID NUMBER,
  NAME          VARCHAR2(100)
)
;
alter table DETAIL
  add constraint PK_DETAIL primary key (ID);
alter table DETAIL
  add constraint FK_DETAIL_MASTER foreign key (REF_MASTER_ID)
  references MASTER (ID);

prompt Disabling foreign key constraints for DETAIL...
alter table DETAIL disable constraint FK_DETAIL_MASTER;
prompt Loading MASTER...
insert into MASTER (ID, NAME)
values (1, 'First');
insert into MASTER (ID, NAME)
values (2, 'Second');
commit;
prompt 2 records loaded
prompt Loading DETAIL...
insert into DETAIL (ID, REF_MASTER_ID, NAME)
values (1, 1, 'REF_FIRST1');
insert into DETAIL (ID, REF_MASTER_ID, NAME)
values (2, 1, 'REF_FIRST2');
insert into DETAIL (ID, REF_MASTER_ID, NAME)
values (3, 1, 'REF_FIRST3');
commit;
prompt 3 records loaded
prompt Enabling foreign key constraints for DETAIL...
alter table DETAIL enable constraint FK_DETAIL_MASTER;
set feedback on
set define on
prompt Done.

然后我们有这个查询:

select * from master t
left join detail d on d.ref_master_id=t.id

结果集是可预测的:我们有来自主表的所有行和来自明细表的 3 行匹配此条件 d.ref_master_id=t.id。

Result Set

然后我将“rownum=1”添加到连接条件中,结果是一样的

select * from master t
left join detail d on d.ref_master_id=t.id and rownum=1

最有趣的是我设置了“rownum

select * from master t
left join detail d on d.ref_master_id=t.id and rownum<-666.

由于结果集的原因,我们可以说对于明细表中的 3 行,此条件被评估为“真”。但如果我使用“内部连接”,一切都会按预期进行。

select * from master t
join detail d on d.ref_master_id=t.id and rownum<-666.

这个查询没有返回任何行,因为我无法想象 rownum 会小于 -666 :-)

此外,如果我使用 oracle 语法进行外连接,使用“(+)”也一切顺利。

select * from master m ,detail t
 where m.id=t.ref_master_id(+) and rownum<-666.

此查询也不返回任何行。

谁能告诉我,我对外部联接和 rownum 有什么误解?

【问题讨论】:

【参考方案1】:

ROWNUM 是结果集的伪属性,而不是基表的伪属性。 ROWNUM 在选择行之后定义,但在它们按 ORDER BY 子句排序之前。

编辑:我之前写的 ROWNUM 有误,所以这里有新信息:

您可以在WHERE 子句中以有限的方式使用ROWNUM,仅用于测试它是否小于一个正整数。详情请见ROWNUM Pseudocolumn。

SELECT ... WHERE ROWNUM < 10

不清楚 ROWNUM 在 JOIN 子句的上下文中具有什么值,因此结果可能未定义。似乎有一些带有 ROWNUM 的表达式的特殊情况处理,例如 WHERE ROWNUM &gt; 10 总是返回 false。我不知道ROWNUM&lt;-666 在您的 JOIN 子句中是如何工作的,但它没有意义,所以我不建议使用它。

在任何情况下,这都无法帮助您获取每个给定主行的第一个详细信息行。

要解决此问题,您可以使用analytic functions and PARTITION,并将其与Common Table Expressions 结合使用,这样您就可以在进一步的WHERE 条件下访问行号列。

WITH numbered_cte AS (
  SELECT *, ROW_NUMBER() OVER (PARTITION BY t.id ORDER BY d.something) AS rn
  FROM master t LEFT OUTER JOIN detail d ON d.ref_master_id = t.id
) 
  SELECT *
  FROM numbered_cte
  WHERE rn = 1;

【讨论】:

谢谢你的回答,比尔。但我很想知道为什么“rownum 对以上内容进行了重大修改。 IMO 获取第一个详细信息行的更简单方法是使用 Oracle 的 rank() 函数,例如 SELECT *, rank() OVER (ORDER BY d.id) d_rank FROM master m, detail d WHERE d_rank = 1。不过,我不是数据库专家,也不知道这意味着性能方面的意义 @StefanHaberl,WHERE 子句不能引用在同一查询中定义的别名。这就是为什么我们需要使用WITH CTE、派生表或视图的原因。 @BillKarwin,感谢您指出这一点-我要去那里。正确的版本当然会包括一个嵌套的子选择:SELECT * FROM (SELECT *, rank() OVER (ORDER BY d.id) d_rank FROM master m, detail d) WHERE d_rank = 1(或者你指出的WITH【参考方案2】:

如果您想从连接条件中获取前三个值,请像这样更改 select 语句。

    select * 
    from (select * 
          from master t left join detail d on d.ref_master_id=t.id)
    where rownum<3;

您将获得所需的输出。使用 * 时注意明确定义的列名

让我给出一个绝对的答案,你可以直接运行而不需要对代码进行任何更改。

    select * 
    from (select t.id,t.name,d.id,d.ref_master_id,d.name 
          from master t left join detail d on d.ref_master_id=t.id)
    where rownum<3;

【讨论】:

【参考方案3】:

ROWNUM 过滤器在连接中没有任何意义,但不会因为无效而被拒绝。

解释计划将包括 ROWNUM 过滤器或排除它。如果它包含它,它将在应用其他连接条件后将过滤器应用于详细信息表。因此,如果您输入 ROWNUM=100(永远不会满足),则所有详细信息行都将被排除,然后外部连接就会启动。

如果您输入 ROWNUM=1,它似乎会删除过滤器。

如果你查询

with 
 a as (select rownum a_val from dual connect by level < 10),
 b as (select rownum*2 b_val from dual connect by level < 10)
select * from a left join b on a_val < b_val and rownum in (1,3);

你得到了一些完全奇怪的东西。

它可能应该作为一个错误被拒绝,所以期待一些荒谬的事情发生

【讨论】:

以上是关于连接条件中的 Rownum的主要内容,如果未能解决你的问题,请参考以下文章

Oracle中的伪列

Oracle中的rownum,ROWID的 用法

Oracle中的rownum

(转)Oracle中的rownum,ROWID的 用法

Oracle中的rownum 和rowid的用法和区别

sql中的inner join ,left join ,right join