最初に1 = 1を使用するよりも、SQL WHERE句を動的に構築するより良い方法はありますか?


110

私はいくつかの建物だSQLの C#でクエリを。コードに変数として格納されているいくつかの条件によって異なります。

string Query="SELECT * FROM Table1 WHERE 1=1 ";
if (condition1) 
    Query += "AND Col1=0 ";
if (condition2) 
    Query += "AND Col2=1 ";
if (condition3) 
    Query += "AND Col3=2 ";

動作しますが、1 = 1のテストはエレガントに見えません。使用しなかった場合は、クエリに "where"キーワードが既に追加されているかどうかを毎回覚えて確認する必要があります。

より良い解決策はありますか?


118
正直に言うと、私もこのようにしますが、私は42 = 42;-) を使用します
fero

5
私はいつもこのように私のクエリを書いています。条件をコメントアウトしやすくします
Deruijter

4
@catfood私がインターンとして最初に取り組んだプロジェクトは、Sybaseサーバーに対するパフォーマンスクエリの分析に役立つツールを作成することでした。面白い発見は、Select 42私たちが受け取っていた何十万ものクエリでした。(面白くないのは、ソースを追跡すること
でした

24
If I didn't use it, I would have to remember and check every time if "where" keyword was already added or not to the query-それがあなたが使う理由です1 = 1。とにかくデータベースエンジンが最適化するので、見た目は醜いかもしれませんが、問題を解決するための最も簡単な方法です。
Robert Harvey 2013年

4
与えられた答えはとても良いですが、私はあなたの元のコードが最も読みやすいと思います。
Uooo 2013年

回答:


157

条件をリストに保存します。

List<string> conditions = new List<string>();

if (condition1) conditions.Add("Col1=0");
//...
if (conditions.Any())
    Query += " WHERE " + string.Join(" AND ", conditions.ToArray());

24
良い解決策ですが、ToArray().NET 4ではすべてを受け入れるオーバーロードがあるため、これは必要ありませんIEnumerable<string>
フェロ2013年

101
これが提供するすべてのSQLインジェクションの機会に興奮しています。
アステリ2013年

12
@Jeff where句の値をハードコーディングしていない場合は、SqlParametersを使用して2番目のリストを作成することもできます。条件リストと同時にそのリストにデータを入力し、最後にAddRange(parameters.ToArray())を呼び出すだけです
Scott Chamberlain 2013年

5
@ScottChamberlainええ、入力文字列をリストに入れる前に、単にエスケープすることもできます。私は主に、面白そうなユーモアを使った攻撃の可能性について警告していました。
アステリ2013年

4
@Jeff条件にユーザー入力が含まれている場合、SQLインジェクションに対してのみ脆弱です(元の例には含まれていません)
Dスタンリー

85

1つの解決策は、文字列を追加してクエリを手動で記述しないことです。Entity FrameworkなどのORMを使用できます。また、LINQ to Entitiesでは、言語とフレームワークが提供する機能を使用します。

using (var dbContext = new MyDbContext())
{
    IQueryable<Table1Item> query = dbContext.Table1;

    if (condition1)
    {
        query = query.Where(c => c.Col1 == 0);
    }
    if (condition2)
    {
        query = query.Where(c => c.Col2 == 1);
    }
    if (condition3)
    {
        query = query.Where(c => c.Col3 == 2);
    }   

    PrintResults(query);
}

@vaheedsその質問は理解できません。どちらも異なるORMです。
CodeCaster 2016

申し訳ありませんが、dapperのパフォーマンスを他のORMと比較するために検索していましたが、Googleから取得したため、生成さPrintResults(query)れたクエリはdapperでクエリとして使用されると思いました!!
vaheeds 2016

@vaheedsは大丈夫ですが、答えを理解していなくても反対票は出されません。それがあなただったら、それは偶然にもあなたのコメントと同時に起こった。
CodeCaster 2016

あなたの権利、それは誤解でした。複雑なクエリでのlinq to entityのパフォーマンスが悪い。私はあなたの他の回答賛成投票で反対票を
埋め

それは質問に対する答えではありません
HGMamaci 2017

17

この単純なケースでは少しやりすぎですが、過去にこれに似たコードを使用しました。

関数を作成する

string AddCondition(string clause, string appender, string condition)
{
    if (clause.Length <= 0)
    {
        return String.Format("WHERE {0}",condition);
    }
    return string.Format("{0} {1} {2}", clause, appender, condition);
}

このように使う

string query = "SELECT * FROM Table1 {0}";
string whereClause = string.Empty;

if (condition 1)
    whereClause = AddCondition(whereClause, "AND", "Col=1");

if (condition 2)
    whereClause = AddCondition(whereClause, "AND", "Col2=2");

string finalQuery = String.Format(query, whereClause);

このようにして、条件が見つからない場合でも、クエリのwhereステートメントをロードする必要がなく、SQLステートメントを解析するときにSQLサーバーがジャンクwhere句を処理するマイクロ秒を節約できます。


これがいかにエレガントになるかはわかりません。ここで何が行われているのかは明確ではありません。そのユーティリティ関数の使用を見ることができますが、それはよりエレガントではありません。
usr

マイクロ秒の重要性について私たちを啓蒙するためにあなたに1つの投票を与えました
user1451111

15

別の解決策がありますが、これもエレガントではないかもしれませんが、機能して問題を解決します。

String query = "SELECT * FROM Table1";
List<string> conditions = new List<string>();
// ... fill the conditions
string joiner = " WHERE ";
foreach (string condition in conditions) {
  query += joiner + condition;
  joiner = " AND "
}

ために:

  • 空の条件リスト、結果は単純SELECT * FROM Table1になります、
  • 単一の状態になります SELECT * FROM Table1 WHERE cond1
  • 以下の各条件により、追加の AND condN

6
WHERE述語がない場合は、ぶら下がっています。1 = 1はそれを避けるために明確に存在します。
Gaius

に切り替えてString query = "SELECT * FROM Table1";string jointer = " WHERE ";
ブレンダンロング

@BrendanLong次にWHEREANDsは条件間に配置されますか?
PenguinCoder 2013年

@PenguinCoderコメントに完全なコードを表示するのは難しいです。このstring joiner行をstring joiner = " WHERE ";に置き換え、その行はそのままにしておきましたjoiner = " AND ";
ブレンダンロング

@Gaius私は条件が空ではないと想定しましたが、ジョイナーにWHEREを置くとうまくいきます。発言ありがとうございます!
Dariusz 2013年

11

次のようなことをしてください:

using (var command = connection.CreateCommand())
{
    command.CommandText = "SELECT * FROM Table1";

    var conditions = "";
    if (condition1)
    {    
        conditions += "Col1=@val1 AND ";
        command.AddParameter("val1", 1);
    }
    if (condition2)
    {    
        conditions += "Col2=@val2 AND ";
        command.AddParameter("val2", 1);
    }
    if (condition3)
    {    
        conditions += "Col3=@val3 AND ";
        command.AddParameter("val3", 1);
    }
    if (conditions != "")
        command.CommandText += " WHERE " + conditions.Remove(conditions.Length - 5);
}

これのSQLインジェクションの安全私見では、それはかなりきれいです。Remove()単に最後を削除しますANDます。

条件が設定されていない場合、1つが設定されている場合、または複数が設定されている場合の両方で機能します。


1
わかりません(自分でC#を使用しないでください)が、それを初期化するときに(C#を除く)、conditions != null常にtrueであると言います。空でない場合、おそらくチェックになるはずです;-)"""" == nullconditions
siegi

9

後ろに2行追加するだけです。

string Query="SELECT * FROM Table1 WHERE 1=1 ";
if (condition1) Query+="AND Col1=0 ";
if (condition2) Query+="AND Col2=1 ";
if (condition3) Query+="AND Col3=2 ";
Query.Replace("1=1 AND ", "");
Query.Replace(" WHERE 1=1 ", "");

例えば

SELECT * FROM Table1 WHERE 1=1 AND Col1=0 AND Col2=1 AND Col3=2 

になる

SELECT * FROM Table1 WHERE Col1=0 AND Col2=1 AND Col3=2 

ながら

SELECT * FROM Table1 WHERE 1=1 

になる

SELECT * FROM Table1

=====================================

このソリューションの欠陥を指摘してくれてありがとう:

「これは、何らかの理由で条件の1つに「1 = 1 AND」または「WHERE 1 = 1」というテキストが含まれている場合にクエリを中断する可能性があります。これは、条件にサブクエリが含まれている場合や、たとえば、列にはこのテキストが含まれています。これはあなたのケースでは問題ではないかもしれませんが、覚えておく必要があります... "

この問題を回避するには、「メイン」のWHERE 1 = 1とサブクエリのWHERE 1 = 1を区別する必要があります。これは簡単です。

「メイン」のWHEREを特別にするだけです:「$」記号を追加します

string Query="SELECT * FROM Table1 WHERE$ 1=1 ";
if (condition1) Query+="AND Col1=0 ";
if (condition2) Query+="AND Col2=1 ";
if (condition3) Query+="AND Col3=2 ";

次に、2行追加します。

Query.Replace("WHERE$ 1=1 AND ", "WHERE ");
Query.Replace(" WHERE$ 1=1 ", "");

1
これは、何らかの理由で条件の1つにテキスト"1=1 AND "またはが含まれている場合にクエリを中断する可能性があります" WHERE 1=1 "。たとえば、条件にサブクエリが含まれている場合や、一部の列にこのテキストが含まれているかどうかを確認しようとした場合などです。多分これはあなたの場合問題ではないかもしれませんが、あなたはそれを覚えておくべきです…
siegi

8

これを使って:

string Query="SELECT * FROM Table1 WHERE ";
string QuerySub;
if (condition1) QuerySub+="AND Col1=0 ";
if (condition2) QuerySub+="AND Col2=1 ";
if (condition3) QuerySub+="AND Col3=2 ";

if (QuerySub.StartsWith("AND"))
    QuerySub = QuerySub.TrimStart("AND".ToCharArray());

Query = Query + QuerySub;

if (Query.EndsWith("WHERE "))
    Query = Query.TrimEnd("WHERE ".ToCharArray());

この回答は動作します、そしてそれには本当に何の問題もありませんが、私はそれがないと思うより元の質問よりクリーンでシンプル。文字列検索QuerySubは、私の意見では、where 1=1ハックを使用するよりも良くも悪くもありません。しかし、それは思慮深い貢献です。
catfood 2013年

3
エラーが発生しました。修正しました。条件が存在しない場合、私のクエリは爆破されたでしょう。私はあなたたちに代わりを提示しただけです!
Anshuman 2013年

これは一般的にはまだ間違っています。それがあったと仮定してください... FROM SOMETABLE WHERE 。その後、TrimEnd実際にこれをに減らし... FROM SOMETABLます。これが実際にStringBuilder(これ以上の文字列操作を行っているか、またはそれ以上である場合)なら、ちょうどできますQuery.Length -= "WHERE ".Length;
Mark Hurd 2013年

マーク、それは機能します。私は多くのプロジェクトでこれを試しました。試してみてください。
Anshuman 2013年

8
地獄のように醜い:)さらに、私が正しく数えた場合、最大7個の文字列を作成できます
Piotr Perak

5

既存のクエリビルダーを使用しないのはなぜですか?Sql Kataのようなもの

複雑なwhere条件、結合、サブクエリをサポートします。

var query = new Query("Users").Where("Score", ">", 100).OrderByDesc("Score").Limit(100);

if(onlyActive)
{
   query.Where("Status", "active")
}

// or you can use the when statement

query.When(onlyActive, q => q.Where("Status", "active"))

Sql Server、MySql、PostgreSqlで動作します。


4

私が考えることができるとあなたが求めていることに対する最も迅速な文字どおりの解決策はこれです:

string Query="SELECT * FROM Table1";
string Conditions = "";

if (condition1) Conditions+="AND Col1=0 ";
if (condition2) Conditions+="AND Col2=1 ";
if (condition3) Conditions+="AND Col3=2 ";

if (Conditions.Length > 0) 
  Query+=" WHERE " + Conditions.Substring(3);

ORMを使用するというCodeCasterの推奨を参照するのは、確かにエレガントに思えません。しかし、ここでこれが何をしているのかを考えれば、4文字のメモリを「無駄にする」ことを心配する必要はありません。また、コンピューターがポインターを4箇所移動するのは非常に簡単です。

ORMの使用方法を学ぶ時間があれば、実際に報われるでしょう。しかし、これに関して、SQL dbにヒットしないようにその追加の条件を維持しようとしている場合は、これでうまくいきます。


4

これがSQL Serverの場合である場合、このコードをよりクリーンにすることができます。

これは既知の数のパラメーターも想定しているため、可能性について考えると、これは不十分な想定になる可能性があります。

C#では、以下を使用します。

using (SqlConnection conn = new SqlConnection("connection string"))
{
    conn.Open();
    SqlCommand command = new SqlCommand()
    {
        CommandText = "dbo.sample_proc",
        Connection = conn,
        CommandType = CommandType.StoredProcedure
    };

    if (condition1)
        command.Parameters.Add(new SqlParameter("Condition1", condition1Value));
    if (condition2)
        command.Parameters.Add(new SqlParameter("Condition2", condition2Value));
    if (condition3)
        command.Parameters.Add(new SqlParameter("Condition3", condition3Value));

    IDataReader reader = command.ExecuteReader();

    while(reader.Read())
    {
    }

    conn.Close();
}

そしてSQL側では:

CREATE PROCEDURE dbo.sample_proc
(
    --using varchar(50) generically
    -- "= NULL" makes them all optional parameters
    @Condition1 varchar(50) = NULL
    @Condition2 varchar(50) = NULL
    @Condition3 varchar(50) = NULL
)
AS
BEGIN
    /*
    check that the value of the parameter 
    matches the related column or that the 
    parameter value was not specified.  This
    works as long as you are not querying for 
    a specific column to be null.*/
    SELECT *
    FROM SampleTable
    WHERE (Col1 = @Condition1 OR @Condition1 IS NULL)
    AND   (Col2 = @Condition2 OR @Condition2 IS NULL)
    AND   (Col3 = @Condition3 OR @Condition3 IS NULL)
    OPTION (RECOMPILE)
    --OPTION(RECOMPILE) forces the query plan to remain effectively uncached
END

式の中、あなたの列を非表示にする索引の使用を防ぐことができ、そしてこの技術はこのような理由のために推奨され、ここで
bbsimonbb 2016年

それは興味深い発見です。その情報をありがとう。更新予定
mckeejm

3

条件によっては、クエリでブールロジックを使用できる場合があります。このようなもの :

string Query="SELECT * FROM Table1  " +
             "WHERE (condition1 = @test1 AND Col1=0) "+
             "AND (condition2 = @test2 AND Col2=1) "+
             "AND (condition3 = @test3 AND Col3=2) ";

3

私はstringbuilderの流暢なインターフェースが好きなので、ExtensionMethodをいくつか作成しました。

var query = new StringBuilder()
    .AppendLine("SELECT * FROM products")
    .AppendWhereIf(!String.IsNullOrEmpty(name), "name LIKE @name")
    .AppendWhereIf(category.HasValue, "category = @category")
    .AppendWhere("Deleted = @deleted")
    .ToString();

var p_name = GetParameter("@name", name);
var p_category = GetParameter("@category", category);
var p_deleted = GetParameter("@deleted", false);
var result = ExecuteDataTable(query, p_name, p_category, p_deleted);


// in a seperate static class for extensionmethods
public StringBuilder AppendLineIf(this StringBuilder sb, bool condition, string value)
{
    if(condition)
        sb.AppendLine(value);
    return sb;
}

public StringBuilder AppendWhereIf(this StringBuilder sb, bool condition, string value)
{
    if (condition)
        sb.AppendLineIf(condition, sb.HasWhere() ? " AND " : " WHERE " + value);
    return sb;
}

public StringBuilder AppendWhere(this StringBuilder sb, string value)
{
    sb.AppendWhereIf(true, value);
    return sb;
}

public bool HasWhere(this StringBuilder sb)
{
    var seperator = new string [] { Environment.NewLine };
    var lines = sb.ToString().Split(seperator, StringSplitOptions.None);
    return lines.Count > 0 && lines[lines.Count - 1].Contains("where", StringComparison.InvariantCultureIgnoreCase);
}

// http://stackoverflow.com/a/4217362/98491
public static bool Contains(this string source, string toCheck, StringComparison comp)
{
    return source.IndexOf(toCheck, comp) >= 0;
}

2

私見、あなたのアプローチは間違っていると思います:

文字列を連結してデータベースをクエリすることは決して良い考えではありませんSQLインジェクションのリスクはありませんあり、他の場所で変更を行うとコードが壊れる可能)。

