2009-12-10 58 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