← Назад к вопросам

Можно ли сделать INSERT в несколько таблиц в рамках одного запроса в PostgreSQL?

1.8 Middle🔥 141 комментариев
#REST API и HTTP#Базы данных (SQL)

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

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Краткий ответ

Да, можно выполнить INSERT в несколько таблиц в одном запросе PostgreSQL используя CTE (Common Table Expressions) или транзакции. Это гарантирует атомарность операции — либо все INSERT пройдут, либо все откатятся при ошибке.

Способ 1: WITH (CTE) — Рекомендуемый

Это самый элегантный и безопасный способ:

WITH insert_authors AS (
  INSERT INTO authors (name, email)
  VALUES ('John Doe', 'john@example.com')
  RETURNING id, name
),
insert_books AS (
  INSERT INTO books (title, author_id)
  SELECT 'Python Expert', id FROM insert_authors
  RETURNING id, title
),
insert_reviews AS (
  INSERT INTO reviews (book_id, rating, text)
  SELECT id, 5, 'Excellent book!' FROM insert_books
  RETURNING id, rating
)
SELECT * FROM insert_reviews;

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

Способ 2: Блок PL/pgSQL

Для более сложной логики:

DO $$
DECLARE
    v_author_id INT;
    v_book_id INT;
BEGIN
    INSERT INTO authors (name, email)
    VALUES ('Jane Smith', 'jane@example.com')
    RETURNING id INTO v_author_id;
    
    INSERT INTO books (title, author_id)
    VALUES ('Advanced Python', v_author_id)
    RETURNING id INTO v_book_id;
    
    INSERT INTO reviews (book_id, rating)
    VALUES (v_book_id, 4);
    
    COMMIT;
EXCEPTION WHEN OTHERS THEN
    ROLLBACK;
    RAISE NOTICE 'Error: %s', SQLERRM;
END $$;

Способ 3: Транзакция из Python (SQLAlchemy)

from sqlalchemy import create_engine, Column, Integer, String, ForeignKey
from sqlalchemy.orm import declarative_base, Session
from sqlalchemy.exc import SQLAlchemyError

Base = declarative_base()

class Author(Base):
    __tablename__ = 'authors'
    id = Column(Integer, primary_key=True)
    name = Column(String)
    email = Column(String)

class Book(Base):
    __tablename__ = 'books'
    id = Column(Integer, primary_key=True)
    title = Column(String)
    author_id = Column(Integer, ForeignKey('authors.id'))

engine = create_engine('postgresql://user:password@localhost/db')

def insert_multiple_tables():
    with Session(engine) as session:
        try:
            author = Author(name='John Doe', email='john@example.com')
            session.add(author)
            session.flush()
            
            book = Book(title='Python Mastery', author_id=author.id)
            session.add(book)
            session.flush()
            
            session.commit()
            print(f'Success! Author ID: {author.id}, Book ID: {book.id}')
            
        except SQLAlchemyError as e:
            session.rollback()
            print(f'Error: {e}')

Способ 4: Из Python с psycopg2

import psycopg2

connection = psycopg2.connect(
    dbname='mydb',
    user='user',
    password='password',
    host='localhost'
)

cursor = connection.cursor()

try:
    cursor.execute('BEGIN;')
    
    cursor.execute(
        'INSERT INTO authors (name, email) VALUES (%s, %s) RETURNING id',
        ('John', 'john@example.com')
    )
    author_id = cursor.fetchone()[0]
    
    cursor.execute(
        'INSERT INTO books (title, author_id) VALUES (%s, %s)',
        ('Python Guide', author_id)
    )
    
    connection.commit()
    print('All inserts successful!')
    
except Exception as e:
    connection.rollback()
    print(f'Error: {e}')
finally:
    cursor.close()
    connection.close()

Сравнение подходов

CTE (WITH) - элегантно, одна транзакция, чистый SQL

PL/pgSQL - мощная логика, использование переменных SQLAlchemy транзакция - Pythonic, type-safe, ORM преимущества psycopg2 транзакция - легкий контроль, низкоуровневый

Важные замечания

Атомарность: PostgreSQL гарантирует, что если одна операция упадёт, то откатятся все.

FOREIGN KEY: Если есть ограничения, важен порядок INSERT - сначала родительские таблицы, потом дочерние.

Производительность: Для массовых INSERT лучше использовать батчи:

INSERT INTO authors (name, email) VALUES
  ('Author 1', 'a1@example.com'),
  ('Author 2', 'a2@example.com'),
  ('Author 3', 'a3@example.com');

Вывод: Да, полностью возможно в одном запросе/транзакции. CTE (WITH) - рекомендуемый способ для SQL. SQLAlchemy транзакция - best practice для Python. Всегда обрабатывай исключения и откатывай транзакцию при ошибке.