1. DML: команды изменения данных
Реальный backend живёт не только на чтении, но и на изменениях: создать товар, поменять цену, деактивировать категорию, удалить ошибочную запись. Поэтому после приятного адреналина от SELECT — «О, я нашёл данные! О, я сделал JOIN! Я почти хакер!» — довольно быстро появляется DML: набор команд, которые меняют данные в уже существующих таблицах.
DML (Data Manipulation Language) обычно включает три главные команды: INSERT, UPDATE, DELETE. Важно сразу поймать правильную ментальную модель: SQL-команда почти никогда не «работает с объектом». Она работает с таблицей и набором строк, которые подошли под условие. В этом и сила, и опасность: одна команда может изменить одну строку, а может — тысячу. И база будет совершенно уверена, что вы именно этого и хотели (она же не психолог).
Для наглядности — мини-таблица, которая помогает держать команды в голове не как набор слов, а как ответ на вопрос «что меняется в реальности»:
| Команда | Что происходит с таблицей | Самый главный вопрос, который нужно задавать себе |
|---|---|---|
| INSERT | Добавляется новая строка | «Все обязательные поля заполнены? Какие значения появятся в таблице?» |
| UPDATE | Меняются значения в существующих строках | «Какие строки попадут под WHERE? Сколько их будет?» |
| DELETE | Строки удаляются из таблицы | «Какие строки попадут под WHERE? Точно ли мы хотим их удалить?» |
Ещё одно уточнение, которое особенно помогает на старте: в нашем курсе мы иногда будем показывать SQL как Java-строки. Это не значит, что SQL «живёт в Java». Это просто удобный способ хранить и обсуждать текст запроса рядом с кодом. Выполнять запросы мы будем позже, когда поднимем реальную базу и инфраструктуру.
2. INSERT: добавляем новую строку
INSERT — самая «дружелюбная» команда из тройки DML. Она добавляет новую строку: раньше её не было — теперь она есть. Проблемы начинаются, если вставить «неполноценную» строку: забыть обязательное поле, перепутать тип, нарушить уникальность. Поэтому INSERT хорошо дисциплинирует: заставляет думать о схеме, обязательности колонок и смысле данных, а не просто «кинуть что-нибудь в базу».
Самый базовый и безопасный вариант такой: мы явно указываем список колонок и явно задаём значения для них. Привычка «всегда перечислять колонки» кажется занудной ровно до того момента, когда кто-то добавляет новую колонку в таблицу, а ваш старый INSERT внезапно начинает вести себя странно. SQL — он как кот: если может сделать непредсказуемо, он сделает.
Для контекста вспомним mini-shop (упрощённо): у нас есть таблица category, где хранится справочник категорий товаров.
Пример: вставка одной категории
// Мы храним SQL в Java-строке только для удобства обсуждения и примеров.
String sql = """
-- Явно перечисляем колонки: так запрос не "сломается", если в таблицу добавят новое поле.
insert into category (id, code, name, active)
values (1, 'BOOKS', 'Книги', true)
""";
Здесь есть несколько вещей, которые новички часто пропускают. Во‑первых, строковые значения в SQL пишутся в одинарных кавычках ('BOOKS', 'Книги'). Во‑вторых, true — это значение для булевой колонки (в PostgreSQL это нормально). В‑третьих, мы явно говорим: «вставляем в такие-то колонки такие-то значения». Никакой телепатии.
Иногда нужно вставить не все поля. Например, у категории может быть description, но сейчас мы не обязаны её заполнять, если колонка допускает NULL или имеет значение по умолчанию. Здесь важно не гадать на кофейной гуще, а помнить схему: какие поля обязательны (NOT NULL), какие уникальны (UNIQUE), а какие можно опустить.
Пример: вставка с минимально нужными полями
// Вставка только обязательного минимума: сработает, если остальные поля допускают NULL или имеют DEFAULT.
String sql = """
insert into category (id, code, name)
values (2, 'ELEC', 'Электроника')
""";
Если active в таблице объявлен как NOT NULL без значения по умолчанию, такой INSERT завершится ошибкой. И это не «база вредничает», а база защищает данные от состояния «категория непонятно активна или нет». Если же active имеет DEFAULT true, вставка будет корректной, а active автоматически станет true. Мы не углубляемся сегодня в DDL, но связь тут простая: поведение INSERT на 80% определяется тем, как вы спроектировали таблицу.
Пример: вставка нескольких строк одной командой
// Один INSERT может создать сразу несколько строк: это удобно для справочников и тестовых данных.
String sql = """
insert into category (id, code, name, active)
values
(3, 'SPORT', 'Спорт', true),
(4, 'TOYS', 'Игрушки', true)
""";
Это полезно помнить просто как факт из жизни: SQL умеет работать «пакетом» в рамках одной команды. Но даже в таком примере сохраняется главный принцип: вы всё равно должны мысленно проверить, какие строки появятся в таблице после выполнения.
3. UPDATE: меняем данные и учимся уважать WHERE
С UPDATE всё кажется простым ровно до первого настоящего факапа. Он «всего лишь» меняет значения в строках таблицы. Но в отличие от INSERT, где вы обычно добавляете что-то новое и чаще трогаете одну строку, UPDATE способен за секунду переписать половину таблицы. И в этот момент становится ясно: WHERE — это не декоративная часть запроса, а предохранитель от случайного массового изменения данных.
Базовый синтаксис простой: выбираем таблицу, задаём новые значения в SET и ограничиваем действие условием WHERE. Причём смысл запроса определяется не только SET. По сути, смысл UPDATE — это комбинация «что меняем» и «в каких строках».
Пример: точечное изменение цены по sku
// Точечное обновление: ожидаем изменить максимум одну строку, если sku уникален.
String sql = """
update product
set price = 199.99
where sku = 'BOOK-001'
""";
Это запрос из категории «хороший учебный пример»: он наглядно показывает, что важнее всего здесь WHERE. SET говорит, каким будет новое значение, а WHERE — кого мы вообще трогаем. Если sku уникален (а для товара это естественный инвариант), мы ожидаем изменение максимум одной строки. Если sku не уникален или условие написано неверно, можно обновить больше, чем вы рассчитывали.
Пример: обновляем сразу несколько колонок
// В одном UPDATE можно менять сразу несколько колонок одной и той же строки (или набора строк).
String sql = """
update product
set name = 'Книга: Spring Data JPA для людей',
price = 249.99
where sku = 'BOOK-001'
""";
В SQL нормально менять несколько колонок одной командой. Это не «две операции», а один UPDATE, который применится ко всем строкам, попавшим под условие.
Ещё один важный момент для начинающих: UPDATE может затронуть 0 строк — и это само по себе не ошибка SQL. Это означает, что условие не нашло ни одной подходящей строки. С точки зрения базы всё честно.
Пример: UPDATE, который затронул 0 строк
// Нормальная ситуация: если строки нет, UPDATE выполнится, но изменит 0 строк.
String sql = """
update product
set price = 199.99
where sku = 'BOOK-404'
""";
Если товара с 'BOOK-404' нет, база просто честно ничего не изменит. На практике это даже полезно: вы можете использовать такое поведение как сигнал «данных не нашлось». Но важно понимать семантику: SQL не гарантирует, что «если команда выполнилась, значит что-то изменилось». Команда может успешно выполниться и при этом изменить ноль строк.
Теперь покажем то, чего боятся все — и правильно делают.
Антипример: UPDATE без WHERE
// ВНИМАНИЕ: без WHERE обновятся ВСЕ строки таблицы.
String sql = """
update product
set price = 199.99
""";
Такой запрос буквально означает: «Сделай цену 199.99 у всех товаров». Иногда это действительно нужно — например, для тестовых данных или миграций, — но в обычной жизни это типичная катастрофа. И заметьте: база не сможет понять, что вы «просто забыли WHERE». Для неё это корректная команда.
Поэтому привычка №1 при работе с UPDATE: читать запрос как инженер, а не как писатель. То есть вы всегда должны уметь ответить на вопрос: какие строки будут затронуты? Если ответа нет, запрос ещё не готов к запуску.
4. DELETE: удаляем строки и не удаляем всё
DELETE — самая «нервная» команда дня: не потому, что она сложная, а потому, что слишком простая. Один неверный WHERE, и вы удалили больше, чем хотели. А дальше начинается классический этап принятия: отрицание, гнев, торг, депрессия и попытка вспомнить, когда вы в последний раз делали бэкап. (Спойлер: обычно «никогда», потому что в учебных проектах мы все бессмертные.)
Синтаксис похож на UPDATE, но вместо SET у нас просто «удали строки»:
Пример: осознанное удаление по id
// Точечное удаление: ожидаем DELETE 0 или DELETE 1 (если id уникален, что обычно так и есть).
String sql = """
delete from product
where id = 10
""";
Если строки с id = 10 нет, команда затронет 0 строк — и это снова нормальная семантика SQL. Если строка есть, она исчезнет из таблицы. И вот тут важно не торопиться: «исчезнет» означает «её больше нет». Мы не обсуждаем сейчас откаты и транзакции, но интуитивно полезно воспринимать DELETE как действие, которое делают только когда понимают последствия.
Можно удалять не только одну строку. Например, вы можете удалить все товары, которые заранее отметили как неактуальные. Это уже массовая операция, и именно здесь аккуратность WHERE решает всё.
Пример: удаляем несколько строк по условию
// Массовое удаление: потенциально может снести много строк, поэтому WHERE здесь критичен.
String sql = """
delete from product
where status = 'ARCHIVED'
""";
Этот запрос может удалить 0 строк, может 3, а может 3000 — всё зависит от данных. И это ещё один повод помнить: SQL мыслит наборами. Он не «удаляет объект», он удаляет все строки, подходящие под условие.
И, конечно, есть «главный антипример дня»:
Антипример: DELETE без WHERE
// ВНИМАНИЕ: без WHERE удалятся ВСЕ строки таблицы.
String sql = """
delete from product
""";
Это означает: удалить все строки из таблицы product. Иногда так делают в тестах или при очистке стенда, но если вы случайно сделали это «в бою», это будет очень короткий урок по смирению.
5. Как читать DML «с конца»
Самая полезная привычка при работе с DML — читать команду как преобразование состояния таблицы. То есть думать не «я написал красивый запрос», а «после выполнения таблица будет выглядеть вот так». Сначала это мышление кажется медленным, но именно оно спасает от большинства ошибок быстрее, чем любые лайфхаки. И да, это ровно тот навык, который потом делает работу с ORM осознанной.
Практически это выглядит так. Когда вы видите UPDATE или DELETE, вы сразу находите WHERE и задаёте себе вопрос: «какие строки подходят под условие?». А затем — «сколько таких строк может быть?». Если условие по уникальному полю (id, sku, code) — вы ожидаете максимум одну строку. Если условие по статусу или флагу — строк может быть много. Потом вы мысленно применяете изменение и представляете новое состояние.
Давайте один раз разберём это совсем «в лоб», как на доске.
Представим, что таблица product сейчас выглядит так (упрощённо):
| id | sku | name | price |
|---|---|---|---|
| 10 | BOOK-001 | Книга про JPA | 299.99 |
| 11 | BOOK-002 | Книга про SQL | 199.99 |
И мы выполняем запрос:
-- 1) Сначала читаем WHERE: какие строки попадут под условие?
-- 2) Потом читаем SET: что именно меняется в этих строках?
update product
set price = 249.99
where sku = 'BOOK-001';
-- ожидаем: UPDATE 1
Мы сначала смотрим на WHERE sku = 'BOOK-001' и понимаем: подходит одна строка — с id = 10. Затем применяем SET price = 249.99. Получаем новое состояние:
| id | sku | name | price |
|---|---|---|---|
| 10 | BOOK-001 | Книга про JPA | 249.99 |
| 11 | BOOK-002 | Книга про SQL | 199.99 |
То есть меняется только нужная строка, и меняется только нужная колонка.
Та же логика работает для DELETE. Если мы выполним:
-- Сначала WHERE: если id уникален, ожидаем удалить максимум 1 строку.
delete from product
where id = 11;
-- ожидаем: DELETE 1
Мы понимаем, что под условие подходит одна строка. После команды в таблице останется только:
| id | sku | name | price |
|---|---|---|---|
| 10 | BOOK-001 | Книга про JPA | 249.99 |
Именно привычка «проверить состояние после» — главный практический смысл всей лекции. Не «запомнить синтаксис», а научиться предсказывать результат. Синтаксис вы и так выучите (SQL на 70% состоит из повторения).
Если хочется визуальной схемы, можно мыслить так:
flowchart TD
A["Есть таблица и данные"] --> B["DML-команда (INSERT/UPDATE/DELETE)"]
B --> C["Новая версия таблицы"]
C --> D["Проверяем: это то состояние, которое мы хотели?"]
Когда вы к этому привыкнете, запросы перестанут быть «заклинаниями» и станут обычными, контролируемыми действиями.
6. Типичные ошибки при работе с INSERT, UPDATE, DELETE
Первые ошибки с DML обычно случаются не из-за незнания синтаксиса, а из-за спешки: человек не проверил, что именно делает. Это нормальная стадия взросления разработчика: сначала ты учишься писать запросы, потом учишься не делать ими больно. Ниже — самые частые грабли, на которые наступают почти все, включая людей, которые «всё понимают».
Ошибка №1: забыли WHERE в UPDATE или DELETE.
Это классика, потому что глаз цепляется за SET или за DELETE FROM, а мозг уже мысленно празднует успех. Лечится очень простой привычкой: при чтении запроса первым делом спрашивать себя: «какие строки будут затронуты?». Если ответа нет — запрос рано запускать. Особенно в DELETE.
Ошибка №2: написали слишком широкое условие и обновили или удалили больше строк, чем ожидали.
Например, вместо where sku = 'BOOK-001' написали where name like '%Книга%', а потом удивились, почему «почему-то» поменялись все книги. Здесь помогает дисциплина выбора условий: в точечных операциях опирайтесь на поля, которые по смыслу уникальны и стабильны (id, sku, code). Если условие не уникальное, считайте, что это массовая операция, и относитесь к ней именно так.
Ошибка №3: ожидали, что UPDATE всегда меняет ровно одну строку.
SQL так не работает: он не обещает вам «один объект». Он обещает «набор строк, который подошёл под условие». Если условие подходит к десяти строкам — изменится десять. Если ни к одной — изменится ноль. Поэтому в голове полезно держать три нормальных исхода: 0, 1, много.
Ошибка №4: вставили строку, забыв про обязательные поля и ограничения.
INSERT часто падает на простых вещах: NULL там, где NOT NULL, или дубликат там, где UNIQUE. И это хорошая новость: база данных реально защищает вас от «невозможных состояний». Плохая новость в том, что такие ошибки обычно приходят в самый неудобный момент. Поэтому при INSERT всегда держите схему в голове: какие поля обязательны, какие уникальны и какие значения вообще имеют смысл в домене.
Ошибка №5: удалили строку, не подумав, что она кому-то нужна.
Даже если сегодня мы не углубляемся в связи, здравый смысл уже сейчас подсказывает: товары могут использоваться в заказах, категории — в товарах, и удаление одной записи может сломать другую часть данных. В лучшем случае база не даст это сделать из-за ограничений, в худшем — вы сами выстрелите себе в ногу, и нога будет долго вспоминать этот день. Поэтому DELETE — команда, которую особенно важно выполнять осознанно.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