あなたは使用することができますORM(私が使用してNHibernateのを)、または少なくとも使用をSqlCommand.Parameters

文字列連結を絶対に使用したい場合は、StringBuilder(文字列連結に適したオブジェクトです)を使用します。

var query = new StringBuilder("SELECT * FROM Table1 WHERE");
int qLength = query.Length;//if you don't want to count :D
if (Condition1) query.Append(" Col1=0 AND");
if (Condition2) query.Append(" Col2=0 AND");
....
//if no condition remove WHERE or AND from query
query.Length -= query.Length == qLength ? 6 : 4;

最後の考えとして、Where 1=1本当に醜いですが、SQL Serverはとにかくそれを最適化します。


SELECT * FROM Table1 WHERE AND Col1=0は正しくないようです。これがのポイントですWHERE 1=1
Mormegil 2013

2

Dapper SqlBuilderはかなり良いオプションです。StackOverflowの本番環境でも使用されます。

それに関するサムのブログエントリを読んでください。

私の知る限り、これはNugetパッケージの一部ではないため、そのコードをプロジェクトにコピーして貼り付けるか、DapperソースをダウンロードしてSqlBuilderプロジェクトをビルドする必要があります。どちらの方法でも、DynamicParametersクラスのDapperを参照する必要があります。


1
DapperのSqlBuilderがそのパッケージに含まれているとは思いません。
ロニーオーバーバイ2014年

