【问题标题】:Oracle FULL OUTER JOIN three tables with two conditionsOracle FULL OUTER JOIN 三张表有两个条件
【发布时间】:2019-01-27 16:37:01
【问题描述】:

背景

Oracle 数据库版本:

SELECT * FROM v$version
WHERE banner LIKE 'Oracle%';
-- OUTPUT
Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production

目标

我正在尝试将 三个表两个条件 进行外部连接,以便缺失值仅显示为 NULL。请参阅下面的详细信息。

表格

以下表格是抽象的,所以请不要试图改进数据模型本身。

测量

主键 = ID

|  ID  |    MEAS_NAME    |
|------|-----------------|
| 1000 | "Measurement 1" |

MEASUREMENT_AREA

主键 = (ID, NAME)
外键ID = MEASUREMENT.ID

|  ID  |    NAME   | AREA |
|------|-----------|------|
| 1000 | "Point 1" |   10 |
| 1000 | "Point 2" |   20 |

MEASUREMENT_VOLUME

主键 = (ID, NAME)
外键ID = MEASUREMENT.ID

|  ID  |    NAME   | VOLUME |
|------|-----------|--------|
| 1000 | "Point 1" |    100 |
| 1000 | "Point 3" |    200 |

预期结果

我想要的是以下输出:

|  ID  |    MEAS_NAME    |    NAME   | AREA | VOLUME |
|------|-----------------|-----------|------|--------|
| 1000 | "Measurement 1" | "Point 1" | 10   | 100    |
| 1000 | "Measurement 1" | "Point 2" | 20   | NULL   |
| 1000 | "Measurement 1" | "Point 3" | NULL | 200    |

这意味着,如果对于特定的 MEASUREMENT.ID 和特定的 NAMEAREAVOLUME 中都有数据,则将它们放在同一行中。否则,只需将 AREAVOLUME 字段留空即可。

查询 1

我想出了以下 SQL 语句,它不起作用,它丢弃了来自 MEASUREMENT_VOLUME 的结果:

SELECT meas.ID AS "ID",
    meas.MEAS_NAME AS "MEAS_NAME",
    COALESCE (area.NAME, vol.NAME) as "NAME",
    area.AREA, vol.VOLUME
FROM MEASUREMENT meas
  LEFT JOIN MEASUREMENT_AREA area
    ON meas.ID = area.ID
  FULL JOIN MEASUREMENT_VOLUME vol
    ON meas.ID = vol.ID AND area.NAME = vol.NAME
WHERE meas.ID = 1000;

查询 2

如果我把MEASUREMENT放在最后,它可以工作,但查询非常慢

SELECT meas.ID AS "ID",
    meas.MEAS_NAME AS "MEAS_NAME",
    COALESCE (area.NAME, vol.NAME) as "NAME",
    area.AREA, vol.VOLUME
FROM MEASUREMENT_AREA area
    FULL JOIN MEASUREMENT_VOLUME vol
        ON area.ID = vol.ID AND area.NAME = vol.NAME
    JOIN MEASUREMENT meas
        ON meas.ID = vol.ID OR meas.ID = area.ID
WHERE meas.ID = 1000;

问题

  • 为什么查询 1 不起作用?
  • 为什么查询 2 有效?
  • 实现输出的最有效方法是什么?

非常感谢您的帮助,我不是 SQL 专家。

