【问题标题】:Avoiding unnecessary joins in an Oracle SQL query在 Oracle SQL 查询中避免不必要的连接
【发布时间】:2012-09-30 02:21:12
【问题描述】:

假设你有一个如下表:

表名:客户 主键:CUSTOMER_ID +-------------+--------------+ |客户 ID |客户名称 | +-------------+--------------+ | 1 |比尔 | | 2 |汤姆 | +-------------+--------------+

现在,假设您有一个 CUSTOMER_ATTRIBUTE 表,可让您将键/值对绑定到特定的 CUSTOMER

表名:CUSTOMER_ATTRIBUTE 主键:(CUSTOMER_ID,ATTRIBUTE_TYPE_ID) +-------------+-------+-------------- --+ |客户 ID | ATTRIBUTE_TYPE_ID | ATTRIBUTE_VALUE | +-------------+-------+-------------- --+ | 1 | FAVORITE_FOOD |披萨 | | 1 | FAVORITE_COLOR |蓝色 | | 2 | FAVORITE_FOOD |炸玉米饼 | | 2 | NAME_OF_PET |菲多 | +-------------+-------+-------------- --+

现在,假设您创建了一个视图来表示具有某些可能属性的客户:

CREATE VIEW CUSTOMER_VIEW AS
SELECT
    CUSTOMER.CUSTOMER_ID,
    CUSTOMER.CUSTOMER_NAME,
    FAVORITE_FOOD_ATTRIBUTE.ATTRIBUTE_VALUE AS FAVORITE_FOOD,
    FAVORITE_COLOR_ATTRIBUTE.ATTRIBUTE_VALUE AS FAVORITE_COLOR
FROM
    CUSTOMER

    LEFT OUTER JOIN CUSTOMER_ATTRIBUTE favorite_food_attribute
        ON customer.customer_id = favorite_food_attribute.customer_id
           AND favorite_food_attribute.attribute_type_id = FAVORITE_FOOD

    LEFT OUTER JOIN CUSTOMER_ATTRIBUTE favorite_color_attribute
        ON customer.customer_id = favorite_color_attribute.customer_id
           AND favorite_color_attribute.attribute_type_id = FAVORITE_COLOR

现在,假设您查询这个视图:

SELECT
    CUSTOMER_ID,
    CUSTOMER_NAME,
    FAVORITE_COLOR
    -- Notice: I did not ask for the FAVORITE_FOOD column
FROM
    CUSTOMER_VIEW

根据解释计划,Oracle 仍在加入favorite_food_attribute,尽管它的值不需要并且不影响查询的基数(因为它是LEFT OUTER JOIN 对表的主键)。

有没有办法强制 Oracle 避免这些不必要的连接?

更新:DDL 示例

这里是一些用于创建示例架构的 DDL:

CREATE TABLE CUSTOMER
(
    CUSTOMER_ID   NUMBER NOT NULL,
    CUSTOMER_NAME VARCHAR2(100)
);

CREATE UNIQUE INDEX CUSTOMER_PK_INDEX
    ON CUSTOMER(CUSTOMER_ID);

ALTER TABLE CUSTOMER
    ADD CONSTRAINT CUSTOMER_PK
    PRIMARY KEY (CUSTOMER_ID)
    USING INDEX CUSTOMER_PK_INDEX;

CREATE TABLE CUSTOMER_ATTRIBUTE
(
    CUSTOMER_ID       NUMBER NOT NULL,
    ATTRIBUTE_TYPE_ID NUMBER NOT NULL,
    ATTRIBUTE_VALUE   VARCHAR2(1000)
);

CREATE UNIQUE INDEX CUSTOMER_ATTRIBUTE_PK_INDEX
    ON CUSTOMER_ATTRIBUTE(CUSTOMER_ID, ATTRIBUTE_TYPE_ID);

ALTER TABLE CUSTOMER_ATTRIBUTE
    ADD CONSTRAINT CUSTOMER_ATTRIBUTE_PK
    PRIMARY KEY (CUSTOMER_ID, ATTRIBUTE_TYPE_ID)
    USING INDEX CUSTOMER_ATTRIBUTE_PK_INDEX;

CREATE OR REPLACE VIEW CUSTOMER_VIEW AS
SELECT
    CUSTOMER.CUSTOMER_ID,
    CUSTOMER.CUSTOMER_NAME,
    favorite_food_attribute.attribute_value AS favorite_food,
    favorite_color_attribute.attribute_value AS favorite_color
FROM
    CUSTOMER

    LEFT OUTER JOIN CUSTOMER_ATTRIBUTE favorite_food_attribute
        ON customer.customer_id = favorite_food_attribute.customer_id
           AND favorite_food_attribute.attribute_type_id = 5

    LEFT OUTER JOIN CUSTOMER_ATTRIBUTE favorite_color_attribute
        ON customer.customer_id = favorite_color_attribute.customer_id
           AND favorite_color_attribute.attribute_type_id = 6;

现在,我对这个查询运行解释计划:

SELECT CUSTOMER_ID FROM HFSMMM.CUSTOMER_VIEW

计划是:

SELECT STATEMENT, GOAL = ALL_ROWS 成本=1 基数=1 字节=65 嵌套循环外部成本=1 基数=1 字节=65 嵌套循环外部成本=1 基数=1 字节=39 INDEX FULL SCAN 对象所有者=HFSMMM 对象名称=CUSTOMER_PK_INDEX 成本=1 基数=1 字节=13 INDEX UNIQUE SCAN 对象所有者=HFSMMM 对象名称=CUSTOMER_ATTRIBUTE_PK_INDEX 成本=0 基数=1 字节=26 INDEX UNIQUE SCAN 对象所有者=HFSMMM 对象名称=CUSTOMER_ATTRIBUTE_PK_INDEX 成本=0 基数=1 字节=26

【问题讨论】:

  • 根据我的经验,Oracle 通常确实避免这些不必要的连接。能发一些真实的代码和解释计划吗?
  • 你运行的是什么版本的 Oracle?
  • @TonyAndrews:我无法发布真实代码,但我会尝试创建一些虚拟表(希望它们会传达同样的问题)。
  • @Annjawn:不,Oracle 的优化器应该能够对视图中引用的表进行连接消除,至少在 11g 中是这样。 Oracle optimizer blog:“一组表可能会作为一个视图公开,其中包含一个连接。连接可能是检索视图公开的所有列所必需的。但是视图的某些用户可能只访问视图的一个子集列,在这种情况下,可以消除连接表。”
  • @Annjawn:我明白了。我正在回复您的评论,您说您认为 Oracle 会始终使用视图中说明的联接。它没有;它可以从视图中消除连接。

标签: sql oracle lazy-evaluation


【解决方案1】:

虽然这种方法只是移动处理而不是消除它,但它使 SQL 更清晰。
创建用户函数

GET_TYPE(customer_id_in NUMBER, attribute_type_id IN NUMBER) RETURN VARCHAR 2
IS
/*  TO DO:  Assertions, error handling */
attribute_name VARCHAR2(300);
BEGIN
SELECT attribute_value
INTO attribute_name
FROM CUSTOMER_ATTRIBUTE
WHERE customer_id = customer_id_in
and attribute_type_id - attribute_type_in;


RETURN attribute_name;

END GET_TYPE;

然后你的观点是

CREATE VIEW CUSTOMER_VIEW as
SELECT  
    CUSTOMER.CUSTOMER_ID,  
    CUSTOMER.CUSTOMER_NAME,  
    GET_TYPE(1, CUSTOMER.CUSTOMER_ID) AS FOOD,
    GET_TYPE(2, CUSTOMER.CUSTOMER_ID) AS COLOR
FROM  
    CUSTOMER;

Adam 指出切换上下文存在开销是正确的 我每天都用它来查看。我宁愿让数据库提前完成工作以准备视图和查询,而不是让应用程序发送必须构建和缓存的多连接查询。

【讨论】:

  • 这是可能的,但我真的希望避免将 PL/SQL 拖入查询中。由于在 SQL 上下文和 PL/SQL 上下文之间不断切换的开销,我过去曾被咬过。
【解决方案2】:

如果您确定每个客户 ID 和属性类型只会有一个条目,则可以执行标量子查询:

SELECT
    CUSTOMER.CUSTOMER_ID,
    CUSTOMER.CUSTOMER_NAME,
    (select ATTRIBUTE_VALUE from CUSTOMER_ATTRIBUTE where customer_id = CUSTOMER.CUSTOMER_ID
        and ATTRIBUTE_TYPE_ID='F') AS FAVORITE_FOOD
FROM
    CUSTOMER

【讨论】:

    【解决方案3】:

    不要使用外连接,而是对您希望在视图中看到的每个属性值使用子查询。这是假设您的数据是结构化的,因此没有一个子查询可以返回多行。

    CREATE VIEW CUSTOMER_VIEW AS
    SELECT CUSTOMER_ID,
           CUSTOMER_NAME,
           (SELECT ATTRIBUTE_VALUE FROM CUSTOMER_ATTRIBUTE ca1
              WHERE ca1.CUSTOMER_ID = c.CUSTOMER_ID
              AND ATTRIBUTE_TYPE_ID = 'FAVFOOD')  FAVORITE_FOOD,
           (SELECT ATTRIBUTE_VALUE FROM CUSTOMER_ATTRIBUTE ca2
              WHERE ca2.CUSTOMER_ID = c.CUSTOMER_ID
              AND ATTRIBUTE_TYPE_ID = 'PETNAME')  PET_NAME,
           (SELECT ATTRIBUTE_VALUE FROM CUSTOMER_ATTRIBUTE ca3
              WHERE ca3.CUSTOMER_ID = c.CUSTOMER_ID
              AND ATTRIBUTE_TYPE_ID = 'FAVCOLOR') FAVORITE_COLOR
           FROM CUSTOMER c
    

    【讨论】:

    • +1:非常感谢您的回答。它与 ivanatpr 的答案几乎相同,所以我接受了一个糟糕的时间。掷硬币最终做出了决定。你有尾巴,对不起。 :)
    • 查看时间戳,我发现 Ivanatpr 的回答比我早 12 分钟,所以我没有抱怨!但我今天才注意到 Ivanatpr 的回答,因为我在输入我的时候还看不到它。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-06-14
    • 2017-12-30
    • 1970-01-01
    • 2012-11-22
    • 2011-07-20
    相关资源
    最近更新 更多