1

これは、ストアドプロシージャ内で動的SQLを構築する際に、Oracleで常に使用されていることがわかりました。クエリでデータの問題を調査するときだけでなく、データのさまざまなフィルター間の切り替えを速くするためにも使用します...条件をコメント化するか、簡単に追加し直すことができます。

私はそれがかなり一般的で、コードをレビューしている誰かに理解するのに十分簡単であることを発見しました。


1
public static class Ext
{
    public static string addCondition(this string str, bool condition, string statement)
    {
        if (!condition)
            return str;

        return str + (!str.Contains(" WHERE ") ? " WHERE " : " ") + statement;
    }

    public static string cleanCondition(this string str)
    {
        if (!str.Contains(" WHERE "))
            return str;

        return str.Replace(" WHERE AND ", " WHERE ").Replace(" WHERE OR ", " WHERE ");
    }
}

拡張メソッドによる実現。

    static void Main(string[] args)
    {
        string Query = "SELECT * FROM Table1";

        Query = Query.addCondition(true == false, "AND Column1 = 5")
            .addCondition(18 > 17, "AND Column2 = 7")
            .addCondition(42 == 1, "OR Column3 IN (5, 7, 9)")
            .addCondition(5 % 1 > 1 - 4, "AND Column4 = 67")
            .addCondition(Object.Equals(5, 5), "OR Column5 >= 0")
            .cleanCondition();

        Console.WriteLine(Query);
    }

