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 字符串与变量连接起来。不确定我在这里做错了什么。不过,我对动态查询相当陌生。
我认为您在这里甚至不需要动态 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 语言中的aswitch
或if 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
,否则为空集。因此,让我们生成一列包含1
when的记录@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
。在任何其他情况下为“假”。(注意:由于前一个条件,需要括号来覆盖AND
over的优先级OR
。)
就查询的执行方式以及性能而言,我认为这不会产生重大影响(如果有的话)(但坦率地说,我不确定这一点)。带有CASE ... WHEN
虽然的解决方案可能更易于阅读和维护。另一方面,仅布尔解决方案也适用于不支持CASE ... END
.