Есть ли стандартный подход к динамической генерации sql?

Я хочу спросить, как другие программисты создают строки динамического SQL для выполнения в качестве CommandText объекта SQLCommand.

Я создаю параметризованные запросы, содержащие сгенерированные пользователем предложения WHERE и поля SELECT. Иногда запросы сложны, и мне нужно много контролировать, как строятся разные части.

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

Есть ли более чистый и стабильный способ сделать это?

Какие-либо предложения?

Обновлено: Чтобы добавить подробностей к моему предыдущему посту:

  1. Я не могу создать шаблон своего запроса из-за требований. Просто это слишком сильно меняется.
  2. Я должен разрешить агрегатные функции, такие как Count (). Это имеет последствия для предложения Group By / Have. Это также вызывает вложенные операторы SELECT. Это, в свою очередь, влияет на имя столбца, используемое
  3. Некоторые данные о контактах хранятся в столбце XML. Пользователи могут запрашивать эти данные ТАКЖЕ и другие реляционные столбцы вместе. Вследствие этого xmlcolumns не могут появляться в предложениях Group By [синтаксис sql].
  4. Я использую эффективную технику разбиения на страницы, в которой используется функция SQL Row_Number (). Следствием этого является то, что я должен использовать таблицу Temp, а затем получить @@ rowcount, прежде чем выбирать мое подмножество, чтобы избежать второго запроса.

Я покажу код (ужас!), Чтобы вы, ребята, поняли, с чем я имею дело.

sqlCmd.CommandText = "DECLARE @t Table(ContactId int, ROWRANK int" + declare
      + ")INSERT INTO @t(ContactId, ROWRANK" + insertFields + ")"//Insert as few cols a possible
      + "Select ContactID, ROW_NUMBER() OVER (ORDER BY " + sortExpression + " "
      + sortDirection + ") as ROWRANK" // generates a rowrank for each row
      + outerFields
      + " FROM ( SELECT c.id AS ContactID"
      + coreFields
      + from         // sometimes different tables are required 
      + where + ") T " // user input goes here.
      + groupBy+ " "
      + havingClause //can be empty
      + ";"
      + "select @@rowcount as rCount;" // return 2 recordsets, avoids second query
      + " SELECT " + fields + ",field1,field2" // join onto the other cols n the table
      +" FROM @t t INNER JOIN contacts c on t.ContactID = c.id" 
      +" WHERE ROWRANK BETWEEN " + ((pageIndex * pageSize) + 1) + " AND " 
      + ( (pageIndex + 1) * pageSize); // here I select the pages I want

В этом примере я запрашиваю данные XML. Для чисто реляционных данных запрос намного проще. Каждая из переменных раздела - это StringBuilders. Где предложения построены так:

// Add Parameter to SQL Command
AddParamToSQLCmd(sqlCmd, "@p" + z.ToString(), SqlDbType.VarChar, 50, ParameterDirection.Input, qc.FieldValue);
// Create SQL code Fragment
where.AppendFormat(" {0} {1} {2} @p{3}", qc.BooleanOperator, qc.FieldName, qc.ComparisonOperator, z);

Возможно, у вас действительно нет возможности здесь, но имейте в виду, что базы данных, такие как Oracle, будут жестко анализировать эти операторы sql каждый раз, когда они встречаются, что замедляет обработку. Что еще хуже, они выталкивают из стека кэшированные планы объяснений, таким же образом влияя на других пользователей.

ScottCher 25.10.2008 01:27
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
12
1
1 597
8
Перейти к ответу Данный вопрос помечен как решенный

Ответы 8

Из любопытства рассматривали ли вы возможность использования ORM для управления доступом к данным. Многие функции, которые вы пытаетесь реализовать, уже могут быть там. Это может быть что-то, на что стоит взглянуть, потому что лучше не изобретать колесо заново.

Вы можете попробовать подход, используемый инструментами генерации кода, такими как CodeSmith. Создайте шаблон SQL с заполнителями. Во время выполнения считайте шаблон в строку и замените заполнители фактическими значениями. Это полезно только в том случае, если весь код SQL следует шаблону.

Ответ принят как подходящий

