【问题标题】:Conditional Joins - Dynamic SQL条件连接 - 动态 SQL
【发布时间】:2009-01-22 14:45:08
【问题描述】:

这里的 DBA 正在尝试将我简单的存储过程变成一个动态的 sql 怪物。诚然,我的存储过程可能没有他们想要的那么快,但我不禁相信有一种足够的方法来执行基本上是条件连接的操作。

这是我的存储过程的示例:

SELECT 
*
FROM
table
WHERE
(
    @Filter IS NULL OR table.FilterField IN 
    (SELECT Value FROM dbo.udfGetTableFromStringList(@Filter, ','))
)

UDF 将逗号分隔的过滤器列表(例如银行名称)转换为表格。

显然,在 where 子句中包含过滤条件并不理想。欢迎任何关于基于存储的 proc 参数有条件地加入的更好方法的建议。除此之外,是否有人对动态 sql 方法有任何建议或反对?

谢谢

【问题讨论】:

  • “这里的 DBA 正在尝试将我简单的存储过程转变为动态的 sql 怪物。” - 这很有趣,对我来说通常是相反的;)

标签: sql sql-server join dynamic-sql


【解决方案1】:

您可以对从 UDF 返回的表进行 INNER JOIN,而不是在 IN 子句中使用它

您的 UDF 可能类似于

CREATE FUNCTION [dbo].[csl_to_table] (@list varchar(8000) )
RETURNS @list_table TABLE ([id] INT)
AS
BEGIN
    DECLARE     @index INT,
            @start_index INT,
            @id INT

    SELECT @index = 1 
    SELECT @start_index = 1
    WHILE @index <= DATALENGTH(@list)
    BEGIN

        IF SUBSTRING(@list,@index,1) = ','
        BEGIN

            SELECT @id = CAST(SUBSTRING(@list, @start_index, @index - @start_index ) AS INT)
            INSERT @list_table ([id]) VALUES (@id)
            SELECT @start_index = @index + 1
        END
        SELECT @index  = @index + 1
    END
    SELECT @id = CAST(SUBSTRING(@list, @start_index, @index - @start_index ) AS INT)
    INSERT @list_table ([id]) VALUES (@id)
    RETURN
END

然后在返回表中的 id 上进行 INNER JOIN。此 UDF 假定您在逗号分隔列表中传递 INT

编辑:

为了处理为@filter 传入的空值或无值,我能看到的最直接的方法是在存储过程中根据@filter 值执行不同的查询。我不确定这将如何影响缓存的执行计划(如果有人可以确认,将更新)或者最终结果是否会比原始存储过程更快,我认为这里的答案在于测试。

【讨论】:

  • 但是如果@Filter 为空并且没有传入呢?这不会导致存储过程不返回任何数据。
【解决方案2】:

看起来代码的重写正在另一个答案中解决,但反对存储过程中动态 SQL 的一个很好的论点是它破坏了所有权链。

也就是说,当你正常调用一个存储过程时,它是在存储过程所有者的权限下执行的,除了用execute命令执行动态SQL时,对于动态SQL的上下文,它恢复到调用者的权限,根据您的安全模型,这可能是不可取的。

最后,您最好妥协并重写它以解决 DBA 的问题,同时避免动态 SQL。

【讨论】:

【解决方案3】:

我不确定我是否理解您对动态 SQL 的厌恶。也许是您的 UDF 很好地抽象了问题的一些杂乱性,而您觉得动态 SQL 将把它带回来。好吧,考虑到大多数(如果不是全部)DAL 或 ORM 工具将广泛依赖于动态 SQL,我认为您的问题可以重述为“我怎样才能很好地抽象出动态 SQL 的混乱”。

就我而言,动态 SQL 准确地为我提供了我想要的查询,以及随后我正在寻找的性能和行为。

【讨论】:

  • 当您使用动态 sql 时,您会丢失所有“编译时间”检查。一切都变成了“运行时”错误。这是我遇到的大问题。即使 DBA 使用“新的和改进的”动态 sql 存储过程,它也存在“运行时”错误。
  • 动态 SQl 也远没有那么安全。它要求您在表级别而不是在存储过程级别设置权限。因此,用户可以直接访问表,而不是仅限于执行 proc 中的操作。这会导致欺诈很容易发生。
  • @Brian - 这是正确验证用户输入的情况。用户应该永远提供表或视图名称作为其数据输入的一部分。 @HLGEM - 这是一个很好的观点。就我而言,我将权限管理提升到应用程序级别,但在纯 SQL 应用程序中这是不可能的
【解决方案4】:

我认为您的方法没有任何问题。老实说,重写它以使用动态 SQL 执行两个不同的查询,这对我来说似乎很愚蠢。

我能看到的唯一潜在缺点是它可能会导致难以确定一个好的执行计划。但是,如果性能足够好,就没有理由改变它。

【讨论】:

  • 我被告知它的表现不够好。
【解决方案5】:

无论你做什么(这里的答案都有好处),一定要比较每个选项的性能和执行计划。

有时,如果手动优化会影响代码的可维护性并且实际上对代码的执行方式没有任何影响,那么手动优化就毫无意义。

我首先会简单地查看将 IN 更改为简单的 LEFT JOIN 并使用 NULL 检查(这不会摆脱您的 udf,但它应该只被调用一次):

SELECT *
FROM table
LEFT JOIN dbo.udfGetTableFromStringList(@Filter, ',') AS filter
    ON table.FilterField = filter.Value
WHERE @Filter IS NULL
    OR filter.Value IS NOT NULL

【讨论】:

    【解决方案6】:

    您似乎正在尝试编写一个查询来处理两种情况:
    1. @filter = "x,y,z"
    2.@filter 为空

    为了优化场景 2,我会在 UDF 上进行 INNER JOIN,而不是使用 IN 子句...

    SELECT * FROM table
    INNER JOIN dbo.udfGetTableFromStringList(@Filter, ',') AS filter
      ON table.FilterField = filter.Value
    

    为了针对场景 2 进行优化,我不会尝试调整现有查询,而是故意将这些情况分开,使用 IF 语句或 UNION 并使用 WHERE 子句模拟 IF...

    TSQL IF

    IF (@filter IS NULL)
      SELECT * FROM table
    ELSE
      SELECT * FROM table
      INNER JOIN dbo.udfGetTableFromStringList(@Filter, ',') AS filter
        ON table.FilterField = filter.Value
    


    UNION 模拟 IF

    SELECT * FROM table
      INNER JOIN dbo.udfGetTableFromStringList(@Filter, ',') AS filter
        ON table.FilterField = filter.Value
    
    UNION ALL
    
    SELECT * FROM table WHERE @filter IS NULL
    

    这种设计的优点是每个案例都很简单,确定哪个简单是它自己简单的。然而,将这两者组合到一个查询中会导致妥协,例如 LEFT JOIN,因此会给每个查询带来显着的性能损失。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-04-09
      • 2015-11-08
      • 1970-01-01
      • 2013-05-18
      • 2014-12-25
      • 2011-03-29
      相关资源
      最近更新 更多