Диалект — PostgreSQL. DDL, DML, типы данных, CREATE, ALTER, INSERT, UPDATE, DELETE, транзакции.
В SQL команды делятся на две большие группы:
DDL (Data Definition Language) — работает со структурой базы данных:
CREATE TABLE — создать таблицуALTER TABLE — изменить структуру таблицыDROP TABLE — удалить таблицу целикомDML (Data Manipulation Language) — работает с данными внутри таблиц:
INSERT — вставить строкиUPDATE — обновить строкиDELETE — удалить строки💡 Простая аналогия: DDL — это строительство дома (стены, комнаты), DML — это расстановка мебели внутри.
Прежде чем создавать таблицу, нужно понять, какие типы данных существуют.
| Тип | Описание | Пример |
|---|---|---|
SMALLINT | Целое, 2 байта, от -32 768 до 32 767 | Количество позиций в заказе |
INTEGER / INT | Целое, 4 байта, до ~2 млрд | ID, счётчики |
BIGINT | Целое, 8 байт, до ~9×10¹⁸ | Большие счётчики |
SERIAL | Автоинкрементный INTEGER | Автоматический ID |
NUMERIC(p, s) / DECIMAL(p, s) | Точное число с фиксированной точностью | Деньги: NUMERIC(10, 2) — до 10 знаков, 2 после запятой |
REAL / DOUBLE PRECISION | Число с плавающей точкой | Научные вычисления |
⚠️ Для денег всегда используй
NUMERIC, а неREAL.REALдаёт погрешности:0.1 + 0.2может быть не равно0.3.
| Тип | Описание | Пример |
|---|---|---|
CHAR(n) | Строка фиксированной длины, дополняется пробелами | Коды, символы |
VARCHAR(n) | Строка переменной длины, до n символов | Имена, email |
VARCHAR / TEXT | Строка без ограничения длины | Описания, комментарии |
💡 В PostgreSQL
TEXTиVARCHARпрактически идентичны по производительности.VARCHAR(n)полезен только если нужно ограничить длину (валидация на уровне БД).
| Тип | Описание | Пример |
|---|---|---|
DATE | Только дата | 2026-10-29 |
TIME | Только время | 14:30:00 |
TIMESTAMP | Дата и время без часового пояса | 2026-10-29 14:30:00 |
TIMESTAMPTZ | Дата и время с часовым поясом | 2026-10-29 14:30:00+03 |
INTERVAL | Промежуток времени | 1 year 2 months |
💡 Всегда используй
TIMESTAMPTZ, если работаешь с разными часовыми поясами. Это спасёт тебя от множества багов.
| Тип | Описание | Значения |
|---|---|---|
BOOLEAN / BOOL | Истина / ложь | TRUE, FALSE, NULL |
UUID — универсальный уникальный идентификаторJSON / JSONB — для хранения JSON-документов (JSONB быстрее и индексируется)ARRAY — массивы значенийCREATE TABLE table_name (
column1 datatype constraint,
column2 datatype constraint,
...
);departmentsCREATE TABLE departments (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL UNIQUE,
budget NUMERIC(12, 2) NOT NULL DEFAULT 0,
location VARCHAR(100),
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);Разберём по частям:
id SERIAL PRIMARY KEY — автоинкрементный первичный ключname VARCHAR(100) NOT NULL UNIQUE — строка до 100 символов, обязательная, уникальнаяbudget NUMERIC(12, 2) NOT NULL DEFAULT 0 — число с двумя знаками после запятой, обязательное, по умолчанию 0location VARCHAR(100) — необязательная строкаcreated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP — дата создания, по умолчанию — текущее время💡
SERIAL— это удобство PostgreSQL. На самом деле он создаёт последовательность (sequence) и делает столбец автоинкрементным. В современном SQL (PostgreSQL 10+) можно использовать более стандартный синтаксис:id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY.
employeesCREATE TABLE employees (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
department_id INTEGER REFERENCES departments(id),
city VARCHAR(100) NOT NULL,
salary NUMERIC(10, 2) NOT NULL CHECK (salary > 0),
bonus NUMERIC(10, 2),
is_active BOOLEAN DEFAULT TRUE,
hire_date DATE DEFAULT CURRENT_DATE
);Здесь появились:
REFERENCES departments(id) — внешний ключ, ссылка на departments.idCHECK (salary > 0) — ограничение: зарплата должна быть положительнойDEFAULT TRUE / DEFAULT CURRENT_DATE — значения по умолчаниюprojectsCREATE TABLE projects (
id SERIAL PRIMARY KEY,
name VARCHAR(200) NOT NULL,
department_id INTEGER REFERENCES departments(id) ON DELETE SET NULL,
start_date DATE NOT NULL,
end_date DATE,
CHECK (end_date IS NULL OR end_date >= start_date)
);💡
ON DELETE SET NULL— что делать с проектом, если удалят отдел. Варианты:
CASCADE— удалить и проектSET NULL— обнулитьdepartment_idRESTRICT/NO ACTION— запретить удаление отделаSET DEFAULT— подставить значение по умолчанию3. Ограничения (Constraints): подробно
Ограничения — это правила, которые защищают целостность данных.
NULL-- Составной первичный ключ:
CREATE TABLE order_items (
order_id INTEGER,
product_id INTEGER,
quantity INTEGER,
PRIMARY KEY (order_id, product_id)
);department_id INTEGER REFERENCES departments(id)NULL (один раз)email VARCHAR(255) UNIQUENULLПроизвольное условие, которое должно выполняться:
CHECK (salary > 0)
CHECK (end_date >= start_date)
CHECK (status IN ('active', 'archived', 'deleted'))Значение по умолчанию, если при вставке оно не указано:
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
is_active BOOLEAN DEFAULT TRUE💡 Именованные ограничения — хорошая практика. Если ограничение нарушится, ты увидишь понятное имя в ошибке:
CREATE TABLE employees ( ... salary NUMERIC(10, 2) CONSTRAINT positive_salary CHECK (salary > 0), CONSTRAINT fk_department FOREIGN KEY (department_id) REFERENCES departments(id) );
ALTER TABLE employees
ADD COLUMN middle_name VARCHAR(100);ALTER TABLE employees
DROP COLUMN middle_name;⚠️ Осторожно: удаление столбца удаляет и все данные в нём.
ALTER TABLE employees
ALTER COLUMN salary TYPE NUMERIC(12, 2);⚠️ Изменение типа может потребовать
USINGдля преобразования данных:ALTER TABLE employees ALTER COLUMN salary TYPE INTEGER USING salary::INTEGER;
-- Добавить NOT NULL (только если в столбце нет NULL):
ALTER TABLE employees
ALTER COLUMN city SET NOT NULL;
-- Добавить CHECK:
ALTER TABLE employees
ADD CONSTRAINT valid_bonus CHECK (bonus IS NULL OR bonus >= 0);
-- Удалить ограничение:
ALTER TABLE employees
DROP CONSTRAINT valid_bonus;ALTER TABLE employees RENAME TO staff;
ALTER TABLE staff RENAME COLUMN city TO location;DROP TABLE employees;⚠️ Если на таблицу ссылаются другие таблицы (через FK), будет ошибка. Варианты:
-- Удалить вместе с зависимыми объектами: DROP TABLE departments CASCADE; -- Удалить только если таблица существует: DROP TABLE IF EXISTS employees;💡 Разница между
DROPиDELETE:
DROP TABLE— удаляет всю таблицу (структуру + данные)DELETE FROM— удаляет строки, оставляя таблицу
TRUNCATE TABLE employees;Быстрее, чем DELETE FROM employees (без WHERE), потому что не удаляет строки по одной, а сбрасывает всю таблицу. Но:
WHEREON DELETESERIAL-счётчикиINSERT INTO departments (name, budget, location)
VALUES ('IT', 5000000.00, 'Москва');💡 Мы не указали
id— он сгенерируется автоматически черезSERIAL.💡 Мы не указали
created_at— подставитсяCURRENT_TIMESTAMP.
INSERT INTO departments (name, budget, location)
VALUES
('Продажи', 3000000.00, 'СПб'),
('Маркетинг', 2000000.00, 'Казань'),
('HR', 1500000.00, 'Москва');Это фишка PostgreSQL — очень удобная:
INSERT INTO departments (name, budget, location)
VALUES ('Финансы', 2500000.00, 'Москва')
RETURNING id, name;Результат:
| id | name |
|---|---|
| 5 | Финансы |
Без RETURNING пришлось бы делать отдельный запрос, чтобы узнать ID.
INSERT INTO departments_archive (name, budget, location)
SELECT name, budget, location
FROM departments
WHERE budget > 2000000;-- Ошибка: salary не может быть NULL
INSERT INTO employees (name, department_id, city, salary)
VALUES ('Тест', 1, 'Москва', NULL);
-- Ошибка: нарушен CHECK (salary > 0)
INSERT INTO employees (name, department_id, city, salary)
VALUES ('Тест', 1, 'Москва', -1000);
-- Ошибка: нарушен FK (нет отдела с id=999)
INSERT INTO employees (name, department_id, city, salary)
VALUES ('Тест', 999, 'Москва', 50000);UPDATE employees
SET salary = 125000
WHERE id = 1;⚠️ Без
WHEREобновятся ВСЕ строки! Это частая и очень болезненная ошибка.
UPDATE employees
SET salary = 135000, bonus = 30000
WHERE name = 'Мария';-- Повысить зарплату на 10% всем в IT:
UPDATE employees
SET salary = salary * 1.10
WHERE department_id = (SELECT id FROM departments WHERE name = 'IT');UPDATE employees e
SET salary = e.salary * 1.05
FROM departments d
WHERE e.department_id = d.id
AND d.budget > 3000000;Это PostgreSQL-специфичный синтаксис. В других СУБД используются подзапросы.
UPDATE employees
SET salary = salary * 1.10
WHERE department_id = 1
RETURNING id, name, salary;Вернёт обновлённые строки с новыми значениями.
DELETE FROM employees
WHERE id = 8;⚠️ Без
WHEREудалятся ВСЕ строки! Для полной очистки лучше использоватьTRUNCATE.
DELETE FROM employees
WHERE department_id IN (
SELECT id FROM departments WHERE name = 'HR'
);DELETE FROM employees
WHERE is_active = FALSE
RETURNING id, name;💡
DELETEvsTRUNCATE:
DELETE— построчное удаление, можно сWHERE, медленнее, вызывает триггерыTRUNCATE— сброс всей таблицы, быстрее, нельзя сWHERE, сбрасывает счётчики
Когда нужно выполнить несколько операций как единое целое (либо все, либо ничего), используются транзакции.
BEGIN;
UPDATE employees SET salary = salary * 1.10 WHERE department_id = 1;
UPDATE departments SET budget = budget - 100000 WHERE id = 1;
-- Если всё хорошо:
COMMIT;
-- Если что-то пошло не так:
-- ROLLBACK;Пример: перевод денег между отделами. Если один UPDATE упадёт, второй тоже откатится — деньги не потеряются.
💡 В PostgreSQL
BEGINможно заменить наSTART TRANSACTION.
Попробуй решить самостоятельно, не подглядывая ниже.
clients с полями: id (автоинкрементный PK), name (обязательное, до 150 символов), email (уникальное, до 255 символов), registered_at (с значением по умолчанию — текущее время).clients столбец phone (до 20 символов).CHECK, чтобы email содержал символ @.clients три записи за один запрос.email клиента с id = 1, используя RETURNING.email заканчивается на @test.com.orders с внешним ключом на clients, чтобы при удалении клиента удалялись и его заказы.-- 1
CREATE TABLE clients (
id SERIAL PRIMARY KEY,
name VARCHAR(150) NOT NULL,
email VARCHAR(255) UNIQUE,
registered_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
-- 2
ALTER TABLE clients
ADD COLUMN phone VARCHAR(20);
-- 3
ALTER TABLE clients
ADD CONSTRAINT valid_email CHECK (email LIKE '%@%');
-- 4
INSERT INTO clients (name, email)
VALUES
('Ольга', 'olga@example.com'),
('Дмитрий', 'dmitry@example.com'),
('Светлана', 'svetlana@example.com');
-- 5
UPDATE clients
SET email = 'new_email@example.com'
WHERE id = 1
RETURNING id, name, email;
-- 6
DELETE FROM clients
WHERE email LIKE '%@test.com';
-- 7
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
client_id INTEGER REFERENCES clients(id) ON DELETE CASCADE,
amount NUMERIC(10, 2) NOT NULL CHECK (amount > 0),
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
-- 8
BEGIN;
INSERT INTO clients (name, email)
VALUES ('Николай', 'nikolay@example.com')
RETURNING id;
-- Предположим, вернул id = 4
INSERT INTO orders (client_id, amount)
VALUES (4, 1500.00);
COMMIT;Ты прошла все шесть этапов, если можешь без подсказки объяснить:
DDL отличается от DML;PRIMARY KEY, FOREIGN KEY, UNIQUE, CHECK, NOT NULL;DROP TABLE отличается от DELETE FROM и от TRUNCATE;RETURNING (в PostgreSQL);BEGIN / COMMIT / ROLLBACK.Ты прошла весь план обучения SQL. Давай вспомним, что ты теперь умеешь:
| Этап | Навык |
|---|---|
| 1. Основы | SELECT, WHERE, ORDER BY, LIMIT |
| 2. Агрегация | GROUP BY, HAVING, агрегатные функции |
| 3. Объединения | JOIN, UNION, связи между таблицами |
| 4. Подзапросы | Подзапросы, CTE, коррелированные подзапросы |
| 5. Оконные функции | ROW_NUMBER, RANK, LEAD, LAG, накопительные итоги |
| 6. Модификация | CREATE, INSERT, UPDATE, DELETE, транзакции |
EXPLAIN ANALYZE)