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

Posted

技术标签:

【中文标题】有没有比在开头使用 1=1 更好的方法来动态构建 SQL WHERE 子句?【英文标题】:Is there a better way to dynamically build an SQL WHERE clause than by using 1=1 at its beginning? 【发布时间】:2013-06-23 15:55:41 【问题描述】:

我正在 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”关键字添加到查询中。

有更好的解决方案吗?

【问题讨论】:

说实话 - 我也会这样做,但我会使用 42 = 42 ;-) 我实际上总是这样写我的查询。使注释条件变得更容易 @catfood 我作为实习生的第一个项目是编写工具来帮助分析针对我们 Sybase 服务器的性能查询。一个有趣的发现是我们收到了数十万个Select 42 查询。 (不好玩是试图追查源头) 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的原因。无论如何,数据库引擎都会对其进行优化,因此虽然它可能看起来很难看,但它是迄今为止解决问题的最简单方法。 虽然给出的答案非常好,但我认为您的原始代码最容易阅读。 【参考方案1】:

将条件保存在列表中:

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

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

【讨论】:

很好的解决方案,但 ToArray() 对于 .NET 4 来说不是必需的,因为存在接受任何 IEnumerable&lt;string&gt; 的重载。 我对它提供的所有 SQL 注入机会感到兴奋。 @Jeff 如果您没有对 where 子句中的值进行硬编码,您也可以使用 SqlParameters 获得第二个列表。您只需要在填充条件列表的同时填充该列表并在最后调用AddRange(parameters.ToArray())。 @ScottChamberlain 是的,您也可以在将输入字符串放入列表之前对其进行转义。我主要是在警告使用滑稽幽默的可能攻击。 @Jeff 只有在条件包括用户输入的情况下才容易受到 SQL 注入攻击(原始示例没有)【参考方案2】:

一种解决方案是简单地不通过附加字符串来手动编写查询。您可以使用 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);

【讨论】:

【参考方案3】:

在这个简单的例子中有点矫枉过正,但我​​过去使用过类似的代码。

创建一个函数

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 子句的微秒。

【讨论】:

我不明白这如何使它更优雅。当然,这里发生的事情并不清楚。我可以看到该实用程序函数的使用,但它并不优雅。 投给你一票,让我们了解微秒的重要性【参考方案4】:

还有另一种解决方案,它可能也不优雅,但可以解决问题:

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

【讨论】:

如果没有谓词,则留下一个悬空的WHERE; 1=1 的存在就是为了避免这种情况。 所以切换到String query = "SELECT * FROM Table1";string jointer = " WHERE "; ? @BrendanLong 那么WHEREANDs要放在条件之间吗? @PenguinCoder 很难在评论中显示完整的代码。我的意思是用string joiner = " WHERE "; 替换string joiner 行,并保留joiner = " AND "; 行。 @Gaius 我认为代码是非空的,但是将 WHERE 放在 joiner 中应该可以解决问题。谢谢你的评论!【参考方案5】:

只需这样做:

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 injectionsafe 和 IMHO,很干净。 Remove() 只是删除了最后一个 AND;

如果没有设置条件、设置了一个条件或设置了多个条件,它都可以工作。

【讨论】:

我不确定(我自己不要使用 C#)但我会说 conditions != null 始终是 true,因为你用 "" 初始化它(除非在 C# 中 @987654329 @)。如果conditions 不为空,它可能应该是一个检查...... ;-)【参考方案6】:

只需在后面添加两行即可。

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 AND "" WHERE 1=1 ",这可能会中断查询。例如,如果条件包含子查询或尝试检查某个列是否包含此文本,则可能是这种情况。也许这对你来说不是问题,但你应该记住......【参考方案7】:

使用这个:

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 hack 好或坏。但这是一个深思熟虑的贡献。 出现错误。更正了它。如果不存在任何条件,我的查询就会被炸毁:-P 我仍然必须说 Ahmed 或 CodeCaster 对我来说是最好的解决方案。我只为你们提供了一个替代方案! 这仍然是错误的,一般来说。假设它是... FROM SOMETABLE WHERE ;那么TrimEnd 实际上会将其减少到... FROM SOMETABL。如果这实际上是一个StringBuilder(如果你有这么多或更多的字符串操作,它应该是)你可以只是Query.Length -= "WHERE ".Length; 马克,它有效。我在很多项目中都试过这个。试试看,你会发现它确实有效! 丑得要命 :) 如果我计数正确,它最多可以创建 7 个字符串【参考方案8】:

