web-dev-qa-db-ja.com

T-SQLストアドプロシージャでオプションのパラメータを使用する方法を教えてください。

テーブルを検索するためのストアドプロシージャを作成しています。私はいろいろな検索フィールドを持っていますが、それらはすべてオプションです。これを処理するストアドプロシージャを作成する方法はありますか? ID、FirstName、LastName、Titleの4つのフィールドを持つテーブルがあるとしましょう。私はこのようなことをすることができます:

CREATE PROCEDURE spDoSearch
    @FirstName varchar(25) = null,
    @LastName varchar(25) = null,
    @Title varchar(25) = null
AS
    BEGIN
        SELECT ID, FirstName, LastName, Title
        FROM tblUsers
        WHERE
            FirstName = ISNULL(@FirstName, FirstName) AND
            LastName = ISNULL(@LastName, LastName) AND
            Title = ISNULL(@Title, Title)
    END

このような作品です。ただし、FirstName、LastName、またはTitleがNULLのレコードは無視されます。 Titleが検索パラメータで指定されていない場合は、TitleがNULLのレコードを含めます - FirstNameとLastNameの場合も同じです。私はおそらく動的SQLを使ってこれを行うことができることを知っていますが、私はそれを避けたいと思います。

175
Corey Burnett

与えられたパラメータに基づいて動的に検索を変更することは複雑な課題であり、ほんのわずかな違いがあっても、それを別の方法で行うと、パフォーマンスに大きな影響を与える可能性があります。重要なのは、インデックスを使用し、コンパクトコードを無視し、コードの繰り返しについて心配することを無視することです。適切なクエリ実行計画を立てる必要があります(インデックスを使用します)。

これを読んで、すべての方法を検討してください。最善の方法は、パラメータ、データ、スキーマ、および実際の使用方法によって異なります。

Erland SommarskogによるT-SQLの動的検索条件

Erland Sommarskogによる動的SQLの呪いと祝福

適切なバージョンのSQL Server 2008(SQL 2008 SP1 CU5(10.0.2746)以降)をお持ちの場合は、このちょっとしたトリックを使って実際にインデックスを使うことができます。

クエリにOPTION (RECOMPILE)を追加します Erlandの記事を参照 、SQL Serverはローカル変数の実行時の値に基づいてクエリプランが作成される前に(@LastName IS NULL OR LastName= @LastName)内からORを解決し、インデックスを使用できます。

これはどのバージョンのSQL Serverでも機能しますが(正しい結果が返されます)、SQL 2008 SP1 CU5(10.0.2746)以降を使用している場合は、OPTION(RECOMPILE)のみを含めます。 OPTION(RECOMPILE)はクエリを再コンパイルします。リストされたverisonのみがローカル変数の現在の実行時の値に基づいて再コンパイルします。これにより、最高のパフォーマンスが得られます。そのバージョンのSQL Server 2008にない場合は、その行を省略してください。

CREATE PROCEDURE spDoSearch
    @FirstName varchar(25) = null,
    @LastName varchar(25) = null,
    @Title varchar(25) = null
AS
    BEGIN
        SELECT ID, FirstName, LastName, Title
        FROM tblUsers
        WHERE
                (@FirstName IS NULL OR (FirstName = @FirstName))
            AND (@LastName  IS NULL OR (LastName  = @LastName ))
            AND (@Title     IS NULL OR (Title     = @Title    ))
        OPTION (RECOMPILE) ---<<<<use if on for SQL 2008 SP1 CU5 (10.0.2746) and later
    END
249
KM.

@KMからの答えはそれができる限りは良いのですが、彼の初期のアドバイスの1つについて完全にフォローアップすることはできません。

...、コンパクトコードを無視し、コードの繰り返しについての心配を無視し、...

