使用连接进行条件过滤

Posted

技术标签:

【中文标题】使用连接进行条件过滤【英文标题】:Conditional filtering with join 【发布时间】:2016-07-14 11:25:53 【问题描述】:

在具有单个参数的存储过程中,我有一个类似于以下性质的查询:

SELECT
    ID,
    DepartmentID,
    FileName
FROM
    Document
-- conditional join from here
JOIN
    AllowedDepartmentList ON DepartmentID = AllowedDepartmentList.ID 
                          AND @IsAdmin = 'false'

参数为@IsAdmin,数据类型位。

我使用的两个表是 Document 表(请参阅上面查询中的结构)和包含单个 int 列的 AllowedDepartmentList

我使用这个查询来过滤带有连接的Document 表的返回结果。我不使用 WHERE DepartmentID IN() 子句,因为 AllowedDepartmentList 可以长达 600-700 个项目(对于 IN() 来说太多了,无法在潜在的 1M 记录表中以良好的性能处理) 所以我使用连接进行过滤,但只有在@IsAdmin 参数为false 时才应执行过滤。就像 --conditional join from here 注释之后的行一样。

我尝试了上面的查询,但它没有产生任何记录。我怀疑我使用了错误的联接类型,但我被卡住了。

【问题讨论】:

【参考方案1】:

您可以将左连接与需要管理员权限的地方结合使用,或者在连接上进行匹配

SELECT
    ID,
    DepartmentID,
    FileName
FROM
    Document
-- conditional join from here
LEFT JOIN
    AllowedDepartmentList ON DepartmentID = AllowedDepartmentList.ID 
WHERE
    @IsAdmin = 'true' OR AllowedDepartmentList.ID IS NOT NULL

【讨论】:

请注意,jhilden 的建议更有效,因为您可以避免在不需要的情况下加入。不过,我确实理解您只保留一个查询的论点。 非常感谢,这似乎没问题,我得测试一下。是的,你是对的,我们还必须测试额外的连接是否会导致如此多的开销,以至于值得复制代码。 我接受了您的回答,因为我们已实施此解决方案以进行测试。它根据需要工作并完成工作,但应该衡量性能。还有许多其他适合我的答案,所以感谢所有花时间摆弄我的问题的人。【参考方案2】:

作为一般规则,将参数放入 WHERE 子句 VS 连接中。一个稍微简单的解决方案是让 TVF 或 sproc 运行两个完全不同的查询。像这样:

IF (@isAdmin = 0) --notice I used a SQL bool vs a string of 'false'
BEGIN
    SELECT
        ID,
        DepartmentID,
        FileName
    FROM
        Document
    JOIN
        AllowedDepartmentList ON DepartmentID = AllowedDepartmentList.ID;
END;
ELSE
   --@IsAdmin is not false, so don't join
    SELECT
        ID,
        DepartmentID,
        FileName
    FROM
        Document;
END;

【讨论】:

这是我试图避免的:重复选择。我们的 SP 中的精确选择要复杂得多(比如 150 行),如果整个 shebang 被复制,就会变得非常难看……更不用说未来的修改了。 @Daniel 如果整个 shebang 是 150 行,则更有理由进行有效的查询。复制粘贴和删除连接有多难? 你在这件事上是绝对正确的。我们将不得不对真实数据进行一些压力测试,以找出最佳答案。我只是在寻找代码重复的替代方法,但我从没想过会得到这么多好的建议(包括保持当前轨道,就像 jhilden - 和你 - 建议的那样)。【参考方案3】:

你可以使用存在条件,像这样:

SELECT
 ID,
 DepartmentID,
 FileName
FROM Document
WHERE exists(
 SELECT 1 from AllowedDepartmentList 
 WHERE DepartmentID = AllowedDepartmentList.ID) 
OR @IsAdmin = 'true'

【讨论】:

我认为它更快,特别是如果您使用附加条件,但您必须尝试。 join 是获取数据,exist 非常适合你:) 加入会导致记录相乘,不存在。 嗯。有趣的厘米。一定会检查这个建议。【参考方案4】:

使用Dynamic query 的另一种方法。与JOIN/Exists options 相比,这将具有良好的性能

DECLARE @sql     NVARCHAR(max)='',
        @IsAdmin BIT = 1

SET @sql = '
SELECT
    ID,
    DepartmentID,
    FileName
FROM
    Document D
    ' + CASE WHEN @IsAdmin = 'false' THEN ' where exists (select 1 from  AllowedDepartmentList AD where D.DepartmentID = AD.ID ) ' ELSE '' END

--PRINT @sql 

exec sp_executesql @sql 

@IsAdmin = 0 时的动态框架查询

SELECT ID,
       DepartmentID,
       FileName
FROM   Document D
WHERE  EXISTS (SELECT 1
               FROM   AllowedDepartmentList AD
               WHERE  D.DepartmentID = AD.ID) 

@IsAdmin = 0 时的动态框架查询

SELECT ID,
       DepartmentID,
       FileName
FROM   Document D

【讨论】:

【参考方案5】:

以下查询会对您有所帮助

SELECT  ID
        ,DepartmentID
        ,FileName
FROM    Document
        LEFT JOIN   AllowedDepartmentList ON DepartmentID = AllowedDepartmentList.ID
WHERE   @isAdmin = 1
    OR  ( @isAdmin = 0 AND AllowedDepartmentList.ID IS NOT NULL)

【讨论】:

【参考方案6】:

为管理员添加一个类似于 -1 的 AllowedDepartmentList.ID 为 @adminID 传递 null 或 -1

ON isnull(@adminID, DepartmentID) = AllowedDepartmentList.ID

一个 OR(或 @IsAdmin = 'true' )效率不高,这也只返回 1

【讨论】:

这会杀死DepartmentID上的所有索引 @Prdp 所以,我假设 AllowedDepartmentList.ID 是一个 PK。它并不比代码更少的 EXISTS 差。

以上是关于使用连接进行条件过滤的主要内容,如果未能解决你的问题,请参考以下文章

使用带有条件过滤器的 Include() 连接两个表

在 Oracle DBMS 中的多个列上使用过滤条件连接表

使用带有多个语句的 data.table 进行条件过滤

连接两个表而不根据连接条件过滤数据

如何在 Django 中使用字段名称、条件和值进行动态过滤

SQL左连接与JOIN条件中的过滤器与WHERE子句中的过滤器[重复]