基于参数值的带有“AND”的 case 语句的存储过程

pmd*_*dci 3 stored-procedures dynamic-sql t-sql

我的数据库中有一个表,其中存储了成功和失败的登录尝试。我正在创建一个存储过程,允许我们删除超过 X 天的记录。到目前为止一切顺利,但我想把它提高一个(或两个)等级,并允许我们指定是否删除关于 [Success] 列是真、假还是两者兼而有之的记录。不过,我在连接需要执行的脚本时遇到了一些问题。

这是我到目前为止所做的:

-- CREATE PROCEDURE [dbo].[sp_delete_log_attempts]
DECLARE @backDays INT = 1 -- Default to 30 days (one test finishes)
DECLARE @successArg BIT = NULL -- default to both true and false success logins
DECLARE @successAnd VARCHAR(50)
DECLARE @query VARCHAR(MAX)

SET @successAnd = CASE
WHEN @successArg = 'true' THEN
    'AND [Success] = ''true'''
WHEN @successArg = 'false' THEN
    'AND [Success] = ''false'''
ELSE
    'AND [Success] = ''true'' OR [Success] = ''false'''
END

PRINT @successAnd -- just for debugging purposes

SET @query = 'SELECT * FROM [audit].LoginAttempt WHERE [TimeStamp] <= DATEADD(day,-' + @backDays + ', GETDATE())'
EXEC @query
Run Code Online (Sandbox Code Playgroud)

在这一点上,我只是想根据 @backDays 变量选择行,但我无法将 @query 字符串与变量连接起来。不确定我在这里做错了什么。不过,我对动态查询相当陌生。

sti*_*bit 6

我认为您在这里甚至不需要动态 SQL。您可以在查询中使用变量。

SELECT *
       FROM [audit].[LoginAttempt]
       WHERE [TimeStamp] <= dateadd(day, -1 * @backDays, getdate())
             AND CASE
                   WHEN @successArg IS NOT NULL
                     THEN (SELECT 1
                                  WHERE [Success] = @successArg)
                   ELSE 1
                 END = 1;
Run Code Online (Sandbox Code Playgroud)

(未测试,因为没有提供样本数据。只是为了证明这个想法。)

如果你仔细观察,你会看到,整个CASE ... END是一个=操作的左操作数。操作的右侧是1

不幸的是,SQL Server 不知道我们可以直接从CASE ... END. (其他的,例如 PostgreSQL 可以。)所以我们必须使用它可以用于布尔运算的表达式来欺骗它。这就是我们使用该=操作的原因。

现在我们需要一种方法,当我们真正想要测试的条件得到满足时,使这个操作评估为真。所以我们的想法是,如果我们决定满足它们,我们会为=. 另一方面,我们只是按字面意思使用该值。该=会是真的呢。如果我们的条件不满足,我们返回左侧的任何其他值,并且=不会为真。作为表示满足条件的值,让我们选择1. 它接近于我们可能想到的布尔值的表示。(但我们几乎可以选择任何东西(NOT NULL,否则我们必须将=操作更改为IS NULL)。)。

那么1当条件满足时,我们如何让我们的左表达式返回?

好吧,我们可以使用CASE ... END语句根据某些条件返回一个值。你已经知道了CASE ... END。它有点类似于类C 语言中的aswitchif else构造(或在其他过程语言中具有不同的名称)。

我们需要测试的是输入变量@successArg是否为空。如果它为空,则意味着调用者不关心记录的登录尝试是否成功。否则, 的值@successArg指示他是只想要成功登录 ( @successArg = 1) 还是只想要不成功登录( @successArg = 0)。这将为我们提供最重要的案例:要么忽略成功,要么考虑成功。因此,我们有我们的两(主)分支CASE ... END的分支有关@successArg IS NOT NULL;

让我们从更简单的情况开始,当@successArg IS NULL. 那是“ELSE”分支。在这里,我们不关心登录尝试是否成功。对于任何行,成功或不成功(将其视为布尔表达式)始终为真。所以我们只返回我们的指标,这是一个匹配,1

@successArg IS NULL,@successArg持有值时,行必须在列中[Success]。仅当@successArg = [Success]. 正如已经提到的,@successArg = [Success]SQL Server 无法在该上下文中单独处理。所以我们需要在当前行时1返回我们的真实指示值@successArg = [Success],否则返回其他值。

这样做的一种方法是另一种内部方法,CASE ... WHEN或者我们可能使用相关子查询。它是“相关的”,因为它使用外部查询当前行中的列值。从外部查询的位置来看,它是“子”,因为它是“子”。此外,我们使用 SQL Server 中的功能,如果该查询的 为真(或没有),则SELECT带有 no 的FROM将生成一条记录WHERE,否则为空集。因此,让我们生成一列包含1when的记录@successArg = [Success]。这样的记录,只有一列,如果需要,会隐式转换为标量,因此它适合我们=在外面的操作。当 时@successArg <> [Success],该子查询将产生一个空集。NULL. 至于NULL = 1是不是真的,这会为我们做。

关于这一点的一个旁注:它也可以在没有CASE ... END布尔表达式的情况下完成:

SELECT *
       FROM [audit].[LoginAttempt]
       WHERE [TimeStamp] <= dateadd(day, -1 * @backDays, getdate())
             AND (@successArg IS NULL
                   OR [Success] = @successArg);
Run Code Online (Sandbox Code Playgroud)

这是我们从上面的想法。万一@successArg IS NULL我们想要“真实”而不考虑其他任何事情。否则我们只想要 "true" when [Success] = @successArg。在任何其他情况下为“假”。(注意:由于前一个条件,需要括号来覆盖ANDover的优先级OR。)

就查询的执行方式以及性能而言,我认为这不会产生重大影响(如果有的话)(但坦率地说,我不确定这一点)。带有CASE ... WHEN虽然的解决方案可能更易于阅读和维护。另一方面,仅布尔解决方案也适用于不支持CASE ... END.