Архитектура обработки данных: использование CTE для выборки, обновления и вставки

    Рассмотрим задачу обработки данных из двух исходных таблиц с последующей записью результатов и синхронизацией статусов.

    Исходные условия

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

    • Выборка необработанных строк из обеих таблиц.
    • Обработка данных (соединения, преобразования, расчёты).
    • Запись результатов обработки в целевую таблицу.
    • Обновление признака IsProcessed на true в исходных таблицах для обработанных строк.
    • Обеспечение атомарности операции: все действия должны выполняться в рамках одной транзакции с возможностью отката при ошибках.
    • Возврат рассчитанных данных клиенту.

    Предлагаемое решение с CTE

    Один из подходов - использование обобщённых табличных выражений (CTE, Common Table Expressions) для объединения выборки, обновления и вставки в единый запрос. Согласно документации СУБД, такая практика допустима.

    WITH t1 AS (
        -- Выборка необработанных данных из первой таблицы
        SELECT * FROM table1
        WHERE "IsProcessed" = false
    ),
    t2 AS (
        -- Выборка необработанных данных из второй таблицы
        SELECT * FROM table2
        WHERE "IsProcessed" = false
    ),
    -- Блок обработки данных (джойны, агрегации, преобразования)
    result AS (
        SELECT ... -- Результат расчётов на основе t1 и t2
    ),
    -- Блок обновления статусов в исходных таблицах
    upd1 AS (
        UPDATE table1
        SET "IsProcessed" = true
        FROM t1
        WHERE t1.id = table1.id
    ),
    upd2 AS (
        UPDATE table2
        SET "IsProcessed" = true
        FROM t2
        WHERE t2.id = table2.id
    ),
    -- Блок вставки результатов в целевую таблицу
    ins1 AS (
        INSERT INTO result_table
        SELECT * FROM result
    )
    -- Финальный SELECT возвращает результат клиенту
    SELECT * FROM result;

    Ключевые вопросы и альтернативы

    Насколько корректно такое решение? Технически оно работоспособно и обеспечивает требуемую атомарность. Однако его можно считать антипаттерном по следующим причинам:

    • Нарушение принципа единой ответственности: Запрос совмещает выборку, бизнес-логику, модификацию данных и возврат результата, что снижает читаемость и усложняет поддержку.
    • Сложность отладки: Трудно изолировать и протестировать отдельные этапы процесса.
    • Потенциальные проблемы с производительностью: Сложные CTE с операциями DML могут создавать неочевидные планы выполнения.

    Рекомендуемая альтернатива

    Более чистый подход - использование явной транзакции на уровне приложения или хранимой процедуры:

    1. Начать транзакцию.
    2. Выполнить SELECT ... FOR UPDATE для блокировки обрабатываемых строк в исходных таблицах.
    3. Провести необходимые расчёты на стороне приложения или в БД.
    4. Выполнить INSERT в целевую таблицу.
    5. Выполнить UPDATE исходных таблиц.
    6. Зафиксировать (COMMIT) транзакцию или откатить (ROLLBACK) при ошибке.

    Это разделяет логические этапы, делает код более прозрачным и упрощает мониторинг каждого шага.

    Вывод

    Использование CTE для DML-операций возможно, но не является лучшей практикой для production-решений. Для сложных процессов обработки данных предпочтительнее явное управление транзакциями с чётким разделением этапов: выборка, обработка, запись, обновление статусов. Это улучшает читаемость, тестируемость и долгосрочную поддержку кода.