有没有比在开始时使用1 = 1更好的动态构建SQL WHERE子句的方法?


110

我正在C#中建立一些SQL查询。根据某些作为变量存储在代码中的条件,它会有所不同。

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我们收到数十万条查询。(有趣的是试图寻找源头)
Mindor先生

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。无论如何,数据库引擎都会对其进行优化,因此尽管看起来很丑陋,但这是解决问题的最简单方法。
罗伯特·哈维

4
尽管给出的答案非常好,但我认为您的原始代码最容易阅读。
Uooo

Answers:


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>
fero 2013年

101
我为提供的所有SQL注入机会感到兴奋。
asteri 2013年

12
@Jeff如果您不对where子句中的值进行硬编码,那么您也可以使用SqlParameters获得第二个列表。您只需要在条件列表的同时填充该列表,然后最后调用AddRange(parameters.ToArray())
Scott Chamberlain

5
@ScottChamberlain是的,您也可以在将输入字符串放入列表之前先对其进行转义。我主要只是警告不要使用滑稽幽默进行攻击。
asteri 2013年

4
@Jeff仅在条件包括用户输入的情况下才容易受到SQL注入的攻击(原始示例没有)
D Stanley

85

一种解决方案是不通过附加字符串手动编写查询。您可以使用ORM(例如Entity Framework),并且在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

抱歉,我正在搜索比较dapper与其他ORM的性能,而我是通过google到达这里的,所以我认为生成的PrintResults(query)查询随后将在dapper中用作查询!
vaheeds,2016年

@vaheeds可以,但是不理解答案并不能表示赞成。如果是您,那么这与您的评论同时发生。
CodeCaster

你的权利,那是一个误会。我受linq折磨,使实体在复杂查询上的性能下降。我通过您的其他回答(对上面的回答进行了补充)来
补偿不赞成的

这不是问题的答案
HGMamaci '17

17

在这种简单的情况下,有些过分的矫but过正,但是我过去使用的代码与此类似。

创建一个功能

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

给了您一票以启发我们微秒的重要性
user1451111 '18

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

那么切换到String query = "SELECT * FROM Table1";string jointer = " WHERE ";
布伦丹·朗

@BrendanLong然后WHEREANDs放在条件之间?
PenguinCoder

@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
我不确定(我自己不要使用C#),但是我会说这conditions != null始终是true,因为您使用C 初始化了它""(除非在C#中"" == null)。如果conditions不为空的话,应该检查一下;;-)
siegi 2013年

9

只需在后面追加两行。

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 AND”或“ 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 ";

然后仍然追加两行:

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

1
如果由于某种原因其中一个条件包含文本"1=1 AND "或,这可能会中断查询" WHERE 1=1 "。例如,如果条件包含一个子查询或试图检查某个列是否包含此文本,则可能是这种情况。也许这对您来说不是问题,但您应该牢记这一点……
siegi 2013年

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=1hack 更好或更糟。但这是一个深思熟虑的贡献。
catfood 2013年

3
有一个错误。更正它。如果不存在任何条件,我的查询就会被炸毁:-P我仍然必须说,对我而言,艾哈迈德(Ahmed)或CodeCaster就是最佳解决方案。我只为你们介绍了一个替代方案!
Anshuman 2013年

一般来说,这仍然是错误的。假设是... FROM SOMETABLE WHERE ; 则TrimEnd实际上会将其减少到... FROM SOMETABL。如果这实际上是一个StringBuilder(如果您有这么多的字符串操作或更多的话,应该是),您就可以Query.Length -= "WHERE ".Length;
Mark Hurd

马克,行得通。我已经在许多项目中尝试过了。试试看,您会发现它确实有用!
Anshuman 2013年

8
丑陋的地狱:)如果我计算正确的话,它最多可以创建7个字符串
Piotr Perak

5

为什么不使用现有的查询生成器?像Sql Kata之类的东西。

它支持复杂的条件,联接和子查询。

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

当然,我会向您推荐CodeCaster使用ORM的建议,这似乎并不优雅。但是,如果您考虑一下这是在做什么,那么您实际上就不必担心“浪费” 4个字符的存储空间,并且计算机将指针移动4个位置确实非常快。

如果您有时间学习如何使用ORM,那么它确实可以为您带来回报。但就此而言,如果您试图防止其他条件进入SQL数据库,则将为您完成此操作。


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

这是一个有趣的发现。感谢您提供的信息。将会更新
mckeejm'6

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的流畅接口,所以我做了一些ExtensionMethods。

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

2

Dapper SqlBuilder是一个很好的选择。它甚至在StackOverflow的生产中使用。

阅读Sam的博客文章

据我所知,它不是任何Nuget软件包的一部分,因此您需要将其代码复制粘贴到您的项目中,或者下载Dapper源代码并构建SqlBuilder项目。无论哪种方式,您都需要为DynamicParameters该类引用Dapper 。


1
我认为该程序包中不包含Dapper的SqlBuilder。
罗尼·欧弗比

1

我看到在Oracle在存储过程中构建动态SQL时一直使用它。在探索数据问题时,我也在查询中使用了它,只是为了使不同的数据过滤器之间的切换更快。

我发现它非常普遍并且很容易理解,对于审查您的代码的人来说。


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);
    }

要抗拒粮食!
罗尼·欧弗比

对不起?什么意思?
Maxim Zhukov

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条件(当condition1为false 时打印)。


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()方法即可,并且一切正常。<opinion>为什么C#开发人员这么不愿意在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 "

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.