有没有比在开头使用 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<string>
的重载。
我对它提供的所有 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 那么WHERE
是AND
s要放在条件之间吗?
@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() 方法,这一切都可以正常工作。
【讨论】:
【参考方案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 密码更好的方法?