INSERT INTO: добавление строк

INSERT INTO: добавление строк

До сих пор все запросы читали данные — SELECT. Но реальные приложения не только читают: они добавляют, изменяют и удаляют данные. Операторы изменения данных называются DML (Data Manipulation Language): INSERT, UPDATE, DELETE. В этом уроке — INSERT INTO: как добавлять новые строки в таблицу.

Базовый синтаксис INSERT

INSERT INTO таблица (col1, col2, col3)
VALUES (val1, val2, val3);

Пример — добавить нового клиента:

INSERT INTO customers (name, email, city)
VALUES ('Анна Новая', 'anna@example.com', 'Москва');

СУБД создаст новую строку в таблице customers с указанными значениями. Колонки, не перечисленные в списке, получают значения по умолчанию (или NULL, если умолчания нет).

Зачем перечислять колонки

Теоретически можно писать без списка колонок:

-- Без списка колонок (не рекомендуется)
INSERT INTO customers VALUES (1, 'Анна', 'anna@example.com', 'Москва');

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

Кроме того, явный список колонок:

  • Документирует, какие поля заполняются
  • Позволяет пропустить колонки с умолчаниями (например, id SERIAL или created_at DEFAULT NOW())

Вставка нескольких строк

Один INSERT может добавить несколько строк сразу:

INSERT INTO customers (name, email, city)
VALUES
    ('Борис Иванов',  'boris@example.com', 'СПб'),
    ('Вера Петрова',  'vera@example.com',  'Казань'),
    ('Гриша Сидоров', 'grisha@example.com', 'Москва');

Это эффективнее, чем три отдельных INSERT — один раз обращаемся к таблице, одна транзакция.

Автоинкрементные колонки: SERIAL и GENERATED

Первичный ключ id обычно заполняется автоматически — не нужно указывать его в INSERT:

-- Таблица: customers(id SERIAL, name TEXT, email TEXT)
INSERT INTO customers (name, email)
VALUES ('Новый клиент', 'new@example.com');
-- id получит следующее значение последовательности: 1, 2, 3...

SERIAL в PostgreSQL — это сокращение для INTEGER с автоматической последовательностью (sequence). PostgreSQL 10+ предлагает стандартный вариант: id GENERATED ALWAYS AS IDENTITY. Оба работают аналогично — при вставке без явного id СУБД генерирует следующее значение.

RETURNING: получить вставленные значения

PostgreSQL-расширение RETURNING возвращает значения вставленных строк — полезно, чтобы узнать сгенерированный id:

INSERT INTO customers (name, email, city)
VALUES ('Дина Орлова', 'dina@example.com', 'Москва')
RETURNING id, name;

Результат:

id | name
---+-----------
5  | Дина Орлова

RETURNING id — самый частый случай: приложение получает id только что вставленной записи и может сразу использовать его для создания связанных записей. Без RETURNING пришлось бы делать отдельный SELECT для получения id.

Проверь себя: что вернёт INSERT ... RETURNING *?

Значения по умолчанию

Если колонка имеет значение по умолчанию (DEFAULT), можно явно его использовать:

INSERT INTO orders (customer_id, amount, status)
VALUES (5, 1500.00, DEFAULT);
-- status получит своё DEFAULT-значение, например 'pending'

Или просто пропустить колонку в списке — результат тот же.

NULL в INSERT

Если нужно явно вставить NULL:

INSERT INTO customers (name, email, phone)
VALUES ('Тест', 'test@example.com', NULL);

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

Если колонка объявлена NOT NULL и нет DEFAULT — попытка вставить NULL или пропустить колонку вызовет ошибку:

-- Ошибка: email NOT NULL, нельзя пропустить или вставить NULL
INSERT INTO customers (name) VALUES ('Тест');
-- ERROR: null value in column "email" of relation "customers"

Ошибки при вставке: нарушения ограничений

INSERT может завершиться ошибкой при нарушении ограничений:

Уникальность (UNIQUE / PRIMARY KEY):

INSERT INTO customers (id, name) VALUES (1, 'Дубль');
-- ERROR: duplicate key value violates unique constraint "customers_pkey"

Внешний ключ (FOREIGN KEY):

INSERT INTO orders (customer_id, amount) VALUES (9999, 100);
-- ERROR: insert or update on table "orders" violates foreign key constraint
-- Key (customer_id)=(9999) is not present in table "customers"

NOT NULL:

INSERT INTO customers (name) VALUES ('Тест');  -- пропустили email NOT NULL
-- ERROR: null value in column "email" violates not-null constraint

