Упрощение SQL с помощью CTE

Позвольте мне начать с того, что я широко использую CTE, потому что это намного проще и проще.

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

Общее табличное выражение — это именованный временный набор результатов, извлекаемый из оператора SELECT, который можно использовать в другом операторе SELECT, INSERT, UPDATE или DELETE. Это означает, что CTE можно использовать для хранения подзапроса и многократного обращения к нему в более крупном запросе, что делает общую структуру запроса более читабельной и удобной в сопровождении.

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

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

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

Без CTE запрос может выглядеть следующим образом:

SELECT
  customer_id,
  COUNT(*) AS total_orders,
  SUM(total_value) AS total_value
FROM
  (
    SELECT
      customer_id,
      order_id,
      SUM(price * quantity) AS total_value
    FROM
      order_details
    GROUP BY
      customer_id,
      order_id
  ) subquery
GROUP BY
  customer_id

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

Используя CTE, мы можем упростить запрос следующим образом:

WITH order_totals AS (
  SELECT
    customer_id,
    order_id,
    SUM(price * quantity) AS total_value
  FROM
    order_details
  GROUP BY
    customer_id,
    order_id
)
SELECT
  customer_id,
  COUNT(*) AS total_orders,
  SUM(total_value) AS total_value
FROM
  order_totals
GROUP BY
  customer_id

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

Мы должны убедиться, что синтаксис правильный, чтобы CTE работало без ошибок. CTE не должен заканчиваться ; для завершения строки кода. Если у нас есть несколько CTE, мы должны разделить их запятой. Ниже я представил запрос, который был бы катастрофой, если бы не CTE.

WITH cte_sales AS (
  SELECT salesperson_id, SUM(sales_amount) AS total_sales
  FROM sales
  GROUP BY salesperson_id
), 
cte_targets AS (
  SELECT salesperson_id, target_sales
  FROM sales_targets
)
SELECT
s.salesperson_id,
s.total_sales,
t.target_sales,
(s.total_sales - t.target_sales) AS difference
FROM cte_sales s
JOIN cte_targets t
  ON s.salesperson_id = t.salesperson_id;

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

Теперь давайте перейдем к другомутипу CTE.

Рекурсивные CTE

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

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

Вот пример рекурсивного CTE, который находит факториал числа:

WITH RECURSIVE factorial (n, result) AS (
  SELECT 1, 1 -- base case
  UNION ALL
  SELECT n + 1, (n + 1) * result
  FROM factorial
  WHERE n < 10 -- stop condition
)
SELECT result
FROM factorial
WHERE n = 10;

Этот SQL-запрос вычисляет факториал 10 с помощью рекурсивного общего табличного выражения (CTE).

Вот как это работает:

  1. CTE определяется с помощью ключевого слова WITH RECURSIVE, за которым следует имя CTE «факториал» и два столбца, n и result.
  2. Оператор SELECT в определении CTE состоит из двух частей: базового случая и рекурсивного случая. Базовый вариант определяется как n = 1 и result = 1. Это начальная точка рекурсии.
  3. Рекурсивный случай вычисляет следующую итерацию факториала, добавляя 1 к текущему значению n и умножая его на текущее значение result. Затем запрос ссылается на имя CTE в своем собственном определении, чтобы получить следующую итерацию.
  4. Условие остановки, определенное как WHERE n < 10, используется для завершения рекурсии. Рекурсия будет продолжаться до тех пор, пока значение n не станет больше или равно 10.
  5. Последний оператор SELECT извлекает значение result из CTE, где n равно 10. Окончательный ответ — 3628800, представляющий факториал 10.

Используя рекурсивное CTE, этот запрос вычисляет факториал кратким и удобочитаемым способом, что упрощает его понимание и поддержку.

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

Теперь поговорим об ограничениях использования CTE.

