【问题标题】:How to optimize JOIN in Oracle in case we are interested only on first matching child row?如果我们只对第一个匹配的子行感兴趣,如何在 Oracle 中优化 JOIN?
【发布时间】:2018-09-04 17:50:58
【问题描述】:

我有父表,可以说 P,带有 Id、Name 和 Type。

P(
  id (pk),
  name,
  type (type in (1..10) )
)

我有一个引用 P 的子表,假设该表称为 B:

B (
  id,
  date,
  other columns,
  parent_id
)

现在问题来了:表P 没有那么多记录,但表B 确实很大。 如果是Type = 3,则B 中的日期对于父记录将始终相同。

所以我想从Ptype = 3 中获取所有记录,并从表B 中获取相应的日期。考虑到 B 很大,最好的方法是什么?

我想写一些类似的东西

Select b.parent_id, p.name, max(b.date)
from B b
join P p on p.id = b.parent_id
group by b.parent_id, p.name
where p.type = 3

此处max(b.date)min(b.date) 无关紧要,因为日期相同。 但是表B 很大。 仅从P 中选择并加入B 不是更好吗,例如“具有最小 id 的子行”,或者基本上是任何子行?

Select p.id, p.name, b.date
from P p
join B b on (p.id = b.parent_id and "take only first matching row")
where p.type = 3

【问题讨论】:

  • 在 Oracle 12c 中使用 FETCH NEXT 1 ROWS ONLY 从 B 中仅获取 1 行。
  • 非常感谢,我们有 Oracle 12。你能写出这个 Join 的样子吗?或者可能是整个查询?
  • B 有多“巨大”? B 上的索引是什么?
  • B 有大约 1 亿行。 P 有 ~ 6,000 行。

标签: sql oracle query-optimization


【解决方案1】:

根据要求,并假设您在b (parent_id) 上有一个索引。如果您拥有的数据是:

create table p (
  id number(6) primary key not null,
  name varchar2(20),
  type number(2)
);

insert into p (id, name, type) values (10, 'John', 2);
insert into p (id, name, type) values (11, 'Peter', 3);
insert into p (id, name, type) values (12, 'Albert', 3);
insert into p (id, name, type) values (13, 'Mary', 4);
insert into p (id, name, type) values (14, 'Diego', 3);

create table b (
  id number(6),
  date1 date,
  parent_id number(6),
  constraint fk1_b foreign key (parent_id) references p (id)
);

create index ix1_b on b (parent_id);

insert into b (id, date1, parent_id) values (101, timestamp '2018-01-01 12:34:56', 10);
insert into b (id, date1, parent_id) values (102, timestamp '2018-01-02 12:34:57', 11);
insert into b (id, date1, parent_id) values (103, timestamp '2018-01-03 12:34:58', 11);
insert into b (id, date1, parent_id) values (104, timestamp '2018-01-04 12:34:59', 11);
insert into b (id, date1, parent_id) values (105, timestamp '2018-01-04 12:55:10', 12);
insert into b (id, date1, parent_id) values (106, timestamp '2018-01-04 12:55:11', 12);
insert into b (id, date1, parent_id) values (107, timestamp '2018-01-04 12:55:12', 12);

查询将是:

select p.*, bl.date1
  from p,
  lateral (
    select * from b where b.parent_id = p.id fetch next 1 rows only
  ) bl
  where p.type = 3;

结果:

ID  NAME    TYPE  DATE1
--  ------  ----  ---------------------
11  Peter   3     2018-01-02 12:34:57.0
12  Albert  3     2018-01-04 12:55:10.0

现在,如果p 中有行而b 中没有任何行(在我的示例中为“Diego”),您将需要外连接:

select p.*, bl.date1
  from p
  outer apply (
    select * from b where b.parent_id = p.id fetch next 1 rows only
  ) bl
  where p.type = 3;

结果:

ID  NAME    TYPE  DATE1
--  ------  ----  ---------------------
11  Peter   3     2018-01-02 12:34:57.0
12  Albert  3     2018-01-04 12:55:10.0
14  Diego   3     <null>

干杯!

【讨论】:

  • nb lateralouter apply 不适用于所有 Oracle 版本,它们在 12c 中可用
  • 我印象深刻 :) 我们有 12c 版本,但我以前从未见过“横向”或“外部应用”。 “外部应用”与左连接相同吗?它是不是只有 Oracle 12c 才有的新东西?
  • 欢迎来到现在! ;) LATERAL(又名CROSS APPLY)和OUTER APPLY 是加入相关“表表达式”的[较新]方式。据我所知,它们可用于 Oracle、PostgreSQL、DB2 和 SQL Server。 Informix 可能有它们,但我没有在那里看到它们。对于某些特殊情况非常有用,例如您的情况。
【解决方案2】:

试一试:

Select p.id, p.name, (select b.date from B where p.id = b.parent_id and rownum = 1) date
from P p
where p.type = 3

【讨论】:

    猜你喜欢
    • 2014-12-31
    • 1970-01-01
    • 2022-01-11
    • 2011-01-07
    • 2020-01-10
    • 1970-01-01
    • 2020-02-10
    • 1970-01-01
    • 2015-05-26
    相关资源
    最近更新 更多