穀物に逆らう!
Ronnie Overby 2013年

すみません?どういう意味ですか?
Maxim Zhukov 2013年

0

string関数を使用して、次のようにすることもできます。

string Query = "select * from Table1";

if (condition1) WhereClause += " Col1 = @param1 AND "; // <---- put conditional operator at the end
if (condition2) WhereClause += " Col1 = @param2 OR ";

WhereClause = WhereClause.Trim();

if (!string.IsNullOrEmpty(WhereClause))
    Query = Query + " WHERE " + WhereClause.Remove(WhereClause.LastIndexOf(" "));
// else
// no condition meets the criteria leave the QUERY without a WHERE clause  

位置は予測しやすいので、個人的には最後に条件要素を削除するのは簡単だと思います。


0

まあ、おそらくもっと読みやすいソリューションを考えました:

string query = String.Format("SELECT * FROM Table1 WHERE "
                             + "Col1 = {0} AND "
                             + "Col2 = {1} AND "
                             + "Col3 = {2}",
                            (!condition1 ? "Col1" : "0"),
                            (!condition2 ? "Col2" : "1"),
                            (!condition3 ? "Col3" : "2"));

SQLインタープリターがCol1 = Col1条件を最適化するかどうかがわかりません(condition1falseの場合に出力されます)。


