Есть ли лучший способ динамически построить предложение SQL WHERE, чем использование 1 = 1 в его начале?

107

Я создаю 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" уже было добавлено или не было в запросе.

Есть ли более приятное решение?

  • 0
    if (условие1 || условие2 || условие1) {здесь разбираются ваши запросы}
  • 116
    Если честно - я бы тоже так сделал, но я бы использовал 42 = 42 ;-)
Показать ещё 15 комментариев
Теги:

21 ответ

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> .
  • 0
    Да, строка. Джоин это путь.
Показать ещё 7 комментариев
79

Одним из решений является просто не писать запросы вручную, добавляя строки. Вы можете использовать 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);
}
  • 0
    Разве Linq to Entities не уменьшит производительность dapper?
  • 0
    @vaheeds Я не понимаю этого вопроса. Оба разные ОРМ.
Показать ещё 5 комментариев
14

Небольшой перегиб в этом простом случае, но я использовал код, похожий на этот в прошлом.

Создать функцию

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-сервер на микросекунду обработки инструкции junk where, когда она анализирует оператор sql.

  • 0
    Я не вижу, как это делает его более элегантным. Конечно, не совсем понятно, что здесь происходит. Я вижу использование этой вспомогательной функции, но она не более элегантна.
  • 0
    дал вам один голос за то, что просветил нас о важности микросекунды
13

Существует еще одно решение, которое также может быть не изящным, но работает и решает проблему:

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 специально существует, чтобы избежать этого.
  • 0
    Так что переключитесь на String query = "SELECT * FROM Table1"; и string jointer = " WHERE "; ?
Показать ещё 3 комментария
7

Просто сделайте что-нибудь вроде этого:

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-инъекция безопасна и IMHO, это довольно чисто. Remove() просто удаляет последний AND;

Он работает и в том случае, если не было установлено никаких условий, если они были установлены или установлено несколько.

  • 1
    Я не уверен (сам не использую C #), но я бы сказал, что conditions != null всегда true , поскольку вы инициализируете его с помощью "" (если только в C # "" == null ). Вероятно, это должна быть проверка, если conditions не пусты… ;-)
  • 0
    @siegi: Спасибо. Исправлена :)
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());
  • 0
    Этот ответ сработает, и в этом нет ничего плохого, но я не думаю, что он более чистый и простой, чем первоначальный вопрос. QuerySub поиском QuerySub , на мой взгляд, не лучше и не хуже, чем использование взлома where 1=1 . Но это вдумчивый вклад.
  • 3
    Там была ошибка. Исправил это. Мой запрос взорвался бы, если бы не было выполнено ни одно из условий :-P Тем не менее я должен сказать, что Ахмед или CodeCaster для меня - лучшие решения. Я только представил альтернативу для вас, ребята!
Показать ещё 4 комментария
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 db, это сделает это за вас.

3

Просто добавьте две строки назад.

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 " . Это может быть в том случае, если условие содержит подзапрос или пытается проверить, например, содержит ли какой-либо столбец этот текст. Может быть, это не проблема в вашем случае, но вы должны помнить об этом ...
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

Если это SQL Server, вы можете сделать этот код намного чище.

Это также предполагает известное количество параметров, которые могут быть плохим предположением, когда я думаю о возможностях.

В С# вы должны использовать:

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
  • 0
    Скрытие ваших столбцов внутри выражения может помешать использованию индексов, и поэтому здесь не рекомендуется использовать эту технику.
  • 0
    это интересная находка. Спасибо за эту информацию. будет обновлять
2

Мне нравится свободный интерфейс стробоструйного устройства, поэтому я сделал некоторые 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;
}
1

Почему бы не использовать существующий Query Builder? Что-то вроде 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.

1

Dapper SqlBuilder - довольно хороший вариант. Он даже используется в производстве на StackOverflow.

Прочитайте запись в блоге Sam.

Насколько я знаю, он не является частью какого-либо пакета Nuget, поэтому вам нужно скопировать его код в свой проект или загрузить источник Dapper и построить проект SqlBuilder. В любом случае вам также потребуется ссылаться на Dapper для класса DynamicParameters.

  • 0
    Теперь на Nuget: nuget.org/packages/Dapper
  • 1
    Я не думаю, что SqlBuilder от Dapper включен в этот пакет.
Показать ещё 1 комментарий
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);
    }
  • 0
    ИДТИ ПРОТИВ ЗЕРНА!
  • 0
    Извините? Что ты имеешь в виду?
1

ИМХО, я думаю, что ваш подход неправильный:

Запросить базу данных путем конкатенации строки НИКОГДА не рекомендуется (риск 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 будет в любом случае оптимизировать его.

  • 0
    SELECT * FROM Table1 WHERE AND Col1=0 не кажется правильным, и в этом весь смысл WHERE 1=1 .
  • 0
    @Mormegil Мормегил опс !! исправленный
1

Я вижу, что это использовалось все время в Oracle при построении динамического SQL в хранимых процедурах. Я использую его в запросах при изучении проблем с данными, а просто для того, чтобы быстрее переключаться между различными фильтрами данных... Просто закомментируйте условие или добавьте его обратно легко.

Я нахожу это довольно распространенным и достаточно простым, чтобы понять кого-то, просматривающего ваш код.

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 "
0

Как было сказано, создание SQL путем конкатенации никогда не является хорошей идеей. Не только из-за SQL-инъекции. В основном потому, что это просто уродливый, трудно поддерживаемый и совершенно ненужный. Вы должны запустить свою программу с помощью трассировки или отладки, чтобы увидеть, какой SQL он генерирует. Если вы используете QueryFirst (отказ от ответственности: что я написал), неудовлетворительное соблазнение удаляется, и вы можете получить прямо в ta doin it в 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 дает вам С# null для db NULL, поэтому вы просто вызываете метод Execute() с нулями, когда это необходимо, и все это просто работает. <opinion> Почему разработчики С# так неохотно делают что-то в SQL, даже когда это проще. Mind boggles. </opinion>

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

Я подумал о решении, которое, может быть, несколько читаемо:

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 является ложным).

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  

Мне лично легко удалить условный элемент в конце, так как его положение легко предсказать.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню