在其他两个表的列上连接一列的最佳方法

Posted

技术标签:

【中文标题】在其他两个表的列上连接一列的最佳方法【英文标题】:Best Way to Join One Column on Columns From Two Other Tables 【发布时间】:2018-03-13 12:46:37 【问题描述】:

我在 Oracle 中有如下架构

部分:

+--------+----------+
| sec_ID | group_ID |
+--------+----------+
|    1   |     1    |
|    2   |     1    |
|    3   |     2    |
|    4   |     2    |
+--------+----------+

Section_to_Item:

+--------+---------+
| sec_ID | item_ID |
+--------+---------+
|    1   |     1   |
|    1   |     2   |
|    2   |     3   |
|    2   |     4   |
+--------+---------+

项目:

+---------+------+
| item_ID | data |
+---------+------+
|    1    |  a   |
|    2    |  b   |
|    3    |  c   |
|    4    |  d   |
+---------+------+

Item_Version:

+---------+----------+--------+
| item_ID | start_ID | end_ID |
+---------+----------+--------+
|    1    |    1     |        |
|    2    |    1     |    3   |
|    3    |    2     |        |
|    4    |    1     |    2   |
+---------+----------+--------+

Section_to_Item 在 *_ID 列上有 FK 到 Section 和 Item。 Item_version 在 item_ID 上建立索引,但没有对 Item.item_ID 的 FK(快照组中的空间不足)。

我有接收版本 ID 列表的代码,并且我想获取给定组中的部分中的所有项目,这些项目至少对传入的一个版本有效。如果项目没有 end_ID,则它对任何东西都有效从 start_ID 开始。如果它有一个 end_id,它对任何直到(不包括)end_ID 都有效。

我目前拥有的是:

SELECT Items.data
FROM Section, Section_to_Items, Item, Item_Version
WHERE Section.group_ID = 1
AND Section_to_Item.sec_ID = Section.sec_ID
AND Item.item_ID = Section_to_Item.item_ID
AND Item.item_ID = Item_Version.item_ID
AND exists (
    SELECT *
    FROM (
        SELECT 2 AS version FROM DUAL
        UNION ALL SELECT 3 AS version FROM DUAL
    ) passed_versions
    WHERE Item_Version.start_ID <= passed_versions.version
    AND (Item_Version.end_ID IS NULL or Item_Version.end_ID > passed_version.version)
)

请注意,UNION ALL 语句是从传入的版本列表中动态生成的。

此查询当前执行笛卡尔连接,速度非常慢。 出于某种原因,如果我将查询更改为加入

AND Item_Version.item_ID = Section_to_Item.item_ID

这不是 FK,查询不进行笛卡尔连接,速度更快。

A) 谁能解释这是为什么? B) 这是加入这一系列表的正确方法吗(我觉得将 Item.item_ID 加入两个不同的表很奇怪) C) 这是获取 start_ID 和 end_ID 之间版本的正确方法吗?

编辑

使用内连接语法的相同查询:

SELECT Items.data
FROM Item
INNER JOIN Section_to_Items ON Section_to_Items.item_ID = Item.item_ID
INNER JOIN Section ON Section.sec_ID = Section_to_Items.sec_ID
INNER JOIN Item_Version ON Item_Version.item_ID = Item_.item_ID
WHERE Section.group_ID = 1
AND exists (
    SELECT *
    FROM (
        SELECT 2 AS version FROM DUAL
        UNION ALL SELECT 3 AS version FROM DUAL
    ) passed_versions
    WHERE Item_Version.start_ID <= passed_versions.version
    AND (Item_Version.end_ID IS NULL or Item_Version.end_ID > passed_version.version)
)

请注意,在这种情况下,性能差异来自于先加入 Item_Version,然后再加入 Item_Version.item_ID 上的 Section_to_Item。

在表大小方面,Section_to_Item、Item 和 Item_Version 应该相似(1000s),而 Section 应该很小。

编辑

我刚刚发现,架构显然没有 FK。架构配置文件中指定的 FK 将被忽略。他们只是为了文档。因此,加入或不加入 FK 列没有区别。话虽如此,通过将连接更改为 SELECT IN 的级联,我能够避免将整个 Item 表连接两次。我不喜欢生成的查询,也不太了解其中的区别,但统计数据表明它的工作量要少得多(将 Section 上最内层扫描返回的 A-Rows 从 656,000 更改为 488(它曾经是656k 开始返回 1 行,现在是 488 开始返回 1 行))。

编辑

原来是陈旧的统计信息——这两个查询一直是等价的,但是由于统计信息不完整,数据库碰巧只在第二个实例中注意到了正确的计划。更新统计信息后,两个查询生成了相同的计划。

【问题讨论】:

学习使用正确、明确的JOIN 语法。这可能会解决您的问题,并且肯定会让其他人更容易找出可能出了什么问题。 @GordonLinoff 两个查询都由 Oracle 优化为同一个计划,但如果它让您更容易阅读,我更新了帖子。 听起来您已经有了一个解决方案,只是一个您不满意的解决方案,因为您不明白它为什么有效?我假设您已经知道创建 FK 约束不会自动创建索引?我完全同意您找到的“修复”不是很好,因为您基本上添加了一个冗余的JOIN(或一个冗余的JOIN 条件,具体取决于您使用的SQL 版本)。我会假设这在这种情况下更像是数据库提示?您的统计数据是最新的/自动生成的吗? 回答你的第三个问题,这是限制返回版本的正确方法吗?我不是特别喜欢您的解决方案,您在其中使用子查询来生成看起来像是硬编码的版本列表。此列表是否可能经常更改?它真的会来自现实生活中的数据库表吗?如果不是,那么为什么不直接取出您的 UNION 子查询,并将 Item_Version.start_ID &lt;= 3 AND (Item_Version.end_ID IS NULL or Item_Version.end_ID &gt; 2) 添加到您的 WHERE 子句中?我可能错过了这里的重点? @RichardHansell 是的,查询有效,我只是想知道是否有更好的方法来构建它。这似乎不应该是一件困难/缓慢的事情,但它是,我不知道为什么。我不知道是否有一种方法可以不在两个不同的表上加入同一列,或者有一种不同的方法来做 UNION ALL 的事情。 【参考方案1】:

我不确定这是否是最好的主意,但这似乎可以避免笛卡尔连接:

select data
from Item
where item_ID in (
    select item_ID
    from Item_Version
    where item_ID in (
        select item_ID
        from Section_to_Item
        where sec_ID in (
            select sec_ID
            from Section
            where group_ID = 1
        )
    )
    and exists (
        select 1
        from (
            select 2 as version
            from dual
            union all
            select 3 as version
            from dual
        ) versions
        where versions.version >= start_ID
        and (end_ID is null or versions.version <)
    )
)

【讨论】:

这是一条红鲱鱼 - 使用更新的数据库统计信息,这并没有提高查询性能

以上是关于在其他两个表的列上连接一列的最佳方法的主要内容,如果未能解决你的问题,请参考以下文章

添加一个表示两个其他 Varchar 列的串联的列

列上加索引时事有条件

mysql怎样将一张表里一列的数据更新到另一个表的一列上

内连接恰好在一列上,而在另一列上模糊

用于连接两个按连接逻辑排序的表的最佳 SQL 查询

将数据从一列插入另一个表的两列的过程