为什么Index不与子查询一起使用

Kag*_*awa 5 sql firebird

这需要0.001秒来执行,它使用索引搜索

SELECT * FROM CUSTOMER WHERE ID IN (1008,1122)
Run Code Online (Sandbox Code Playgroud)

现在我有一个存储过程U_VIP,返回与示例一相同的ID(1008,1122),执行只需0.001秒

SELECT ID FROM U_VIP    //returns (1008,1122)
Run Code Online (Sandbox Code Playgroud)

现在,当我将它们组合起来时,执行大约需要半秒钟,而不使用索引

SELECT * FROM CUSTOMER WHERE ID IN (SELECT ID FROM U_VIP)
Run Code Online (Sandbox Code Playgroud)

我已经简化了上面的例子,在实际应用中,性能受到更高幅度的影响.在这种情况下如何强制Firebird使用索引?

**使用Firebird 2.1

**编辑**

根据Mark的回答,使用JOIN确实可以缩短执行时间,因为它现在正在进行索引搜索.

SELECT CUSTOMER.* 
FROM CUSTOMER
INNER JOIN U_VIP ON U_VIP.ID = CUSTOMER.ID
Run Code Online (Sandbox Code Playgroud)

这很好,但是,它为我引入了另一个问题,我将在下面的例子中尝试解释.

SELECT CUSTOMER.* 
FROM CUSTOMER
WHERE (:AREAID = 0 OR ID IN (SELECT ID FROM U_VIP(:AREAID)))
Run Code Online (Sandbox Code Playgroud)

使用where子句,我可以根据是否有条件地应用过滤器:AREAID是否由用户提供.当我用连接替换where子句时,如何实现相同的目的?

就像是:

SELECT CUSTOMER.* 
FROM CUSTOMER
{IF :AREAID > 0 THEN}
INNER JOIN (SELECT ID FROM U_VIP(:AREAID)) VIP ON VIP.ID = CUSTOMER.ID
{END IF}
Run Code Online (Sandbox Code Playgroud)

当然,Firebird不喜欢使用大括号= /

Mar*_*eel 5

而不是IN,你需要使用EXISTSINNER JOIN.我不完全确定详细信息,但我相信您的查询CUSTOMER表已完全读取,评估每行的子查询结果(甚至可能为每一行执行子查询).由于优化器事先不知道子查询的结果数,因此如果您在第一个查询中使用固定数量的文字值,则无法创建优化.

尝试将您的查询更改为:

SELECT * 
FROM CUSTOMER 
WHERE EXISTS (SELECT 1 FROM U_VIP WHERE U_VIP.ID = CUSTOMER.ID)
Run Code Online (Sandbox Code Playgroud)

要么:

SELECT CUSTOMER.* 
FROM CUSTOMER
INNER JOIN U_VIP ON U_VIP.ID = CUSTOMER.ID
Run Code Online (Sandbox Code Playgroud)

或者(改变顺序有时可以带来更好的性能):

SELECT CUSTOMER.* 
FROM U_VIP
INNER JOIN CUSTOMER ON CUSTOMER.ID = U_VIP.ID
Run Code Online (Sandbox Code Playgroud)

一般来说,我希望这些查询的执行效果优于查询IN.

编辑以响应更新

根据您更新的问题,我可以想到多种解决方案,但我对它们的表现并不完全确定.

  • 使用单独的查询为:AREAID0 :AREAID且不为0
  • 使用存储过程或EXECUTE BLOCK使用EXECUTE STATEMENT动态构建的语句(前一个变体)
  • 如果:AREAID为0,则使存储过程U_VIP返回所有客户
  • 使用附加JOIN条件OR :AREAID = 0; 如果U_VIP没有返回任何0(并且可能不执行*),这可能不会产生结果
  • 使用LEFT JOIN和添加WHERE U_VIP.ID IS NOT NULL OR :AREAID = 0(可能不执行*)
  • 使用UNION"正常"的查询和第二查询CUSTOMERWHERE :AREAID = 0(可能不执行*)

对于(*),请参阅"智能逻辑"反模式

对于动态构建的查询,您可以考虑以下内容:

EXECUTE BLOCK (INPUTCONDITION INTEGER = ?)
    RETURNS (ID INTEGER)
AS
    DECLARE VARIABLE QUERY VARCHAR(6400);
BEGIN
    QUERY = 'SELECT a.ID FROM SORT_TEST a';
    IF (INPUTCONDITION <> 0) then
        QUERY = QUERY || ' WHERE a.ID = ' || INPUTCONDITION;
    FOR EXECUTE STATEMENT QUERY INTO :ID
    DO
        SUSPEND;
END
Run Code Online (Sandbox Code Playgroud)

在此示例中,值0 for INPUTCONDITION将生成不带WHERE-clause 的查询,而对于其他输入则使用-clause 生成查询WHERE.做这样很容易受到SQL注入,如果该参数是(VAR)CHARBLOB,所以要小心.您还可以考虑两个分支,其中一个使用EXECUTE STATEMENT参数而另一个不使用.

相反的EXECUTE BLOCK,你也可以使用一个可选的过程像你已经使用U_VIP; EXECUTE BLOCK本质上是一个未存储在数据库中的存储过程.

另见'神话:动态SQL很慢'