Поделиться через


Создание кода SQL из деревьев команд. Рекомендации

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

Группирование узлов DbExpression в инструкции SELECT языка SQL

Типичная инструкция SQL имеет вложенную структуру следующего вида:

SELECT …
FROM …
WHERE …
GROUP BY …
ORDER BY …

Одно или несколько предложений могут быть пустыми. Вложенная инструкция SELECT может находиться в любой строке.

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

В качестве примера рассмотрим следующее дерево команд запроса:

Project (
a.x,
   a = Filter(
      b.y = 5, 
      b = Scan("TableA")
   )
)

Неэффективное преобразование даст нам:

SELECT a.x
FROM (   SELECT *
         FROM TableA as b
         WHERE b.y = 5) as a

Заметьте, что каждый узел реляционного выражения превращается в отдельную инструкцию SELECT языка SQL.

Следовательно, важно агрегировать как можно больше узлов выражений в одну инструкцию SELECT языка SQL, не нарушая правильности инструкции.

Результат такого агрегирования для приведенного выше примера будет иметь следующий вид:

SELECT b.x 
FROM TableA as b
WHERE b.y = 5

Уплощение соединений в инструкции SELECT языка SQL

Частным случаем агрегирования нескольких узлов в одну инструкцию SELECT языка SQL является агрегирование нескольких выражений соединения. Класс DbJoinExpression представляет одно соединение между двумя исходными таблицами. Но в одной инструкции SELECT языка SQL можно задать несколько соединений. В этом случае соединения выполняются в порядке, в котором они указаны.

Левосторонние соединения (которые выглядят как левые дочерние другого соединения) проще сделать плоскими и превратить в одну инструкцию SELECT языка SQL. В качестве примера рассмотрим следующее дерево команд запроса:

InnerJoin(
   a = LeftOuterJoin(
   b = Extent("TableA")
   c = Extent("TableB")
   ON b.y = c.x ),
   d = Extent("TableC") 
   ON a.b.y = d.z
)

Оно верно переводится в следующую инструкцию:

SELECT *
FROM TableA as b
LEFT OUTER JOIN TableB as c ON b.y = c.x
INNER JOIN TableC as d ON b.y = d.z

Однако соединения, не являющиеся левосторонними соединениями, невозможно легко превратить в плоскую структуру, и не следует пытаться это сделать. В качестве примера рассмотрим соединения в следующем дереве команд запроса:

InnerJoin(
   a = Extent("TableA") 
   b = LeftOuterJoin(
   c = Extent("TableB")
   d = Extent("TableC")
   ON c.y = d.x),
   ON a.z = b.c.y
)

Это дерево будет преобразовано в инструкцию SELECT языка SQL с подзапросом.

SELECT *
FROM TableA as a
INNER JOIN (SELECT * 
   FROM TableB as c 
   LEFT OUTER JOIN TableC as d
   ON c.y = d.x) as b
ON b.y = d.z

Перенаправление входных псевдонимов

Чтобы понять, что представляет собой перенаправление входных псевдонимов, рассмотрим структуру реляционных выражений, таких как DbFilterExpression, DbProjectExpression, DbCrossJoinExpression, DbJoinExpression, DbSortExpression, DbGroupByExpression, DbApplyExpression и DbSkipExpression.

Каждый из этих типов имеет одно или несколько свойств Input, описывающих входную коллекцию; для представления каждого элемента этого входа во время обхода коллекции используется переменная привязки, соответствующая данному элементу. Переменная привязки используется для ссылки на входной элемент, например в свойстве Predicate выражения DbFilterExpression или свойстве Projection выражения DbProjectExpression.

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

Рассмотрим первый пример данного раздела. При примитивном преобразовании Projection a.x (DbPropertyExpression(a, x)) правильно будет преобразовать в a.x, поскольку мы создали псевдоним входного значения «a» для соответствия переменной привязки. Однако при агрегировании обоих узлов в единую инструкцию SELECT языка SQL то же выражение DbPropertyExpression следует преобразовать в b.x, так как для входного значения был задан псевдоним «b».

Преобразование псевдонимов соединений в плоские

В отличие от любых других реляционных выражений в дереве выходных команд, результирующим типом для выражения DbJoinExpression является строка, состоящая из двух столбцов, каждый из которых соответствует одному из входов. При построении DbPropertyExpresssion для доступа к скалярному свойству, образованному соединением, это выражение будет содержать еще одно выражение DbPropertyExpresssion.

Например, можно указать «a.b.y» в примере 2 и «b.c.y» в примере 3. Однако в соответствующих инструкциях SQL они именуются «b.y». Такое присвоение новых псевдонимов называется уплощением псевдонимов соединений.

Переименование столбцов и псевдонимов экстентов

Если запрос SELECT языка SQL, содержащий соединение, должен быть выполнен в проекции, то при перечислении всех участвующих в запросе столбцов из входных таблиц может произойти коллизия имен, так как имена столбцов во входных таблицах могут совпадать. Чтобы избежать коллизии, следует использовать для столбца другое имя.

Кроме того, при уплощении соединений участвующие в запросе таблицы (или вложенные запросы) могут содержать конфликтующие псевдонимы. В таком случае эти псевдонимы следует переименовать.

Предотвращение применения инструкции SELECT *

Не следует использовать оператор SELECT * для выборки из базовых таблиц. Модель хранения в приложении Entity Framework может включать лишь подмножество столбцов из таблицы базы данных. В этом случае оператор SELECT * может выдать неверный результат. Вместо этого следует указать все столбцы, участвующие в запросе, по именам столбцов из результирующего типа выражений, участвующих в запросе.

Повторное использование выражений

Выражения в дереве команд запроса, передаваемом Entity Framework , могут быть использованы повторно. Не следует предполагать, что каждое выражение появляется в дереве команд запроса только один раз.

Сопоставление примитивных типов

При сопоставлении концептуальных типов (типов модели EDM) с типами поставщика следует проводить сопоставление с самым большим типом (Int32), чтобы поместились все возможные значения. Кроме того, следует избегать сопоставления с типами, которые нельзя использовать во многих операциях, такими как типы BLOB (например, ntext в SQL Server).

См. также

Основные понятия

Создание SQL