0

これはよりエレガントな方法です:

    private string BuildQuery()
    {
        string MethodResult = "";
        try
        {
            StringBuilder sb = new StringBuilder();

            sb.Append("SELECT * FROM Table1");

            List<string> Clauses = new List<string>();

            Clauses.Add("Col1 = 0");
            Clauses.Add("Col2 = 1");
            Clauses.Add("Col3 = 2");

            bool FirstPass = true;

            if(Clauses != null && Clauses.Count > 0)
            {
                foreach(string Clause in Clauses)
                {
                    if (FirstPass)
                    {
                        sb.Append(" WHERE ");

                        FirstPass = false;

                    }
                    else
                    {
                        sb.Append(" AND ");

                    }

                    sb.Append(Clause);

                }

            }

            MethodResult = sb.ToString();

        }
        catch //(Exception ex)
        {
            //ex.HandleException()
        }
        return MethodResult;
    }

0

すでに述べたように、連結によってSQLを作成することは決して良い考えではありません。SQLインジェクションのためだけではありません。ほとんどの場合、それは単に醜く、維持するのが難しく、まったく不要です。生成するSQLを確認するには、トレースまたはデバッグを使用してプログラムを実行する必要があります。QueryFirst(免責事項:私が書いたもの)を使用すると、不幸な誘惑が取り除かれ、SQLでそれを直接行うことができます。