其他信息

  • MEASUREMENT 中的一行仅包含一次测量的元数据
  • 一次测量可以包含数百个测量点,这些点由它们的 `NAME 来区分。
  • MEASUREMENT_AREAMEASUREMENT_VOLUMEMEASUREMENT 大得多,每个都包含超过 1000 万行

【问题讨论】:

  • 你能试试这个查询吗? SELECT meas.ID, meas.MEAS_NAME, area.NAME, area.AREA, vol.VOLUME FROM MEASUREMENT meas LEFT JOIN MEASUREMENT_AREA area ON meas.ID = area.ID LEFT JOIN MEASUREMENT_VOLUME vol ON meas.ID = vol.ID AND area.NAME = vol.NAME WHERE meas.ID = 1000;您能否解释一下为什么您的第一个查询不起作用?输出是什么?一些建议: - 如果列已经以相同的方式命名,则无需命名列 - 您应该将键和索引放在用于在表之间查找数据的列上以优化搜索。
  • LEFT JOIN 是我尝试的第一件事,但它保证不起作用。它遗漏了任何具有NAME 的值,该值仅存在于MEASURMENT_VOLUME 中。在我的“预期结果”中,第三行将丢失,MEASURMENT_VOLUME 中没有Point 3。不幸的是,查询 1 也会发生这种情况。我不知道为什么,我希望 FULL JOIN 也会接受与条件 ON meas.ID = vol.ID OR meas.ID = area.ID 不匹配的值。

标签: sql oracle oracle12c


【解决方案1】:

为什么一个查询有效而另一个无效已在另一个答案中进行了解释。所以我只是添加我将如何编写查询:

您想要measurement_areameasurement_volume 的完全外连接。在子查询中执行此操作并加入 measurement 表:

select id, m.meas_name, data.name, data.area, data.volume
from measurement m
join 
(
  select id, name, ma.area, mv.volume
  from measurement_area ma
  full outer join measurement_volume mv using (id, name)
) data using(id);

【讨论】:

  • +1 用于使用 using 子句并避免多余的 nvl() 等。 OP 也有可能在外部查询中需要一个完整的外部联接(或至少一个左外部联接),以说明任何一个详细信息表中都不存在的“测量”。但这需要 OP 来澄清。
  • 感谢您的回答。我还考虑过明确拆分 JOIN。但是,您的解决方案在我的情况下不起作用,因为 measurement_areameasurement_volume 非常大(高达 4 亿),因此 FULL JOIN 甚至不适合我们的临时空间。但我找到了一个解决方案,首先加入measurementmeasurement_area,其次是measurementmeasurement_volume,并完全加入两者的结果。我会发布解决方案。
  • 你真的需要所有的测量吗?在您的示例中,您将其限制为一次测量(ID 1000)。一旦你在上面的查询中添加了WHERE 子句,我想应该不会有任何问题。
  • 是的,我总是限制为一个测量 ID。但是,将 WHERE ID = 1000 添加到 FULL OUTER JOIN 并没有帮助。计算结果仍然需要几分钟。我不确定为什么。我必须分析执行计划。由于我已经找到了可行的解决方案,因此我不会这样做。我的猜测是,FULL JOIN 上的 USING(ID, NAME) 会计算出许多行,这些行随后会被 WHERE ID = 1000 过滤掉,但我真的不知道。
  • 另请注意,我的解决方案首先使用(INNER) JOIN,这对于包括WHERE ID = 1000 条件的数据库的计算可能更有效。自我注意:只在非常小的桌子上使用FULL JOIN
【解决方案2】:

为什么查询 1 不起作用?

...
ON meas.ID = vol.ID AND area.NAME = vol.name
...
where meas.ID = 1000

您的完整连接条件有 area.name = vol.name,这意味着 MEAS_VOLUME 表中名称为“Point 3”的行不匹配。仅通过连接,您确实可以从该表中获取行,但由于它与条件不匹配,因此只有该表中的字段具有值 - meas.ID 与 MEAS_NAME 和 AREA 一起为空。但随后您过滤掉 ID 不 = 1000 的行。如果您删除该查询的 where 子句,您会得到:

ID      MEAS_NAME       NAME    AREA    VOLUME
1000    Measurement 1   Point 1 10      100
                        Point 3         200
1000    Measurement 1   Point 2 20  

为什么查询 2 有效?

基本上是因为它对于回答问题是正确的。似乎您在那个区域中认识到 area.ID 和 vol.ID 并非始终可用,因此您将 MEASUREMENT 与联接中的任何一个进行匹配,这意味着您的查询有效。

实现输出的最有效方法是什么?

如果没有更多信息,这很难回答 - 您的执行计划是什么样的?有哪些索引可用?正在使用什么?

我猜首先会发生完全连接,所以您要对 2 个大表执行此操作,然后再连接回第一个表。更新表的统计信息可能会解决查询 2 的性能问题,或者可能需要进行更深入的分析。

Edited to Add - 这是您查询的另一个正确版本,它的执行速度可能比查询 2 更快。将 OR 排除在连接条件之外,这有时会使优化器变得困难。

with MEASUREMENT as
(
  select 1000 as ID, 'Measurement 1' as MEAS_NAME from dual
), MEASUREMENT_AREA as
(
   select 1000 as ID, 'Point 1' as NAME, 10 as AREA from dual union all
   select 1000 as ID, 'Point 2' as NAME, 20 as AREA from dual
), MEASUREMENT_VOLUME as
(
   select 1000 as ID, 'Point 1' as NAME, 100 as VOLUME from dual union all
   select 1000 as ID, 'Point 3' as NAME, 200 as VOLUME from dual
),
base_qry as (
    select meas.ID, meas_name, area.name, area, null as volume
    FROM MEASUREMENT meas
      LEFT JOIN MEASUREMENT_AREA area
        ON meas.ID = area.ID
    WHERE meas.ID = 1000

    union all 

    select meas.ID, meas_name, vol.name, null, volume
    FROM MEASUREMENT meas
      LEFT JOIN MEASUREMENT_VOLUME vol
        ON meas.ID = vol.ID
    WHERE meas.ID = 1000)
select ID, MEAS_NAME, NAME,
    max(AREA) as AREA,
    max(VOLUME) as VOLUME
from base_qry
group by ID, MEAS_NAME, NAME
order by 1,2,3
;

【讨论】:

  • @gucce 这展示了基于JOIN 条件的过滤与使用WHERE 子句之间的区别。您的FULL JOIN 可能正确连接了所有行,但随后您排除了任何不是WHERE meas.ID = 1000 的行。 INNER JOINs 对结果没有影响,但 OUTER JOINs 可以,正如您所见。
  • 非常感谢您澄清为什么查询 1 不起作用。
【解决方案3】:

我基本上结合了@dandarc 和@thorsten-kettner 的答案(非常感谢您的宝贵意见):

由于MEASUREMENT_VOLUMEMEASUREMENT_AREAMEASUREMENT 大得多,我将JOIN 分开:

SELECT *
FROM 
(
  SELECT *
  FROM MEASUREMENT
  JOIN MEASUREMENT_AREA
    USING(ID)
  WHERE ID = 1000
)
FULL JOIN
(
  SELECT *
  FROM MEASUREMENT
  JOIN MEASUREMENT_VOLUME
    USING(ID)
  WHERE ID = 1000
) USING (ID, MEAS_NAME, NAME);

就我的目的而言,重要的是首先将大表连接到 MEASUREMENT,然后将这些结果合并(也可以使用 @dandarc 建议的 UNION ALLGROUP BY)。

这有效地解决了我的问题。三个表上的FULL JOIN 查询 2 花费了 3 多分钟。使用此解决方案需要几秒钟。

请注意,我现实生活中的问题更复杂,因为我选择了几十列,不能简单地使用SELECT *。因此,我不能使用USING(ID, MEAS_NAME, NAME),但需要坚持使用ON 语法。

【讨论】:

    【解决方案4】:

    试试这个 -

    SELECT meas.ID AS "ID",
    meas.MEAS_NAME AS "MEAS_NAME",
    COALESCE (area.NAME, vol.NAME) as "NAME",
    area.AREA, vol.VOLUME
    FROM MEASUREMENT meas
    LEFT JOIN MEASUREMENT_AREA area
    ON meas.ID = area.ID
    LEFT JOIN MEASUREMENT_VOLUME vol
    ON meas.ID = vol.ID
    WHERE meas.ID = 1000;
    

    只需从您的第一个查询中删除 area.NAME = vol.NAME

    【讨论】:

    • 那么我不会得到在 NAME 匹配的地方加入的值,这就是我想要的。请参阅“预期结果”第一行 (Point 1),它同时填充了 VOLUMEAREA。不使用NAME 条件加入太多,我会得到两行Point 1,一个只填充AREA,另一个只填充VOLUME
    猜你喜欢
    • 2012-02-13
    • 1970-01-01
    • 1970-01-01
    • 2019-08-07
    • 1970-01-01
    • 2020-05-25
    • 1970-01-01
    • 1970-01-01
    • 2013-04-16
    相关资源
    最近更新 更多