web-dev-qa-db-ja.com

パラメータ値に基づく「AND」のケースステートメントを含むストアドプロシージャ

成功したログイン試行と失敗したログイン試行の両方を保存するテーブルがdbにあります。 X日以上経過したレコードを削除できるストアドプロシージャを作成しています。これまでのところ良好ですが、私はそれを1つか2つ上げて、[Success]列がtrue、false、またはその両方であるかどうかのレコードを削除するかどうかを指定できるようにしたいと考えています。ただし、実行する必要のあるスクリプトの連結に問題があります。

これが私がこれまでに行ったことです:

-- 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

この時点では、@ backDays変数に基づいて行を選択しようとしているだけですが、@ query文字列を変数に連結できません。ここで何が間違っているのかわからない。ただし、動的クエリは初めてです。

3
pmdci

ここでは動的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;

(サンプルデータが提供されていないため、テストされていません。アイデアを示すためだけです。)

よく見ると、CASE ... END全体が=演算の左オペランドであることがわかります。操作の右側は1です。

残念ながら、SQL ServerはCASE ... ENDから直接返すことができるブール型を認識していません。 (その他、たとえばPostgreSQLは行います。)ブール演算に使用できる式を使用して、トリックする必要があります。そのため、=操作を使用しています。

ここで、実際にテストしたい条件が満たされたときに、この操作をtrueと評価する方法が必要です。つまり、決定に達した場合、それらが満たされると、=の左側に指定された値を1つ返すという考え方です。反対に、その値をそのまま使用します。その場合、=はtrueになります。条件が満たされない場合、左側に他の値が返され、=はtrueになりません。条件が満たされたことを示す値として、1を選択します。これは、私たちが考えているブール値の表現に近いものです。 (しかし、ほとんど何でも選択できます(NOT NULL、それ以外の場合は=操作をIS NULLに変更する必要がありました))。

では、条件が満たされたときに、左の式が1を返すようにするにはどうすればよいでしょうか。

さて、CASE ... ENDステートメントを使用して、特定の条件に基づいて値を返すことができます。あなたはすでにCASE ... ENDを知っています。これは、Cのような言語(または他の手続き型言語では名前が異なる)のswitchまたはif else構成要素にいくぶん似ています。

テストする必要があるのは、入力変数@successArgがnullかどうかです。 nullの場合、ログに記録されたログイン試行が成功したかどうかにかかわらず、呼び出し元は気にしません。それ以外の場合、@successArgの値は、成功したログインのみ(@successArg = 1)または失敗したログインのみ(@successArg = 0)が必要かどうかを示します。これにより、最優先のケースが得られます。成功を無視するか、それを考慮に入れてください。したがって、CASE ... ENDに関する@successArg IS NOT NULLブランチには2つの(メイン)ブランチがあります。

@successArg IS NULLの場合の簡単なケースから始めましょう。それが「ELSE」ブランチです。ここでは、ログイン試行が成功したかどうかは関係ありません。成功したかどうかは(ブール式と考えると)、どの行でも常にtrueです。したがって、インジケーターを返します。これは一致でした、1

@successArg IS NULL@successArgが値を保持している場合、行は列[Success]にある必要があります。これは、@successArg = [Success]の場合にのみ指定されます。すでに述べたように、@successArg = [Success]だけでは、SQL Serverはそのコンテキストで処理できません。そのため、現在の行の1が返されたときにtrueを示す値@successArg = [Success]を返す必要があります。

これを行う方法の1つは、別の内部CASE ... WHENか、相関サブクエリを使用することです。外部クエリの現在の行の列の値を使用するため、これは「相関」しています。そして、それは外部クエリの位置から見ると「サブ」なので、「サブ」です。さらに、SQL Serverの機能を使用して、そのクエリのSELECTがtrueの場合(または何もない場合)、FROMのないWHEREは1つのレコードを生成します。それ以外の場合は空のセット。それでは、1のときに@successArg = [Success]を保持する1つの列を持つレコードを作成してみましょう。列が1つしかないこのようなレコードは、必要に応じて暗黙的にスカラーにキャストされるため、外部の=操作に適しています。 @successArg <> [Success]の場合、そのサブクエリは空のセットを生成します。そのスカラーコンテキストの空のセットは、暗黙的にNULLにキャストされます。 NULL = 1は真実ではないので、これでうまくいきます。

これに関する1つの注意点:CASE ... ENDを使用せずに、ブール式を使用することもできます。

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

それは上からの私たちの考えに従います。 @successArg IS NULLの場合、他に関係なく「true」が必要です。それ以外の場合は、[Success] = @successArgの場合にのみ「true」が必要です。それ以外の場合は「false」。 (注:前の条件のため、ANDの優先順位をORよりも優先するには、括弧が必要です。)

クエリがどのように実行されるか、つまりパフォーマンスに関しては、これが大きな違いになるとは思いません(正直なところ、これは確かにわかりません)。ただし、CASE ... WHENを使用したソリューションの方が読みやすく、保守が容易な場合があります。一方、ブール値のみのソリューションは、CASE ... ENDに対応していないシステムでも機能します。

6
sticky bit