このページには、検索述語を動的に追加するためのTSQLオプションが包括的にカバーされています。次のオプションは、検索述語の組み合わせの選択をユーザーに任せたい場合に便利です。

select * from table1
where (col1 = @param1 or @param1 is null)
and (col2 = @param2 or @param2 is null)
and (col3 = @param3 or @param3 is null)
OPTION (RECOMPILE)

QueryFirstはC#nullからdb NULLを提供するため、必要に応じてnullを指定してExecute()メソッドを呼び出すだけで、すべてが正常に機能します。<意見>なぜC#開発者は、SQLでの作業が単純な場合でも、SQLでの作業に消極的であるのですか。気をつけてください。</ opinion>


0

多くの人が言うように、より長いフィルタリング手順では、StringBuilderがより良いアプローチです。

あなたの場合私は行くでしょう:

StringBuilder sql = new StringBuilder();

if (condition1) 
    sql.Append("AND Col1=0 ");
if (condition2) 
    sql.Append("AND Col2=1 ");
if (condition3) 
    sql.Append("AND Col3=2 ");

string Query = "SELECT * FROM Table1 ";
if(sql.Length > 0)
 Query += string.Concat("WHERE ", sql.ToString().Substring(4)); //avoid first 4 chars, which is the 1st "AND "

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.