Ограничения

  1. Длительность и область действия. CTE доступны только в контексте одного запроса и не хранятся в базе данных, как временные таблицы. Это означает, что CTE не подходят для хранения промежуточных результатов, которые необходимо использовать в нескольких запросах. Их нельзя использовать в других частях вашего приложения, таких как хранимые процедуры или функции.
  2. Производительность. Хотя CTE могут улучшить читабельность и удобство сопровождения ваших запросов, в некоторых случаях они могут повлиять на производительность. В частности, рекурсивные CTE могут привести к снижению производительности и вызвать зависание запроса, если рекурсия не контролируется должным образом.
  3. Совместимость: CTE поддерживаются не всеми системами управления базами данных. Некоторые системы не поддерживают CTE, а другие, например Oracle, имеют ограниченную поддержку. Прежде чем использовать CTE в своих запросах, убедитесь, что они поддерживаются вашей системой управления базами данных, и ознакомьтесь со всеми существующими ограничениями.
  4. Вложенность. Некоторые системы управления базами данных ограничивают количество вложенных CTE, которые можно использовать в одном запросе. Это означает, что вы не сможете использовать CTE в сложных сценариях, где требуется несколько уровней вложенности.
  5. Ограничения по размеру. В зависимости от вашей системы управления базами данных могут быть ограничения по размеру результатов, возвращаемых CTE. Это может ограничить объем данных, которые вы можете обработать в одном запросе, и может потребовать разбиения запроса на более мелкие части.

В целом, CTE — полезный инструмент для написания сложных SQL-запросов, но их следует использовать с осторожностью, учитывая их ограничения и любое потенциальное влияние на производительность.

Сравнение CTE с другими конструкциями запросов

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

  1. Подзапросы: Подзапросы — это тип вложенного запроса, который можно использовать для возврата одного значения или набора значений, которые можно использовать в основном запросе. CTE можно рассматривать как альтернативу подзапросам, поскольку они обеспечивают более читаемый и удобный способ организации сложных запросов. В отличие от подзапросов, CTE могут использоваться для выполнения рекурсивных операций и являются более гибкими с точки зрения данных, которые они могут возвращать.
  2. Временные таблицы. Временные таблицы — это способ хранения промежуточных результатов в базе данных. В отличие от CTE, временные таблицы хранятся в базе данных, и к ним можно получить доступ с помощью нескольких запросов. Это делает их хорошим выбором для хранения промежуточных результатов, которые необходимо использовать в течение более длительного периода времени. Однако временные таблицы могут работать медленнее, чем CTE, и для их создания и обслуживания требуется дополнительное дисковое пространство и операции с базой данных.
  3. Производные таблицы. Производные таблицы — это тип подзапроса, определенный в операторе SELECT основного запроса. Как и CTE, производные таблицы могут упростить сложные запросы и упростить их чтение и обслуживание. Однако производные таблицы можно использовать только в контексте одного запроса, и они недоступны за пределами оператора SELECT, в котором они определены. Это делает их менее гибкими, чем CTE, которые можно использовать в нескольких частях запроса.

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

Когда использовать CTE

Лично я ранжирую их CTE, затем подзапрос, а таблица #temp является последним средством. Золотого правила не существует, и я считаю, что CTE более удобочитаемы, и использую их, если только они не вызывают проблем с производительностью. В некоторых случаях дорогостоящее CTE в операторе join будет выполняться несколько раз и будет лучше, если его заменит временная таблица. Это означает, что было бы лучше использовать CTE, если запрос является простой оценкой и выполняется несколько раз, потому что временный запрос был бы ненужным накладным расходом.

CTE можно использовать в качестве альтернативы представлению, когда обычное использование представления не требуется (т. е. хранить определение в метаданных). Мы должны помнить, что CTE нельзя использовать повторно. Область CTE находится только в пределах первого запроса, и если мы хотим использовать CTE дальше по строке, нам следует рассмотреть возможность использования временной таблицы.

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

Удачного кодирования!