Здравствуйте, дорогие друзья! Сегодня мы рассмотрим различные подходы, которые разработчики используют для работы с данными в БД. В современном мире разработки, где информация становитесь все больше и больше, и скорость получения данных имеет большое значение, умение эффективно извлекать и обрабатывать данные становится неотъемлемой частью работы многих SQL специалистов (особенно тех, кто работает с нагруженными системами и DWH). Мы поговорим о таких методах, как Common Table Expressions (CTE), подзапросы, представления и материализованные представления.


CTE (Общее Табличное Выражение) и их использование

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

Характеристики CTE

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

План выполнения запроса с CTE (Materialize  (cost=0.00..527.63 rows=51719 width=1) говорит нам о материализации результатов )
План выполнения запроса с CTE (Materialize (cost=0.00..527.63 rows=51719 width=1) говорит нам о материализации результатов )

Синтаксис:

WITH CTE_Name AS (
    SELECT column1, column2
    FROM table_name
    WHERE condition
)
SELECT *
FROM CTE_Name;

Преимущества CTE:

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

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

  3. Рекурсия: CTE поддерживают рекурсивные запросы, что позволяет выполнять сложные иерархические запросы (например, при работе с родительскими и дочерними записями).

  4. Модульность: CTE помогают инкапсулировать логику, что делает её повторно используемой в рамках одного запроса, а также упрощает поддержку и изменение SQL кода.

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

  • При работе с сложными соединениями или множеством агрегаций, где повторное выполнение одной и той же подзадачи может значительно снизить производительность.

  • В ситуациях, когда необходимо создать рекурсивный запрос (например, для извлечения данных из иерархической структуры).

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


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

Определение: Подзапрос — это вложенный запрос, который помещается внутри другого SQL-запроса. Он может возвращать данные, которые затем могут использоваться в основном запросе.

Синтаксис:

SELECT column1, column2
FROM table_name
WHERE column1 IN (
    SELECT column1
    FROM another_table
    WHERE condition
);
SELECT 
column1,
(
    SELECT column1
    FROM another_table as at
    WHERE at.id = t.another_table_id
) as column1_from_another_table
FROM table_name as t

Преимущества подзапросов:

  • Инлайн-запросы: Подзапросы позволяют использовать результат напрямую без создания временной структуры.

  • Гибкость: Их можно использовать в операторах SELECT, INSERT, UPDATE и DELETE.

Когда использовать подзапросы:

  • Когда вычисление простое и не требует повторного использования.

  • Когда необходимо фильтровать данные на основе результатов другого запроса.


Представление и их использование

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

Синтаксис:

CREATE VIEW View_Name AS
SELECT column1, column2
FROM table_name
WHERE condition;

Преимущества представлений:

  • Безопасность данных: Ограничивают доступ к конкретным строкам или столбцам.

  • Повторное использование: Легко повторно использовать в нескольких запросах.

Когда использовать представления:

  • Когда необходимо инкапсулировать сложные запросы.

  • Для повышения безопасности путём ограничения доступа к данным.

  • Для избегания дублирования кода


Материализованное представление и их использование

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

Синтаксис:

CREATE MATERIALIZED VIEW Materialized_View_Name AS
SELECT column1, column2
FROM table_name
WHERE condition;

Преимущества материализованных представлений:

  • Увеличение производительности: Существенно ускоряет запросы, заранее вычисляя сложные соединения и агрегации.

  • Хранящие результаты: Результаты хранятся, обеспечивая более быстрый доступ, чем если бы их приходилось вычислять каждый раз.

Когда использовать материализованные представления:

  • Когда первоначальные данные изменяются редко.

  • Когда возникают проблемы с производительностью из-за сложных запросов или агрегаций.


временные таблицы и их использование

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

Синтаксис:

CREATE TEMPORARY TABLE Temp_Table_Name (
    column1 datatype,
    column2 datatype
);

Преимущества временных таблиц:

  • Область сеанса: Данные хранятся временно, не оказывая воздействия на основную схему базы данных.

  • Гибкость структуры: Их можно динамически структурировать по мере необходимости.

  • Удаление и изоляция: Автоматически удаляются в конце сеанса

Когда использовать временные таблицы:

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

  • Когда необходимо временно хранить большое количество строк.


Когда использовать каждую из конструкций?

SQL Конструкция

Случай использования

CTE

Сложные запросы, рекурсивные запросы, улучшение читаемости, когда нужно повторно использовать результат

Подзапрос

Инлайн-вычисления, фильтрация на основе результатов других запросов.

Представление

Инкапсуляция сложных запросов, повышение безопасности.

Материализованное представление

Улучшение производительности для не часто изменяющихся данных.

Временная таблица

Хранение промежуточных результатов для преобразований.

Заключение

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

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

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

