2009-12-10 23 views
2
SELECT a.*, b.* 
FROM a 
    LEFT OUTER JOIN b 
     ON b.user IN (:userlist) 
     AND b.key = a.fk_to_b 
WHERE 
a.user IN (:userlist) 
OR b.user IN (:userlist) 
  • 表b的索引:(用戶,密鑰)

數據庫僅使用索引時:用戶列表參數包含一個單一的值。何時:用戶包含多個值(內部擴展爲多個OR語句?)時,不使用索引並執行表(b)的表掃描。與外側索引使用JOIN包含IN語句

爲什麼當數據庫提供多個用戶列表值時不會使用索引?

有沒有人知道這個查詢的更優化版本?

+0

你使用哪種'RDBMS'? – Quassnoi 2009-12-10 16:13:26

+0

多個數據庫 - 主要是Sybase,Ingres和Firebird。 – Starfield 2009-12-10 16:54:53

回答

1

這個查詢將在所有主要的系統工作,並且可能會更有效率:

SELECT a.*, NULL 
FROM a 
WHERE a.user IN (:userlist) 
     AND a.fk_to_b NOT IN 
     (
     SELECT key 
     FROM b 
     ) 
UNION ALL 
SELECT a.*, b.id 
FROM a 
JOIN b 
ON  b.key = a.fk_to_b 
WHERE b.user IN (:userlist) 

你能告訴RDBMS你用哪個?

+0

多個數據庫 - 主要是Sybase,Ingres和Firebird。 有趣的答案! 不幸的是,我們使用的是複合主鍵,所以我們只能使用字符串連接(egax + ay NOT IN(SELECT x + y FROM b))來實現NOT IN。正確? 另外我測試了下部分,它運行比我當前在Ingres上的查詢慢 – Starfield 2009-12-10 16:53:25

0

快速回答是:這取決於。

如果您在userlist中指定了多個值,那麼數據庫服務器可能會選擇以不同的方式優化查詢,例如它可能會選擇全表掃描。

大部分的時間,最好的選擇是查看查詢是如何優化,通過做

  1. 在SQL服務器EXPLAIN計劃,甲骨文
  2. 顯示執行計劃。

爲了幫助您更多,我們真的需要知道您正在使用哪個數據庫。

0

IN(:userlist)擴展爲多個OR語句。
查詢優化器忽略OR行/子句。
這裏做什麼,如果DB是Oracle:

CREATE TABLE userListTable 
( 
    sessionId NUMBER(9), 
    user  NUMBER(9) 
); 

CREATE INDEX userListTableMulti1 ON userListTable(sessionId,user); 

...

CREATE OR REPLACE FUNCTION fn_getUserList(parmUserList VARCHAR2) 
    RETURN NUMBER DETERMINISTIC 
    varUser  NUMBER(9); 
    varSessionId NUMBER(9); 
BEGIN 
    varSessionId := sys_context('USERENV','SESSIONID'); 

    -- You have to work on a VARCHAR2TOLIST() function 
    FOR varUser IN VARCHAR2TOLIST(parmUserList) LOOP 
     INSERT INTO userListTable(sessionId,user) 
     VALUES(varSessionId, varUser) 
    END LOOP; 

    INSERT INTO resultsTable 
     SELECT 
     varSessionId as sessionId , 
     a.*      , 
     b.* 
     FROM 
     (SELECT a.* 
      FROM a 
      INNER JOIN userListTable 
      ON a.user = userListTable.user AND 
       userListTable.sessionId = varSessionId) a 
     LEFT OUTER JOIN (SELECT b.* 
          FROM b 
          INNER JOIN userListTable 
          ON b.user = userListTable.user AND 
           userListTable.sessionId = varSessionId) b 
     ON b.key = a.fk_to_b; 

    RETURN varSessionId; 
END; 
/ 

...

// C Client side 
int varSessionId; 
char* parmUserList; 
char* sqlStr; 

... 

sqlStr = (char*)malloc(strlen(parmUserList) + 17) ; 
sprintf(sqlStr,"fn_getUserList(%s)", parmUserList); 

// EXEC_SQL_FUNC_C_MACRO 
// EXEC_SQL_RETURN_QUERY_RESULTS_C_MACRO 
// EXEC_SQL_C_MACRO 
// are all based on the database API C libraries 

// Run the function for this session 
varSessionId = EXEC_SQL_FUNC_C_MACRO(sqlStr); 
free(sqlStr); 

// Get the results 
sqlStr = (char*)malloc(128); 
sprintf( 
    sqlStr, 
    "SELECT * " 
    "FROM resultsTable " 
    "WHERE sessionId=%s", 
    varSessionId); 
EXEC_SQL_RETURN_QUERY_RESULTS_C_MACRO(sqlStr); 
free(sqlStr); 

... 

// Clean up the resultsTable for this session 
sqlStr = (char*)malloc(128); 
sprintf( 
    sqlStr, 
    "DELETE " 
    "FROM resultsTable " 
    "WHERE sessionId=%s", 
    varSessionId); 
EXEC_SQL_C_MACRO(sqlStr); 
free(sqlStr); 

// Clean up the userListTable for this session 
sqlStr = (char*)malloc(128); 
sprintf( 
    sqlStr, 
    "DELETE " 
    "FROM userListTable " 
    "WHERE sessionId=%s", 
    varSessionId); 
EXEC_SQL_C_MACRO(sqlStr); 
free(sqlStr); 
+0

您還需要對resultsTable和userListTable進行定期掃描,以清除客戶端應用程序會話中斷所遺留的所有數據,指出使用sessionId來跟蹤事務可將您釋放到充分利用數據庫,而不必向後彎曲,以便使用複雜的SQL獲取數據。您還會發現,這樣做可以讓所有事情從長遠來看更容易維護。 – 2009-12-11 16:58:56