T-SQLストアドプロシージャでオプションのパラメーターを使用するにはどうすればよいですか?


185

テーブルを検索するストアドプロシージャを作成しています。さまざまな検索フィールドがあり、すべてオプションです。これを処理するストアドプロシージャを作成する方法はありますか?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でこれを実行できると思いますが、それは避けたいと思います。



2
次のwhereステートメントを試してください: codeISNULL(FirstName、 ')= ISNULL(@FirstName、' ')-これにより、すべてのNULLが空の文字列になり、それらをeqで比較できます。オペレーター。入力パラメーターがnullの場合にすべてのタイトルを取得したい場合は、codeFirstName = @FirstName OR @FirstName IS NULLのようなものを試してください。
baHI 2016年

回答:


257

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

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

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

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

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

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

これはどのSQL Serverバージョンでも機能します(適切な結果を返します)が、OPTION(RECOMPILE)が含まれるのは、SQL 2008 SP1 CU5(10.0.2746)以降を使用している場合のみです。OPTION(RECOMPILE)はクエリを再コンパイルします。リストされたバージョンのみがローカル変数の現在のランタイム値に基づいてクエリを再コンパイルします。これにより、最高のパフォーマンスが得られます。そのバージョンの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

15
AND / ORの優先順位に注意してください。ANDはORよりも優先されるため、適切な角かっこがないと、この例では期待した結果が得られません...したがって、次のようになります。(@ FirstName IS NULL OR(FirstName = @FirstName))AND(@LastNameIS NULL OR(LastName = @LastName))AND(@TitleIS NULL OR(Title = @Title))
Bliek

...(@FirstName IS NULL OR(FirstName = @FirstName)be be ...(FirstName = Coalesce(@ firstname、FirstName))
fcm

括弧を忘れないでください。そうしないと機能しません。
パブロカラスコエルナンデス

27

@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

このアプローチの利点は、ビスポーククエリで処理される一般的なケースで、クエリが可能な限り効率的であることです。指定されていない基準による影響はありません。また、インデックスやその他のパフォーマンス強化は、すべての可能な状況を満たそうとするのではなく、特定のカスタムクエリを対象にすることができます。


確かに、ケースごとに個別のストアドプロシージャを記述する方が適切です。その後、なりすましや再コンパイルの心配はありません。
Jodrell、2018年

5
言うまでもなく、このアプローチはすぐにメンテナンスの悪夢になります。
アタリオ2018年

3
@Atarioメンテナンスとパフォーマンスの容易さは一般的なトレードオフです。この答えはパフォーマンスを対象としています。
Rhys Jones

26

次の場合にできます

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

ただし、データに依存する場合は、動的クエリを作成して実行する方が適切な場合があります。


10

パーティーに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の記述により、ひどいクエリ(またはナビゲーションプロパティが読み込まれている場合は多くのクエリ)が生成される可能性があります。

1
すべての中間文字列が ''ではなくN ''であることを確認してください。SQLが8000文字を超えると、切り捨ての問題が発生します。
アランSingfield

1
また、ユーザーへの直接SELECT権限を拒否した場合は、ストアドプロシージャに "WITH EXECUTE AS OWNER"句を配置する必要がある場合があります。ただし、この句を使用する場合は、SQLインジェクションを避けてください。
アランSingfield

8

あなたの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, '') = '')

つまり、さまざまなケースをブール条件と組み合わせます。


-3

これも機能します:

    ...
    WHERE
        (FirstName IS NULL OR FirstName = ISNULL(@FirstName, FirstName)) AND
        (LastName IS NULL OR LastName = ISNULL(@LastName, LastName)) AND
        (Title IS NULL OR Title = ISNULL(@Title, Title))
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.