Более подробно с примерами и планами выполнения мы рассмотрим в следующих статьях.

Комментарии (10)


  1. Akina
    02.11.2024 18:34

    CTE могут материализоваться в памяти в ходе выполнения запроса

    Существует две принципиально разные стратегии выполнения CTE - инлайн и материализация.

    Какую стратегию реализует PostgreSQL? или, если реализованы обе, как выполняется установка стратегии для текущего запроса либо соединения? Ваши слова неоднозначны, "могут материализоваться" - не означает "безусловно материализуются", материализация точно реализована, но реализован ли инлайн? доступен ли он вообще, и на каком уровне?

    Аналогичный вопрос - по нематериализованным представлениям.

    Синтаксис, показанный вами для CTE, весьма неаккуратен, и скорее должен быть отнесён к bad practice. Имена выходных полей крайне желательно указывать в заголовке CTE, а не полагаться на трансляцию имён из подзапроса.

    Синтаксис подзапросов неполон, пропущен наиболее частый вариант - подзапрос в секции FROM.

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


    1. KainoRhine Автор
      02.11.2024 18:34

      В зависимости от плана выполнения. Например в запросе

      explain  
      with cte as (
      	select "Name"  from Example tc 
      )
      select * from cte

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

      with cte as (
      	select "Name"  from Example tc 
      )
      select * from cte

      И видим что шаг Materialize отсутствует

      Теперь давайте напишем запрос который будет повторно использовать результаты cte

      with cte as (
      	select "Name"  from Example tc 
      )
      select * from cte
      left join cte as c on  c."Name" = cte."Name"

      здесь мы добавили простой join с результатами cte

      Теперь давайте посмотрим на план выполнения добавив перед запросом explain

      explain  
      with cte as (
      	select "Name"  from Example tc 
      )
      select * from cte
      left join cte as c on  c."Name" = cte."Name"
      
      

      И в плане выполнения видим:

      Появился шаг Materialize.

      Таким образом, когда мы повторно используем CTE, результаты будут материализованы

      Я ответил на ваш вопрос?

      C подзапросами в секции FROM расскажу отдельно и про временные таблицы тоже (тут очень много подводных камней с которыми я сталкивался когда то )


      1. Akina
        02.11.2024 18:34

        Нет. Вы его, похоже, даже не поняли.

        WITH
        cte1 (value1) AS (SELECT random()),
        cte2 (value2) AS (SELECT value1 FROM cte1)
        SELECT value1, value2
        FROM cte1 CROSS JOIN cte2;

        Materialized strategy вернёт равные значения полей, inline strategy - разные.

        На практике иногда нужна одна стратегия, иногда другая. Реализованы ли обе стратегии, и если да, то как указать нужную?


        1. shurutov
          02.11.2024 18:34

          Ключевое свойство запросов WITH состоит в том, что они обычно вычисляются один раз для всего основного запроса, даже если в основном запросе содержатся несколько ссылок на них. В частности, гарантируется, что операторы, изменяющие данные, будут выполняться ровно один раз, вне зависимости от того, будет ли их результат прочитан основным запросом и в каком объёме.

          Однако от этой гарантии можно отказаться, добавив для запроса WITH пометку NOT MATERIALIZED. В этом случае запрос WITH может быть свёрнут в основной запрос, как если бы это был простой SELECT внутри предложения FROM основного запроса.

          Документация по ПГ: https://postgrespro.ru/docs/postgresql/12/sql-select#SQL-WITH


          1. Akina
            02.11.2024 18:34

            Вот опять...

            .. запрос WITH может быть свёрнут ..

            "Может" - это не "будет". Нередки случаи, когда инлайн подзапроса производит гораздо более вменяемый план выполнения и разницу по времени выполнения на порядок. А тут - ты укажи NOT MATERIALIZED, и молись...

            Документация по ПГ

            Постгресс и ПостгрессПро - это совсем не одно и то же.


            1. shurutov
              02.11.2024 18:34

              Я привёл ссылку на перевод официальной документации. У каждого ПгПро (стандарт/энтерпрайз) своя документация.


        1. KainoRhine Автор
          02.11.2024 18:34

          в Greenplum( у меня PostgreSQL 9.4.26 ) я нашел решение либо через временные таблицы либо через материализованные представления, так как инструкции MATERIALIZED и NOT MATERIALIZED для CTE появились в 12+ версии.

          Так же они есть в arenadata https://docs.arenadata.io/en/ADPG/current/how-to/queries/common-table-expressions.html


        1. KainoRhine Автор
          02.11.2024 18:34

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


  1. erogov
    02.11.2024 18:34

    А планы, внезапно, от Greenplum-а.


    1. 0xMihalich
      02.11.2024 18:34

      ну в целом GreenPlum и есть Postgres, только очень очень старый, но зато иногда еще и умеющий быть колоночной бд