T-SQL - 如何编写条件连接

Posted

技术标签:

【中文标题】T-SQL - 如何编写条件连接【英文标题】:T-SQL - How to write a conditional join 【发布时间】:2010-12-23 10:12:02 【问题描述】:

我有一个带有许多参数的存储过程。我想编写我的查询,以便它与某些表连接,但前提是特定参数具有值。举个例子:我有一个 Person 表。还有一个包含人员地址的地址表和一个包含人员组的组表。两者都是与 Person 表的一对多关系。我的存储过程有一个@AddressID 参数和一个@GroupID 参数。

查询总是只返回 Person 表中的字段。如果两个参数都没有值,则查询应返回 Person 表中的所有记录。如果提供了@AddressID 参数,那么它应该只返回在地址表中具有匹配记录的记录并忽略组表。如果提供了 @GroupID 参数,那么它应该只返回在 Groups 表中具有匹配记录的记录,并忽略 Addresses 表。如果提供了两个参数,那么它应该只显示在两个表中都有匹配记录的记录。有意义吗?

有没有我想念的简单方法来做到这一点?

谢谢, 科里

【问题讨论】:

感谢您的提问。其他人没有。例如,这在使用 LEFT JOIN 时非常有用。 【参考方案1】:

如果我理解正确,听起来您的加入条件相当于ON ((@AddressID IS NOT NULL) AND (alias.column = @AddressID)) 和群组加入。

我有时会使用这种条件连接。

【讨论】:

完美!像魅力一样工作。非常感谢。 有时候就这么简单!谢谢facepalm 这是怎么回事?如果@AddressID 为NULL,则内连接将不返回任何内容 不正确。如果@AddressID 为NULL,则不会返回任何内容。 @hidarikani 当@AddressID 为NULL 时,您应该使用LEFT OUTER JOIN(而不是INNER JOIN)返回值【参考方案2】:

是的,很简单。对地址和组进行左连接。然后在where子句中...

(@group_id is null or g.group_id = @group_id)
and (@address_id is null or a.address_id = @address_id)

【讨论】:

这会给出正确的答案,但是在你加入 OR 条件时性能会很慢。【参考方案3】:

你应该可以扩展这个...

DECLARE @SQL varchar(max)

    SET @SQL = 'SELECT * FROM PERSON P'

    IF NULLIF(@ADDRESSID,"") IS NULL SET @SQL = @SQL + " INNER JOIN ADDRESSES A ON P.AddressID = A.AddressID"

    EXEC sp_executesql @SQL, N'@ADDRESSID int', @ADDRESSID

【讨论】:

为什么不呢?动态 SQL 很棒! 不,大多数时候它是魔鬼自己的产物,只能在没有任何真正替代品的情况下使用。简单地尝试保存一行 SQL 不算数。不仅如此,您的动态 SQL 将生成效率较低的查询计划,使任何升级或审计变得非常复杂,最重要的是使您容易受到 SQL 注入攻击。主要是为懒人准备的。 @Corey,如果您使用 sp_executesql 并因此利用查询计划缓存,动态 sql 并不慢。 叹气,祝你好运将 sql 注入到那个整数参数中,我很想看到...... 大多数时候它是恶魔的后代。它被使用了 999/1000 次,它被糟糕而危险地使用。并且查询计划不仅会在参数包含实际 SQL 的情况下发生变化 - 如果参数的性质(例如上述)影响 SQL 运行的性质,它也会发生变化。在有限的情况下,例如这种情况,这不太可能成为问题。以上可能对注入是安全的,但我仍然不喜欢它干扰审计和权限的复杂性。但我认为让我印象深刻的是“Dynamic SQL 很棒!”这句话。 - “动态 SQL 很强大!”会更好【参考方案4】:

Quntin 发布的内容不错,但存在一些性能问题。 信不信由你更快的是检查每个参数并编写SQL Join 视情况而定

另外:

IF @AddressParameter IS NOT NULL
BEGIN
SELECT blah1, blah2 FROM OneTable INNER JOIN AddressTable WHERE ....
-more code
END
ELSE...
BEGIN
END
...

您可以做的另一件事是在查询过滤器中执行连接(where 子句) 你可以这样做:

WHERE
(Address = @Address OR @Address IS NULL)

这里的表现也很糟糕。

【讨论】:

好吧,在这种情况下,这真的行不通。我有大约 9 个参数,它们可能为空,也可能不为空。所有这些不同参数的可能组合就像 9 阶乘或一些巨大的数字。 我不确定我理解你的意思科里。您要做的就是在 WHERE 中处理 n 个参数。此外,WHERE ((Address = @Address OR @Address IS NULL) AND (Group = @Group OR @Group IS NULL) AND (...)) 简单的连接将排除记录。它必须是左连接。【参考方案5】:

简单的方法实际上并不是好的解决方案。尽管听起来很糟糕,但最好的解决方案是在代码中使用显式 IF 并单独查询:

IF (condition) 
  SELECT ... FROM Person WHERE ...
ELSE IF (otherCondition)
  SELECT ... FROM Person JOIN ... ON ... WHERE ...
ELSE IF (moreCondition)
  SELECT ... FROM Persons JOIN ... JOIN ... WHERE ...

这样做的原因是,如果您尝试构建一个匹配所有三个(或更多)条件的单个查询,那么引擎必须生成一个在 中工作的 单个 查询计划>所有条件。在 T-SQL 中,一个语句等于一个计划。请记住,计划是为通用情况创建的,针对任何变量值,所以结果总是一个非常非常糟糕的计划。

虽然这是违反直觉的,并且对于任何程序员来说似乎都是一个可怕的解决方案,但这就是数据库的工作方式。这在 99.99% 的情况下都不是问题的原因是,在尝试了您的要求并看到了必须完成的工作之后,开发人员很快就会清醒过来并修改他们的要求,这样他们就不必运行可以选择加入的查询基于运行时变量值;)

【讨论】:

LINQ2SQL 可能令人惊讶地更适合这样的事情,因为查询表达式的本质是可以操作和改进的完全成熟的对象,请参阅***.com/questions/1849005/… 如果您有相互排斥的标准,那很好,大多数搜索屏幕都没有。而且它增加了维护开销——如果一个 JOIN 改变了,你必须对所有的副本进行改变。在这种情况下,动态 SQL 是更现实的解决方案。 @Pony:是的,动态 SQL 更适合复杂的条件。 不过,在某些情况下,单次选择会更快,例如当您有一个内联表值函数选择(更快)与带有 IF 的多语句表值函数(更慢)时.【参考方案6】:

将三个表连接在一起,并在 WHERE 子句中使用类似的内容:

WHERE Addresses.ID = COALESCE(@AddressID, Addresses.ID)
AND  Groups.ID = COALESCE(@GroupID, Groups.ID)

【讨论】:

简单的连接将排除记录。我相信它必须是左连接。【参考方案7】:

嗯,到目前为止,你们可能都解决了这个问题。

据我了解,您希望进行“动态”查询,如果参数存在则加入表,或者如果参数为空则省略加入。 秘密在于使用左外连接。喜欢:

SELECT p.*
FROM Parent AS p
LEFT OUTER JOIN Child AS c ON p.Id = c.ParentId
WHERE
        (@ConditionId IS NULL OR c.ConditionId = @ConditionId)

这是如何工作的?

如果过滤参数@ConditionId为null,则外连接没有child,结果将是Parent的全部。 如果过滤参数@ConditionId不为null,则outer join会将Child加入到这个parent中,condition(@ConditionId IS NULL OR c.ConditionId = @ConditionId)会抛出没有加入Child的Parent,条件为c.ConditionId = @ConditionId

LEFT OUTER JOIN 肯定有性能问题,但尽管它工作得很快,但我不想连接字符串来创建查询。

【讨论】:

【参考方案8】:

这就是我为我的案子所做的。


DECLARE
    @ColorParam varchar(500)

SET
    @ColorParam = 'red, green, blue'

declare @Colors table
(
    Color NVARCHAR(50) PRIMARY KEY
)

-- populate @Colors table by parsing the input param, 
-- table can be empty if there is nothing to parse, i.e.: no condition
INSERT @Colors SELECT Value FROM dbo.Splitter(@ColorParam, ',')

SELECT
    m.Col1,
    c.Color
FROM
    MainTable AS m
FULL JOIN -- instead of using CROSS JOIN which won't work if @Colors is empty
    @Colors AS c
ON
    1 = 1 -- the trick
WHERE
    (@ColorParam IS NULL OR c.Color = m.Color)
    

【讨论】:

完全连接将在主表列中返回一堆空值,其中 id 不匹配。你需要一个左连接。【参考方案9】:

左连接和 where 子句应该可以解决问题:

SELECT Customers.CustomerName, Customers.Country, Orders.OrderID
    FROM Customers
    LEFT JOIN Orders
    ON Customers.CustomerID=Orders.CustomerID
    WHERE Country= @MyOptionalCountryArg or @MyOptionalCountryArg is null;

【讨论】:

以上是关于T-SQL - 如何编写条件连接的主要内容,如果未能解决你的问题,请参考以下文章

t-sql如何创建连接2台服务器的触发器

如何在ionic html模板中编写条件语句

如何使用左连接和内连接编写 laravel 查询?

如何从 T-SQL 存储过程返回表

如何在雪花中编写相关子查询

如何缩短 T-SQL 中的 WHERE 子句