避免在Oracle SQL查询中进行不必要的连接

时间:2012-10-09 17:34:45

标签: sql oracle lazy-evaluation

假设您有一个表格如下:

Table Name:  CUSTOMER
Primary Key: CUSTOMER_ID
+-------------+---------------+
| CUSTOMER_ID | CUSTOMER_NAME |
+-------------+---------------+
| 1           | Bill          |
| 2           | Tom           |
+-------------+---------------+

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

Table Name:  CUSTOMER_ATTRIBUTE
Primary Key: (CUSTOMER_ID, ATTRIBUTE_TYPE_ID)
+-------------+-------------------+-----------------+
| CUSTOMER_ID | ATTRIBUTE_TYPE_ID | ATTRIBUTE_VALUE |
+-------------+-------------------+-----------------+
| 1           | FAVORITE_FOOD     | Pizza           |
| 1           | FAVORITE_COLOR    | Blue            |
| 2           | FAVORITE_FOOD     | Taco            |
| 2           | NAME_OF_PET       | Fido            |
+-------------+-------------------+-----------------+

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

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           Cost=1  Cardinality=1   Bytes=65
 NESTED LOOPS OUTER         Cost=1  Cardinality=1   Bytes=65
  NESTED LOOPS OUTER            Cost=1  Cardinality=1   Bytes=39
   INDEX FULL SCAN  Object owner=HFSMMM Object name=CUSTOMER_PK_INDEX   Cost=1  Cardinality=1   Bytes=13
   INDEX UNIQUE SCAN    Object owner=HFSMMM Object name=CUSTOMER_ATTRIBUTE_PK_INDEX Cost=0  Cardinality=1   Bytes=26
  INDEX UNIQUE SCAN Object owner=HFSMMM Object name=CUSTOMER_ATTRIBUTE_PK_INDEX Cost=0  Cardinality=1   Bytes=26

3 个答案:

答案 0 :(得分: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指出切换上下文有开销是正确的 我每天都用它来观看。我希望数据库提前完成工作以准备视图和查询,而不是让应用程序发送必须构造和缓存的多连接查询。

答案 1 :(得分:1)

如果您确定每个客户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

答案 2 :(得分:1)

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

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