あなたが最高のパフォーマンスを達成しようとしているなら、あなたはオプションの基準の可能な組み合わせごとにオーダーメイドのクエリを書くべきです。これは極端に聞こえるかもしれません、そしてあなたが多くのオプションの基準を持っているならそれはそうかもしれませんが、パフォーマンスはしばしば努力と結果の間のトレードオフです。実際には、オーダーメイドのクエリでターゲットを設定できる共通のパラメータの組み合わせのセットがあり、次に他のすべての組み合わせの一般的なクエリー(他の答えと同様)があります。

CREATE PROCEDURE spDoSearch
    @FirstName varchar(25) = null,
    @LastName varchar(25) = null,
    @Title varchar(25) = null
AS
BEGIN

    IF (@FirstName IS NOT NULL AND @LastName IS NULL AND @Title IS NULL)
        -- Search by first name only
        SELECT ID, FirstName, LastName, Title
        FROM tblUsers
        WHERE
            FirstName = @FirstName

    ELSE IF (@FirstName IS NULL AND @LastName IS NOT NULL AND @Title IS NULL)
        -- Search by last name only
        SELECT ID, FirstName, LastName, Title
        FROM tblUsers
        WHERE
            LastName = @LastName

    ELSE IF (@FirstName IS NULL AND @LastName IS NULL AND @Title IS NOT NULL)
        -- Search by title only
        SELECT ID, FirstName, LastName, Title
        FROM tblUsers
        WHERE
            Title = @Title

    ELSE IF (@FirstName IS NOT NULL AND @LastName IS NOT NULL AND @Title IS NULL)
        -- Search by first and last name
        SELECT ID, FirstName, LastName, Title
        FROM tblUsers
        WHERE
            FirstName = @FirstName
            AND LastName = @LastName

    ELSE
        -- Search by any other combination
        SELECT ID, FirstName, LastName, Title
        FROM tblUsers
        WHERE
                (@FirstName IS NULL OR (FirstName = @FirstName))
            AND (@LastName  IS NULL OR (LastName  = @LastName ))
            AND (@Title     IS NULL OR (Title     = @Title    ))

END

このアプローチの利点は、特注のクエリで処理される一般的なケースでは、クエリは可能な限り効率的であるということです。供給されていない基準による影響はありません。また、インデックスやその他のパフォーマンスの向上は、考えられるすべての状況を満たすのではなく、特定のオーダーメイドのクエリを対象にすることができます。

26
Rhys Jones

次のような場合には、

CREATE PROCEDURE spDoSearch
   @FirstName varchar(25) = null,
   @LastName varchar(25) = null,
   @Title varchar(25) = null
AS
  BEGIN
      SELECT ID, FirstName, LastName, Title
      FROM tblUsers
      WHERE
        (@FirstName IS NULL OR FirstName = @FirstName) AND
        (@LastNameName IS NULL OR LastName = @LastName) AND
        (@Title IS NULL OR Title = @Title)
END

ただし、データに依存する場合は、動的クエリを作成して実行することをお勧めします。

25

WHERE条件を拡張します。

WHERE
    (FirstName = ISNULL(@FirstName, FirstName)
    OR COALESCE(@FirstName, FirstName, '') = '')
AND (LastName = ISNULL(@LastName, LastName)
    OR COALESCE(@LastName, LastName, '') = '')
AND (Title = ISNULL(@Title, Title)
    OR COALESCE(@Title, Title, '') = '')

私。 e。異なるケースをブール条件と組み合わせます。

8
devio

パーティーに5年遅れた。

それは受け入れられた答えの提供されたリンクで言及されます、しかし私はそれがSOの上の明白な答えに値すると思います - 提供されたパラメータに基づいてクエリを動的に構築する。例えば。:

設定

-- drop table Person
create table Person
(
    PersonId INT NOT NULL IDENTITY(1, 1) CONSTRAINT PK_Person PRIMARY KEY,
    FirstName NVARCHAR(64) NOT NULL,
    LastName NVARCHAR(64) NOT NULL,
    Title NVARCHAR(64) NULL
)
GO

