2

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

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

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

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

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

谢谢

4

6 回答 6

4

您可以在从 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 值执行不同的查询。我不确定这将如何影响缓存的执行计划(如果有人可以确认,将更新)或者最终结果是否会比您的原始存储过程更快,我认为这里的答案在于测试。

于 2009-01-22T14:50:21.520 回答
3

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

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

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

于 2009-01-22T15:07:17.663 回答
2

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

我能看到你所拥有的唯一潜在的缺点是它可能会在确定一个好的执行计划时造成一些困难。但是,如果性能足够好,就没有理由改变它。

于 2009-01-22T15:53:18.867 回答
2

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

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

于 2009-01-22T15:39:23.980 回答
1

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

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

我首先会简单地查看将其更改IN为简单LEFT JOIN的 with NULLcheck (这不会摆脱你的 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
于 2009-01-26T15:01:05.247 回答
1

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

为了优化场景 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 (@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,因此会给每个查询带来显着的性能损失。

于 2009-04-19T07:49:30.597 回答