web-dev-qa-db-ja.com

WHERE句をSQLに動的/プログラムで追加する

プログラムでSQLストアドプロシージャに検索条件を追加するにはどうすればよいですか?私のアプリケーション(C#)では、ストアドプロシージャ(SQL Server 2008R2)を使用しています

ALTER PROCEDURE [dbo].[PROC001]
@userID varchar(20),
@password varchar(20)
AS
SELECT * FROM tUsers WHERE RTRIM(Name) = @userID AND RTRIM(Password) = @password

このクエリをさらに条件で拡張したいのですが、プログラムの実行により、このクエリを使用する条件がいくつあるかわかりません。2、3、6 OR 20。追加したいこれらの条件はプログラムで次のようになります。

SELECT * FROM tUsers WHERE RTRIM(Name) = @userID AND RTRIM(Password) = @password
AND Field2 = '1' AND Field3 = '0' OR Field4 <> '8' AND Field5 < '100' ....

条件をストアドプロシージャに動的に送信することは可能ですか?

13
mbigun

編集-可能であれば、LINQベースのORMの設定

ADOでこれを行う必要がない場合、より良い解決策は、最終的にパラメーター化されたアドホックSQLを構築するORMを使用することです。これは両方の長所です。オプティマイザーを混乱させる冗長フィルターがなく、動的クエリの柔軟性が得られ、クエリプラン自体がキャッシュ可能であり、インジェクション攻撃などの厄介な問題から安全です。また、LinqベースのORMクエリを使用すると、次のように読みやすくなります。

_ // Build up a non-materialized IQueryable<>
 var usersQuery = db.Users;
 if (!string.IsNullOrEmpty(userID))
 {
       usersQuery = usersQuery.Where(u => u.Name == userId);
 }
 // Of course, you wouldn't dream of storing passwords in cleartext.
 if (!string.IsNullOrEmpty(anotherField))
 {
       usersQuery = usersQuery.Where(u => u.AnotherColumn == anotherField);
 }
 ...
 // Materialize (and execute) the query
 var filteredUsers = usersQuery.ToList();
_

複雑なクエリの場合は、 PredicateBuilder を確認することをお勧めします。

ADO /手動クエリ構築

_sp_executesql_を使用して、以下のように動的にSQLを構築できます。変数をパラメーター化する場合、SQLインジェクションや引用符のエスケープなどの問題から安全である必要があります。

_CREATE PROCEDURE [dbo].[PROC001]
    @userID varchar(20),
    @pwdHash varchar(20),
    @optionalParam1 NVARCHAR(50) = NULL -- Other optional parameters
AS        
    BEGIN        
        SET NOCOUNT ON        

        DECLARE @SQL NVARCHAR(MAX)        

        -- Mandatory / Static part of the Query here. 
        -- Cleartext passwords are verboten, and RTRIM is redundant in filters
        SET @SQL = N'SELECT * FROM tUsers WHERE Name = @userID AND PwdHash = @pwdHash'

        IF @OptionalParam1 IS NOT NULL        
            BEGIN        
                SET @SQL = @SQL + N' AND AnotherField = @OptionalParam1'    
            END        

        EXEC sp_executesql @SQL,        
            N'@userID varchar(20),
            @pwdHash varchar(20),
            @optionalParam1 NVARCHAR(50)'
            ,@userID = @userID
            ,@pwdHash = @pwdHash
            ,@optionalParam1 = @optionalParam1
    END
_

Re、なぜWHERE (@x IS NULL OR @x = Column)は悪い考えなのですか?

(以下の私のコメントから)

「オプションパラメータ」パターンは、小さなテーブルで使用する場合にオプションフィルタの多数の順列をクエリするための「スイスアーミーナイフ」として適切に機能しますが、残念ながら、大きなテーブルの場合、これにより、次のフィルタのすべての順列に対して単一のクエリプランが作成されます。 パラメータスニッフィングの問題 が原因で、オプションのパラメータの特定の順列でクエリのパフォーマンスが低下する可能性があるクエリ。可能であれば、冗長なフィルターを完全に排除する必要があります。

Re:述語に関数を適用するのはなぜ悪い考えですか

例えば.

_WHERE SomeFunction(Column) = @someParameter
_

述語で関数を使用すると、RDBMSによるインデックスの使用が不適格になることがよくあります( "non-sargable" )。

この場合、SQLサーバーとしてRTRIMは不要です 無視 末尾のスペース 比較中

7
StuartLC

これは、次のようにSQLでのみ実行できます。

SELECT * 
FROM tUsers 
WHERE 1 = 1
  AND (@userID IS NULL OR RTRIM(Name) = @userID )
  AND (@password IS NULL OR RTRIM(Password) = @password)
  AND (@field2 IS NULL OR Field2 = @field2)
....

NULL値でストアドプロシージャに渡されたパラメータがある場合、条件全体が無視されます。

:クエリにパラメータが渡されない場合にクエリを機能させるためにWHERE 1 = 1を追加しました。この場合、1 = 1なので、結果セットが返されます。常に真です。

17
Mahmoud Gamal

プロシージャを文字列として使用し、条件、連結、およびexecを含む文字列を送信できます。

ALTER PROCEDURE [dbo].[PROC001] @userID varchar(20), @password varchar(20), @WhereToAdd varchar(MAX) AS 

exec ('SELECT * FROM tUsers WHERE RTRIM(Name) = @userID AND RTRIM(Password) = @password AND ' + @WhereToAdd)
2
Gonzalo.-