为什么不使用现有的查询生成器? 类似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。

【讨论】:

【参考方案9】:

如果这是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

【讨论】:

将列隐藏在表达式中可以防止使用索引,因此不鼓励使用这种技术here。 这是一个有趣的发现。谢谢你的信息。会更新【参考方案10】:

我能想到的最快的文字解决方案是:

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 数据库,这将为您完成。

【讨论】:

【参考方案11】:

根据条件,可以在查询中使用布尔逻辑。像这样:

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

【讨论】:

【参考方案12】:

我喜欢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://***.com/a/4217362/98491
public static bool Contains(this string source, string toCheck, StringComparison comp)

    return source.IndexOf(toCheck, comp) >= 0;

【讨论】:

【参考方案13】:

恕我直言,我认为您的方法是错误的:

通过连接字符串来查询数据库绝不是一个好主意(SQL injection 的风险,如果您在其他地方进行一些更改,代码很容易被破坏)。

你可以使用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 的重点。【参考方案14】:

Dapper SqlBuilder 是一个不错的选择。它甚至被用于 *** 的生产环境。

阅读Sam's blog entry about it。

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

【讨论】:

我不认为 Dapper 的 SqlBuilder 包含在那个包中。【参考方案15】:

在stored procedures 中构建动态 SQL 时,我看到它一直在 Oracle 中使用。我在查询数据问题时也使用它,只是为了更快地在不同的数据过滤器之间切换......只需注释掉一个条件或轻松地将其添加回来。

我发现审查您的代码的人很容易理解它。

【讨论】:

【参考方案16】:

使用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  

我个人觉得删除最后的条件元素很容易,因为它的位置很容易预测。

【讨论】:

【参考方案17】:

我想到了一个解决方案,嗯,也许更具可读性:

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 为假时打印)。

【讨论】:

【参考方案18】:
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);
    

【讨论】:

反对谷物! 执行我?什么意思?【参考方案19】:

这是一种更优雅的方式:

    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;
    

【讨论】:

【参考方案20】:

如前所述,通过连接创建 SQL 绝不是一个好主意。不仅仅是因为 SQL 注入。主要是因为它只是丑陋、难以维护且完全没有必要。您必须使用跟踪或调试来运行您的程序,以查看它生成的 SQL。如果您使用QueryFirst(免责声明:我写的),那么不愉快的诱惑就会被消除,您可以直接在 SQL 中进行操作。

This page 全面涵盖了用于动态添加搜索谓词的 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 中做事,即使它更简单。令人难以置信。

【讨论】:

【参考方案21】:

正如许多人所说,对于更长的过滤步骤,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 "

【讨论】:

【参考方案22】:

简洁、优雅、甜美,如下图所示。

【讨论】:

Sql-injection ahoi ... :D 比 string.Format 更好地使用参数 ;)

以上是关于有没有比在开头使用 1=1 更好的方法来动态构建 SQL WHERE 子句?的主要内容,如果未能解决你的问题,请参考以下文章

有啥比在 Java Spring Boot 中使用自定义查询更好的方法?

调试OpenGL比在每个命令后调用glGetError有更好的方法吗?

寻找比在 Scriptable Objects 中使用字符串在别处调用特定方法更好的解决方案

比在配置文件中以纯文本形式存储 mysql 密码更好的方法?

有没有比在 python 中使用 loc 更快的方法来根据现有数据框填充数据框中的新列?

为啥 Mongodb 在 Linux 上的性能比在 Windows 上更好?