INSERT INTO Person (FirstName, LastName, Title)
VALUES ('Dick', 'Ormsby', 'Mr'), ('Serena', 'Kroeger', 'Ms'), 
    ('Marina', 'Losoya', 'Mrs'), ('Shakita', 'Grate', 'Ms'), 
    ('Bethann', 'Zellner', 'Ms'), ('Dexter', 'Shaw', 'Mr'),
    ('Zona', 'Halligan', 'Ms'), ('Fiona', 'Cassity', 'Ms'),
    ('Sherron', 'Janowski', 'Ms'), ('Melinda', 'Cormier', 'Ms')
GO

手続き

ALTER PROCEDURE spDoSearch
    @FirstName varchar(64) = null,
    @LastName varchar(64) = null,
    @Title varchar(64) = null,
    @TopCount INT = 100
AS
BEGIN
    DECLARE @SQL NVARCHAR(4000) = '
        SELECT TOP ' + CAST(@TopCount AS VARCHAR) + ' *
        FROM Person
        WHERE 1 = 1'

    PRINT @SQL

    IF (@FirstName IS NOT NULL) SET @SQL = @SQL + ' AND FirstName = @FirstName'
    IF (@LastName IS NOT NULL) SET @SQL = @SQL + ' AND FirstName = @LastName'
    IF (@Title IS NOT NULL) SET @SQL = @SQL + ' AND Title = @Title'

    EXEC sp_executesql @SQL, N'@TopCount INT, @FirstName varchar(25), @LastName varchar(25), @Title varchar(64)', 
         @TopCount, @FirstName, @LastName, @Title
END
GO

使い方

exec spDoSearch @TopCount = 3
exec spDoSearch @FirstName = 'Dick'

長所:

  • 書きやすく理解しやすい
  • 柔軟性 - トリッキーなフィルタリングのためのクエリを簡単に生成する(例えば動的TOP)

短所:

  • 提供されたパラメータ、インデックス、およびデータ量によっては、パフォーマンス上の問題が発生する可能性があります。

直接的な答えではありませんが、全体像とも呼ばれる問題に関連しています

通常、これらのフィルタリングストアドプロシージャは浮動しませんが、何らかのサービス層から呼び出されます。これにより、ビジネスロジック(フィルタ処理)をSQLからサービス層に移行するという選択肢が残ります。

1つの例は、提供されたフィルタに基づいてクエリを生成するためにLINQ2SQLを使用することです。

    public IList<SomeServiceModel> GetServiceModels(CustomFilter filters)
    {
        var query = DataAccess.SomeRepository.AllNoTracking;

        // partial and insensitive search 
        if (!string.IsNullOrWhiteSpace(filters.SomeName))
            query = query.Where(item => item.SomeName.IndexOf(filters.SomeName, StringComparison.OrdinalIgnoreCase) != -1);
        // filter by multiple selection
        if ((filters.CreatedByList?.Count ?? 0) > 0)
            query = query.Where(item => filters.CreatedByList.Contains(item.CreatedById));
        if (filters.EnabledOnly)
            query = query.Where(item => item.IsEnabled);

        var modelList = query.ToList();
        var serviceModelList = MappingService.MapEx<SomeDataModel, SomeServiceModel>(modelList);
        return serviceModelList;
    }

長所:

  • 提供されたフィルタに基づいて動的に生成されたクエリ。いいえ パラメーター・スニッフィング または 再コンパイル ヒントが必要
  • OOPの世界の人々のために書くのがやや簡単
  • 「単純な」クエリが発行されるため、通常はパフォーマンスに優しいです(ただし、適切なインデックスが依然として必要です)。

短所:

  • 場合によっては、LINQ2QLの制限に達してLINQ2Objectsにダウングレードするか、純粋なSQLソリューションに戻ることがあります。
  • lINQを不用意に書くと、ひどいクエリ(ナビゲーションプロパティがロードされている場合は多くのクエリ)が生成される可能性があります。
7
Alexei