Все эти ошибки — защитные механизмы СУБД, которые не дают сохранить некорректные данные. Подробнее об ограничениях — в модуле 9.

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

При необходимости вставить тысячи строк есть несколько подходов:

Пакетная вставка (batch insert) — несколько VALUES в одном INSERT:

INSERT INTO products (title, price, category) VALUES
  ('Товар 1', 100, 'A'), ('Товар 2', 200, 'B'), ..., ('Товар 1000', 150, 'C');

Значительно быстрее, чем 1000 отдельных INSERT.

COPY — специальная команда PostgreSQL для загрузки данных из файла. Это самый быстрый способ вставки больших объёмов:

COPY customers (name, email, city) FROM '/path/to/data.csv' CSV HEADER;

COPY — тема за рамками этого курса, но важно знать: для ETL-задач и загрузки больших датасетов COPY на порядок быстрее последовательных INSERT.

INSERT и транзакции

По умолчанию каждый INSERT в PostgreSQL — автоматическая транзакция: либо строка вставляется полностью, либо не вставляется вовсе. Если INSERT нескольких строк прерывается на середине (например, вторая строка нарушает ограничение), ни одна строка не будет вставлена.

Подробнее о транзакциях — в модуле 10. Пока достаточно знать: INSERT атомарен.

Вставка из SELECT: краткий обзор

Можно вставить в таблицу данные из другой таблицы:

-- Скопировать московских клиентов в архивную таблицу
INSERT INTO customers_archive (name, email, city)
SELECT name, email, city
FROM   customers
WHERE  city = 'Москва';

INSERT INTO ... SELECT — мощный инструмент для массового копирования данных. Подробнее — в следующем уроке модуля.

Практический пример: регистрация пользователя

Типичный сценарий в веб-приложении — создание нового пользователя с получением его id:

INSERT INTO users (username, email, password_hash, created_at)
VALUES ('anna_smith', 'anna@company.com', 'hashed_value', NOW())
RETURNING id;

Приложение использует возвращённый id для создания профиля или начальных настроек. NOW() — функция, возвращающая текущее время — подставляет временную метку регистрации.

Как проверить результат INSERT

INSERT без RETURNING не возвращает данные — только сообщение INSERT 0 1 (0 OID, 1 строка вставлена). Чтобы убедиться, что вставка прошла корректно, выполните SELECT после:

INSERT INTO customers (name, email, city)
VALUES ('Тест', 'test@example.com', 'Москва');

-- Проверка:
SELECT * FROM customers WHERE email = 'test@example.com';

Или используйте RETURNING сразу:

INSERT INTO customers (name, email, city)
VALUES ('Тест', 'test@example.com', 'Москва')
RETURNING *;

RETURNING * возвращает всю вставленную строку, включая сгенерированный id и все поля с DEFAULT.

Ментальная модель: INSERT как добавление в список

Таблица — это упорядоченный список строк. INSERT добавляет в конец этого списка новую запись. Порядок строк без ORDER BY не гарантирован — СУБД возвращает строки в произвольном порядке. Не полагайтесь на то, что «новая строка всегда последняя в SELECT * FROM».

Краткий итог

  • INSERT INTO таблица (колонки) VALUES (значения) — добавляет строку
  • Всегда перечисляйте колонки явно — защита от изменений схемы
  • Несколько строк в одном INSERT VALUES (...) — эффективнее серии одиночных вставок
  • Колонки с DEFAULT или SERIAL можно не указывать — СУБД заполнит автоматически
  • RETURNING col (PostgreSQL) — возвращает значения вставленной строки, например сгенерированный id
  • Нарушения ограничений (PK, FK, NOT NULL, UNIQUE) вызывают ошибку и отменяют вставку

INSERT в реальных приложениях

В production-коде INSERT почти никогда не выполняется напрямую из SQL-клиента — он идёт через ORM (Object-Relational Mapper) или prepared statements в коде приложения. Это важно с точки зрения безопасности: параметры передаются отдельно, что предотвращает SQL-инъекции.

Пример на Node.js с pg-клиентом:

// Параметры передаются отдельно — безопасно
const result = await client.query(
  'INSERT INTO customers (name, email) VALUES ($1, $2) RETURNING id',
  ['Анна', 'anna@example.com']
);
const newId = result.rows[0].id;

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

Что дальше

Вы умеете добавлять строки. Следующий урок — UPDATE: как изменять существующие данные. Особое внимание к WHERE — без него UPDATE изменит все строки в таблице.

Попробуйте интерактивную версию

Практические задачи, квизы и AI-наставник — бесплатный старт без карты

Перейти к практике