Мне пришлось сделать это в одном из моих недавних проектов. Вот схема, которую я использую для генерации SQL:

  • Каждый компонент запроса представлен объектом (который в моем случае является объектом Linq-to-Sql, который сопоставляется с таблицей в БД). Итак, у меня есть следующие классы: Query, SelectColumn, Join, WhereCondition, Sort, GroupBy. Каждый из этих классов содержит все подробности, относящиеся к этому компоненту запроса.
  • Все пять последних классов связаны с объектом Query. Таким образом, сам объект Query имеет коллекции каждого класса.
  • У каждого класса есть метод, который может генерировать SQL для той части запроса, которую он представляет. Таким образом, создание общего запроса заканчивается вызовом Query.GenerateQuery (), который, в свою очередь, выполняет перечисление всех вложенных коллекций и вызывает их соответствующие методы GenerateQuery ().

Это все еще немного сложно, но в конце концов вы знаете, откуда происходит генерация SQL для каждой отдельной части запроса (и я не думаю, что есть какие-либо большие операторы переключения). И не забудьте использовать StringBuilder.

Гульзар и Райан Лансиукс хорошо упомянули CodeSmith и ORM. Любой из них может уменьшить или устранить вашу текущую нагрузку, когда дело доходит до создания динамического SQL. Ваш текущий подход к использованию параметризованного SQL является разумным просто потому, что он хорошо защищает от атак SQL-инъекций.

Без фактического образца кода, который можно было бы прокомментировать, трудно предоставить информированную альтернативу циклам и операторам переключения, которые вы используете в настоящее время. Но поскольку вы упомянули, что устанавливаете свойство CommandText, я бы рекомендовал использовать string.Format в вашей реализации (если вы еще не используете его). Я думаю, что это может упростить реструктуризацию вашего кода и, следовательно, улучшить читаемость и понимание.

Обычно это примерно так:

string query= "SELECT {0} FROM .... WHERE {1}"
StringBuilder selectclause = new StringBuilder();
StringBuilder wherecaluse = new StringBuilder();

// .... the logic here will vary greatly depending on what your system looks like

MySqlcommand.CommandText = String.Format(query, selectclause.ToString(), whereclause.ToString());

Я также только начинаю работать с ORM. Возможно, вы захотите взглянуть на один из них. ActiveRecord / Hibernate - хорошие ключевые слова для Google.

ORMs уже решили проблему динамической генерации SQL (я предпочитаю NHibernate / ActiveRecord). Используя эти инструменты, вы можете создать запрос с неизвестным количеством условий, перебирая вводимые пользователем данные и генерируя массив объектов Expression. Затем выполните встроенные методы запроса с этим набором настраиваемых выражений.

List<Expression> expressions = new List<Expression>(userConditions.Count);
foreach(Condition c in userConditions)
{
    expressions.Add(Expression.Eq(c.Field, c.Value));
}
SomeTable[] records = SomeTable.Find(expressions);

Есть больше вариантов «Выражения»: неравенство, больше / меньше, ноль / не-нуль и т. д. Тип «Условие», который я только что придумал, вы, вероятно, можете поместить в полезный класс, введенный пользователем.

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

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

Создайте хранимую процедуру, которая принимает все возможные параметры, а затем используйте что-то вроде этого в предложении where:

where...
and (@MyParam5 is null or @MyParam5 = Col5)

тогда из кода гораздо проще установить значение параметра в DBNull.Value, когда оно неприменимо, чем изменять генерируемую строку SQL.

Ваши администраторы баз данных будут намного счастливее с вами, потому что у них будет одно место для настройки запросов, SQL будет легко читать, и им не придется рыться в трассировках профилировщика, чтобы найти множество разных запросов, генерируемых вашим код.

Мы создали наш собственный объект FilterCriteria, который является своего рода конструктором динамических запросов черный ящик. Он имеет свойства коллекции для SelectClause, WhereClause, GroupByClause и OrderByClause. Он также содержит свойства для CommandText, CommandType и MaximumRecords.

Затем мы передаем наш объект FilterCriteria нашей логике данных, и он выполняет его на сервере базы данных и передает значения параметров в хранимую процедуру, которая выполняет динамический код.

У нас хорошо работает ... и сохраняет генерацию SQL в объекте.

Другие вопросы по теме