
SQL: знакомство
19 постов
19 постов
20 постов
Когда мы видим AVG, кажется, всё просто:
Возьми все значения и найди среднее арифметическое.
Но на практике AVG часто ведёт себя не так, как ты ожидаешь — и это причина десятков аналитических факапов в отчётах.
А пока подписывайся на мой каналНа связи: SQL Там я публикую посты про особенности и нюансы SQL. Этот канал про то, как не бояться баз данных, понимать, что такое JOIN, GROUP BY и почему NULL ≠ 0. Его я веду с нуля подписчиков. Присоединяйся!
Что делает AVG()?
AVG() - агрегатная функция. Она считает среднее значение по всем строкам, которые попали в выборку после фильтрации WHERE
SELECT AVG(column_name)
FROM table_name
WHERE condition;
Пример:
| id | salary |
| -- | ------ |
| 1 | 1000 |
| 2 | 2000 |
| 3 | NULL |
| 4 | 3000 |
SELECT AVG(salary) FROM employees;
Результат → 2000
❗ Почему не (1000 + 2000 + 0 + 3000) / 4 = 1500?
Потому что AVG игнорирует NULL.
Он считает (1000 + 2000 + 3000) / 3.
Если тебе нужно учесть “отсутствие зарплаты” как ноль —
надо явно это сказать базе:
SELECT AVG(COALESCE(salary, 0)) FROM employees;
AVG() — базовый инструмент в аналитике:
📈 средний чек (AVG(order_amount))
💰 средняя зарплата по отделу
🕒 среднее время выполнения заказа
⭐ средний рейтинг продукта
Но это только верхушка айсберга.
Ниже — интересные кейсы, где AVG используется неочевидно, но мощно 👇
Обычный AVG считает всё одинаково,
но в реальном мире “вес” данных может быть разный.
Пример — средняя оценка курса:
| user_id | rating | reviews |
| ------- | ------ | ------- |
| 1 | 5 | 1 |
| 2 | 4 | 20 |
Если ты просто возьмёшь AVG(rating) → 4.5
Но по факту второй пользователь оставил 20 отзывов, его мнение должно весить больше:
SELECT SUM(rating * reviews) / SUM(reviews) AS weighted_avg
FROM ratings;
Вот это уже взвешенное среднее, и результат будет ближе к 4.
AVG(AVG(...)) ≠ AVG(...)
Пример:
| group | value |
| ----- | ----- |
| A | 10 |
| A | 20 |
| B | 100 |
| B | 100 |
| B | 100 |
SELECT AVG(avg_val) FROM (
SELECT group, AVG(value) AS avg_val
FROM t GROUP BY group
) s;
Результат будет → 57.5
А реальное среднее по всей таблице = 86.
Почему?
Потому что при втором AVG каждая группа имеет одинаковый “вес”,
а не количество строк. Это типичный аналитический капкан.
AVG() можно использовать по “скользящему окну”, чтобы считать динамику:
SELECT
date,
AVG(price) OVER (ORDER BY date ROWS 6 PRECEDING) AS moving_avg_7d
FROM stock_prices;
👉 Это 7-дневное скользящее среднее — классика анализа временных рядов, трейдинга и предсказания трендов.
В аналитике AVG часто используют вместе со STDDEV:
STDDEV (Standard Deviation) — стандартное отклонение (среднеквадратичное отклонение). Это статистическая мера разброса данных относительно их среднего значения в определённом периоде.
SELECT *
FROM purchases
WHERE amount > AVG(amount) + 3 * STDDEV(amount);
Так находят аномально большие значения — подозрительные платежи, мошеннические операции и т.д.
Мало кто знает, что AVG() можно применять даже к датам:
SELECT AVG(order_date)::date FROM orders;
PostgreSQL переведёт даты во внутренние числа и вычислит “среднюю дату” —
по сути, середину временного диапазона.
Это удобно, если хочешь понять, когда чаще всего происходили события.
AVG() всегда вычисляется через SUM и COUNT,
поэтому если ты делаешь:
SELECT COUNT(*), SUM(amount), AVG(amount)
— оптимизатор посчитает всё за один проход по данным.
Но если AVG в отдельном запросе — будет второй проход.
Мелочь, а при миллионах строк чувствуется.
Мой канал На связи: SQL ждет тебя, если ты тоже хочешь познакомиться с базовым языком для аналитики данных.
Подписывайся!
Когда мы слышим «COUNT» и «SUM» мы подразумеваем - «считает строки», «суммирует числа»
Но даже для таких действий есть тонкости и нюансы.
А пока подписывайся на мой канал На связи: SQL Там я публикую посты про особенности и нюансы SQL. Этот канал про то, как не бояться баз данных, понимать, что такое JOIN, GROUP BY и почему NULL ≠ 0. Его я веду с нуля подписчиков. Присоединяйся!
В большинстве случаев мы используем 3 вида COUNT
COUNT(*) — считает все строки, даже пустые.
COUNT(column) — считает только те строки, где есть данные.
COUNT(DISTINCT column) — считает уникальные значения в column.
Рассмотрим на конкретном примере:
| id | name | gift |
-------------------------------
| 1 | Оля | Торт |
| 2 | Вася | NULL |
| 3 | Маша | Цветы |
| 4 | Петя | Торт |
| 5 | Катя | NULL |
| 6 | Оля | Торт |
Считаем все строки, независимо от содержимого колонок:
SELECT COUNT(*) FROM guests;
Результат: 6
Всего 6 гостей пришло.
Неважно, принес ли кто-то подарок или нет, учитывается каждая строка.
Считаем только те строки, где колонка gift не NULL:
SELECT COUNT(gift) FROM guests;
Результат: 4
Только Оля (Торт), Маша (Цветы), Петя (Торт), Оля (Торт) учитываются.
Вася и Катя, у которых gift = NULL, не считаются.
Эта разница показывает: сколько записей реально имеют данные по этой колонке.
Считаем уникальные подарки:
SELECT COUNT(DISTINCT gift) FROM guests;
Результат: 2
Есть только два уникальных подарка: Торт и Цветы.
Независимо от того, сколько гостей принесли одинаковый подарок, каждый подарок считается один раз.
COUNT(*) — общее количество записей (все строки).
COUNT(column) — сколько строк с заполненным значением в колонке.
COUNT(DISTINCT column) — сколько уникальных значений встречается в колонке.
Вывод: разница между этими числами может показать пропуски (NULL) и повторяющиеся данные. Это важно при анализе — если просто взять COUNT(*), можно недооценить проблему с пропущенными значениями.
Чтобы показать, как эта функция работает и какие тонкости бывают, рассмотрим таблицу:
Таблица orders:
| id | customer | amount |
| -- | -------- | ------ |
| 1 | Оля | 100 |
| 2 | Вася | NULL |
| 3 | Маша | 200 |
| 4 | Петя | 150 |
| 5 | Катя | NULL |
| 6 | Оля | 100 |
1️⃣ SUM(amount)
Считаем сумму по колонке amount (игнорирует NULL):
SELECT SUM(amount) FROM orders;
Результат: 550
Складываются только числа: 100 + 200 + 150 + 100 = 550
NULL не учитываются
Если бы все значения были NULL, результат был бы NULL, а не 0
Считаем сумму уникальных значений:
SELECT SUM(DISTINCT amount) FROM orders;
Результат: 450
Уникальные значения amount: 100, 150, 200
Складываем их: 100 + 150 + 200 = 450
Показывает, сколько реально различных сумм встречается, игнорируя повторения
узнаем, сколько различных сумм клиенты реально платят.
Это полезно, чтобы понять разнообразие корзин, например: есть ли клиенты, которые покупают одинаковые пакеты товаров.
NULL не участвуют — всегда нужно помнить, что SUM(column) не считает NULL.
DISTINCT меняет результат — если в колонке повторяются значения, сумма с DISTINCT будет меньше обычной суммы.
Суммирование строк с пропусками — может дать неожиданное ощущение «потери данных».
Вывод:
SUM(column) = фактическая сумма всех чисел
SUM(DISTINCT column) = сумма только уникальных чисел
Важно учитывать NULL, иначе можно получить неожиданные результаты
Все знают GROUP BY.
Тот самый оператор, который превращает кучу строк в аккуратную табличку с суммами и средними.
Но можно и по-другому взглянуть на GROUP BY
А пока подписывайся на мой канал На связи: SQL Там я публикую посты про особенности и нюансы SQL. Этот канал про то, как не бояться баз данных, понимать, что такое JOIN, GROUP BY и почему NULL ≠ 0. Его я веду с нуля подписчиков. Присоединяйся!
В большинстве случаев GROUP BY используют вместе с агрегирующими функциями SUM, COUNT или AVG.
Но есть и другие возможности использования группировки.
В качестве изящной замены DISTINCT
SELECT department FROM employees GROUP BY department;
работает так же, как
SELECT DISTINCT department FROM employees;
Группировать можно по выражениям, а не только по столбцам
Например, хочешь посчитать заказы по годам:
SELECT EXTRACT(YEAR FROM created_at) AS year, COUNT(*)
FROM orders
GROUP BY EXTRACT(YEAR FROM created_at);
Или сгруппировать товары по тысячам рублей:
SELECT (price / 1000)::int AS price_group, COUNT(*)
FROM products
GROUP BY (price / 1000)::int;
GROUP BY умеет строить иерархии
ROLLUP, CUBE, GROUPING SETS — три команды богов:
SELECT region, city, SUM(sales)
FROM orders
GROUP BY ROLLUP (region, city);
→ покажет суммы по городам, по регионам и общий итог.
И всё это одним запросом.
NULL — это тоже группа
Если у тебя несколько строк с NULL в поле department,
то GROUP BY department соберёт их все в одну группу NULL.
SELECT department, COUNT(*)
FROM employees
GROUP BY department;
Логичней использовать COALESCE, чтобы потом не работать с пустыми строками
SELECT COALESCE(department, 'Unknown') AS department, COUNT(*)
FROM employees
GROUP BY COALESCE(department, 'Unknown');
SELECT vs GROUP BY — всё, что не агрегат, должно быть в GROUP BY
SELECT department, name, COUNT(*)
FROM employees
GROUP BY department;
Запрос упадёт, потому что name не в агрегате и не в GROUP BY.
В PostgreSQL есть хитрости: можно использовать array_agg(name) или string_agg(name, ', ')
GROUP BY и оконные функции — не конкуренты
GROUP BY сжимает таблицу.
OVER(PARTITION BY) — сохраняет строки, но добавляет агрегат.
SELECT name, department,
SUM(salary) OVER (PARTITION BY department) AS dep_total
FROM employees;
SQL сам решает, как группировать
PostgreSQL может выбрать:
HashAggregate — если данных много
Sort + GroupAggregate — если их мало или мало уникальных значений
То есть одна и та же команда GROUP BY под капотом работает по-разному.
Вот почему один и тот же запрос на 10k строк работает мгновенно, а на 10M — вечность.
PostgreSQL не просто тупо группирует строки, а выбирает стратегию (план выполнения) — как именно эту группировку реализовать.
Это можно отследить в EXPLAIN и уже потом контролировать включением/выключением конкретных алгоритмов.
SET enable_hashagg = off;
SET enable_sort = off;
Это полезно для тестирования или отладки - посмотреть, как изменится план.
GROUP BY — это не просто «посчитать среднюю зарплату по отделу».
Это мощный инструмент, который может:
имитировать DISTINCT
строить иерархические отчёты
объединяться с оконными функциями
…и при этом легко устроить тебе день боли, если ты не знаешь, что делаешь 😅
Псевдонимы в SQL (алиасы) кажутся «косметикой», но у них есть подводные камни и тонкости, которые реально влияют на запросы.
Псевдонимы - это временные имена для таблиц или столбцов внутри запроса.
В моем канале На связи: SQL уже есть про это посты: вот этот. Канал я веду с нуля подписчиков. Чтобы показать, что аналитика и SQL это не страшно, а интересно. Присоединяйся, если ты тоже хочешь начать изучать SQL или вспомнить и осознать некоторые нюансы.
SELECT c.name AS customer_name
FROM customers c;
c — алиас таблицы customers
customer_name — алиас для колонки c.name
Внутри результата ты видишь только customer_name.
Сокращение длинных имён
- читабельнее, чем всё время писать orders.id, customers.name
JOIN и self-join
Без алиасов нельзя отличить таблицы при самосоединении:
Агрегация и подзапросы
Алиас часто обязателен:
SELECT avg(salary) AS avg_salary
FROM employees;
Derived tables (подзапросы в FROM)
Тут алиас обязателен:
SELECT sub.department, sub.cnt
FROM ( SELECT department, count(*) cnt
FROM employees
GROUP BY department
) sub;
В том же уровне WHERE
SELECT salary * 1.2 AS new_salary
FROM employees
WHERE new_salary > 5000; -- ❌ Ошибка
Алиасы доступны только в SELECT/ORDER BY, но не в WHERE или GROUP BY.
→ нужно повторно писать выражение или завернуть в CTE.
Порядок обработки
SQL выполняется в порядке: FROM → JOIN → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT.
Алиасы появляются только после SELECT.
Поэтому:
в WHERE и GROUP BY алиасы ещё не видны;
в ORDER BY алиасы уже можно использоват
Алиасы влияют только на внешний уровень запроса
Они не меняют структуру таблицы.
В EXPLAIN ты увидишь исходные имена столбцов, а не алиасы.
В подзапросах алиас обязателен для таблицы
Даже если кажется, что можно без него.
SELECT *
FROM (SELECT 1) -- ❌ Ошибка
SELECT *
FROM (SELECT 1) AS t;
ORDER BY может использовать алиас
Ускоряет жизнь:
SELECT salary * 1.2 AS new_salary
FROM employees
ORDER BY new_salary DESC;
CTE против алиасов
Если выражение слишком длинное (например, CASE WHEN …), лучше вынести его в CTE вместо алиаса в SELECT → так его можно переиспользовать и в WHERE, и в ORDER BY.
Алиасы могут влиять на читаемость, но и запутывать
Пример анти-паттерна:
SELECT u.id AS order_id -- ❌ псевдоним вводит в заблуждение
FROM users u;
→ плохая идея, потому что вводит читателя в ошибку.
Итог: алиасы — не просто косметика. Это инструмент:
для читаемости;
для самосоединений и подзапросов;
для управления порядком сортировки.
Но они не живут дольше SELECT-а
Подписывайся на мой канал На связи SQL и давай изучать особенности вместе!
Про JOIN обычно пишут общую теоретическую информацию. Всегда упоминают виды JOIN-ов: INNER, LEFT, RIGHT, FULL, CROSS, а за кулисами могут остаться интересные факты, подводные камни и тонкости, которые редко упоминаются, но которые могут реально пригодиться.
В моем канале На связи: SQL уже есть про это посты: вот этот и вот этот. Канал я веду с нуля подписчиков. Чтобы показать, что аналитика и SQL это не страшно, а интересно. Присоединяйся, если ты тоже хочешь начать изучать SQL или вспомнить и осознать некоторые нюансы.
Для INNER JOIN — разницы нет, фильтрация в ON или в WHERE даст одинаковый результат.
Но для LEFT JOIN это уже не так:
ON фильтрует при объединении (строка может остаться с NULL в правой таблице).
WHERE фильтрует после — и может "выбросить" строки, ради которых делался LEFT JOIN.
👉 Классический баг у новичков: пишут условие в WHERE и не понимают, почему LEFT превратился в INNER.
Предположим, у нас есть две таблицы:
orders (таблица заказов):
order_id | customer_id | amount
---------|-------------|--------
1 | 1 | 100
2 | 2 | 200
3 | 1 | 150
customers (таблица клиентов):
customer_id | country
------------|---------
1 | USA
2 | UK
3 | USA
И есть два запроса:
SELECT o.order_id, c.customer_id, c.country
FROM orders o
LEFT JOIN customers c ON o.customer_id = c.customer_id
AND c.country = 'USA';
SELECT o.order_id, c.customer_id, c.country
FROM orders o
LEFT JOIN customers c ON o.customer_id = c.customer_id
WHERE c.country = 'USA';
Результаты этих запросов будут одинаковы?
Можно писать свои рассуждения в комментариях.
SELECT *
FROM a
JOIN b ON a.value BETWEEN b.min AND b.max;
Это называется non-equi join (неравенственный джойн).
👉 В BI и аналитике это часто используют для «поиска диапазона» (например, попадает ли дата заказа в акцию).
Но! Такой JOIN почти всегда тяжелее, потому что индексы плохо помогают.
Многие базы его поддерживают, но реально на проектах встречается редко. Почему?
Почти всегда можно заменить комбинацией LEFT JOIN UNION RIGHT JOIN.
А оптимизаторы некоторых СУБД работают с FULL OUTER JOIN медленнее.
👉 Часто факт наличия FULL JOIN в запросе сигнализирует, что "что-то не так со схемой данных".
Все думают, что CROSS JOIN = "перемножить всё на всё". Но он используется:
для генерации тестовых данных:
SELECT d::date
FROM generate_series('2025-01-01', '2025-01-31', interval '1 day') d
CROSS JOIN users;
для построения матриц, календарей, отчетов с дырками.
👉 То есть CROSS JOIN часто — инструмент BI-разработчика
NULL = NULL → всегда FALSE.
Поэтому если соединяешь таблицы по колонке с NULL, то такие строки просто теряются.
👉 В проде это часто ломает аналитику: ожидали, что будет связь «пустое с пустым», а SQL этого не понимает.
Вместо NOT IN или NOT EXISTS можно писать LEFT JOIN ... WHERE b.id IS NULL.
Это часто быстрее, особенно в старых версиях MySQL.
👉 Но не забывай: NOT IN (NULL, ...) ведет себя неожиданно (возвращает пустой набор).
SQL — декларативный язык. Ты пишешь JOIN-ы в любом порядке, но оптимизатор сам решает, какую таблицу читать первой, как переставить местами соединения. Поэтому писать «самую маленькую таблицу первой» часто не имеет смысла. Но иногда хинты (JOIN ORDER, FORCE JOIN, USE INDEX) всё же нужны, когда оптимизатор ошибается.
Когда мы пишем:
мы как бы говорим:
👉 «Дай мне все заказы, вместе с клиентами и регионами».
НО! Мы не указываем порядок, в котором эти таблицы реально будут соединяться.
Оптимизатор (query planner) сам решает:
какую таблицу читать первой;
по какому индексу идти;
в каком порядке выполнять JOIN-ы.
И этот порядок почти всегда ≠ порядок в SQL-запросе.
Оптимизатор строит граф зависимостей между таблицами и условиями.
Считает «стоимость» разных стратегий (в PostgreSQL это cost-based optimizer).
Выбирает план с минимальной стоимостью: например, начать с маленькой таблицы, потом по индексу сходить в большую.
Иногда оптимизатор ошибается. Причины:
Неправильная статистика (например, таблица только что обновилась).
Очень сложный запрос (10+ JOIN-ов, подзапросы).
Особенности движка (MySQL раньше любил «сначала левую таблицу»).
Тогда СУБД позволяют подсказать оптимизатору:
FORCE ORDER (SQL Server, Oracle) → использовать JOIN-ы в том порядке, как написаны.
LEADING (Oracle) → указать, с какой таблицы начать.
USE INDEX (MySQL) → подсказать, какой индекс использовать.
PostgreSQL не имеет FORCE JOIN, но можно:
отключить конкретные алгоритмы (SET enable_hashjoin = off;)
использовать LATERAL, JOIN LATERAL, CROSS JOIN чтобы «подсказать» порядок.
Суперсложные запросы (20+ таблиц).
Оптимизатор может выбрать очень дорогой план → запрос работает минуты/часы.
Иногда правильный хинт → ускорение в десятки раз.
Необновлённая статистика.
Оптимизатор думает: «таблица маленькая», но на самом деле она разрослась. → выбирает плохой порядок JOIN.
LIMIT + ORDER BY.
Тут реально важно, с чего начать — иногда оптимизатор «тянет» всю таблицу, хотя мог бы остановиться раньше.
В PostgreSQL можно посмотреть план:
EXPLAIN (ANALYZE, BUFFERS) SELECT ...
→ увидишь реальный JOIN order.
Не доверяй слепо «писать маленькую таблицу первой» — это миф, из старых времён MySQL.
Иногда лучше переписать запрос так, чтобы оптимизатору нечего было гадать. Например, вынести фильтрацию в CTE или subquery.
JOIN — это не только "соединить таблицы", а целый набор особенностей: от NULL и порядка фильтрации до генерации календарей через CROSS JOIN.
Подписывайся на мой канал На связи SQL и давай изучать особенности вместе!
NULL — это специальное значение в SQL, которое означает отсутствие данных или неизвестное значение. Важно понимать, что NULL ≠ 0 и NULL ≠ пустая строка (‘’).
А пока подписывайся на мой канал На связи: SQL Там я публикую посты про особенности и нюансы SQL. Этот канал про то, как не бояться баз данных, понимать, что такое JOIN, GROUP BY и почему NULL ≠ 0. Его я веду с нуля подписчиков. И пост про NULL уже ждет тебя! Присоединяйся!
NULL не равен ничему, даже самому себе. Не является числом, строкой или другим типом данных.
И это надо запомнить при работе с данными.
Все что вы будете сравнивать с NULL будет возвращаться как UNKNOWN.
Для работы с NULL есть специальные операторы.
-- Проверка на NULL
SELECT *
FROM table
WHERE column IS NULL;
-- Проверка на НЕ NULL
SELECT *
FROM table
WHERE column IS NOT NULL;
-- Замена NULL на значение
SELECT COALESCE(column, 'значение_по_умолчанию')
FROM table;
NULL появляется там, где есть необязательные поля для заполнения, при отсутствии связей между сущностями, при ошибках ввода, когда данные неизвесты.
Вообще, понятие NULL появилось в начале 1970-х годов. Разработчики реляционных БД специально создали такое значение для обозначения отсутствия данных. Это было революционным решением, позволившим хранить информацию более гибко.
Математические фокусы с NULL:
Любое число + NULL = NULL
NULL * 100 = NULL
NULL / 2 = NULL
Индексы могут работать медленнее при наличии NULL
Некоторые типы индексов вообще не поддерживают NULL
Агрегатные функции игнорируют NULL по умолчанию
Ну и коротко про то что такое Но(у)ль:
Ноль — это чётко определённое значение:
Числовое значение
Имеет математический смысл
Участвует в вычислениях
Равно самому себе
А теперь про пусто:
Пустота — отсутствие содержимого
Пустое значение может быть:
Пустой строкой (‘’ или “”)
Пустым массивом
Пустым объектом
Имеет конкретный тип данных
Рекомендации:
Используйте NULL для неизвестных данных
Применяйте 0 для числовых значений по умолчанию
Используйте пустые строки для незаполненных текстовых полей
Помните: каждое из этих значений несёт свой смысл. Неправильное использование может привести к:
Ошибочным расчётам
Некорректной логике
Потерям данных
Системным ошибкам
А как вы работаете с этими значениями в своих проектах? Поделитесь опытом в комментариях!
DELETE — инструменте, который может как спасти вашу базу данных от хлама, так и превратить её в пустыню за считанные секунды.
DELETE как цифровой пылесос**, который может убрать всё, что вы ему скажете. Только вот вернуть обратно будет ой как непросто!
А пока подписывайся на мой канал На связи: SQL Там я публикую посты про особенности и нюансы SQL. Этот канал про то, как не бояться баз данных, понимать, что такое JOIN, GROUP BY и почему NULL ≠ 0. Его я веду с нуля подписчиков. Присоединяйся!
Базовый синтаксис:
DELETE FROM таблица
WHERE условие;
Можно использовать EXISTS с подзапросами
DELETE FROM orders
WHERE EXISTS (
SELECT 1
FROM customers
WHERE customers.id = orders.customer_id
AND customers.status = 'deleted' );
Удаляет заказы неактивных клиентов.
Можно использовать JOIN
DELETE o
FROM orders o
INNER JOIN customers c ON o.customer_id = c.id
WHERE c.registration_date < '2020-01-01';
Удаляет заказы клиентов, зарегистрированных до 2020 года.
Физическое удаление в SQL — это не мгновенное стирание данных с диска. Когда вы выполняете DELETE:
Система записывает операцию в журнал транзакций
Помечает удалённые строки как свободные
Физическое место может быть использовано для новых данных
Сами данные остаются в файле некоторое время
Формируется основной файл .mdf (или аналогичный). Также информация записывается в журнал транзакций. Сроки хранения удаленных данных зависят от:
- модели восстановления базы данных
- активности базы данных
- настроек резервного копирования
- объема свободного места
Удаленные данные могут храниться до следующего бэкапа или до перезаписи журнала.
Восстановление из резервной копии
RESTORE DATABASE имя_базы FROM DISK = 'путь_к_бэкапу'
Использование моментальных снимков (Snapshot)
CREATE DATABASE имя_снимка
ON (NAME = имя_файла, FILENAME = 'путь_к_файлу')
AS SNAPSHOT OF исходная_база;
Восстановление данных из снимка:
INSERT INTO исходная_таблица
SELECT * FROM снимок.dbo.таблица
Восстановление через LSN (Log Sequence Number)
Если есть журналы транзакций:
BACKUP LOG имя_базы TO DISK = 'путь_к_журналу'
Поиск удалённых записей:
SELECT [Current LSN], [Transaction ID]FROM fn_dblog(NULL, NULL)
WHERE Operation = 'LOP_DELETE_ROWS'
Либо использовать специальные посторонние программы для восстановления БД
Регулярно создавайте резервные копии
Используйте транзакции (BEGIN TRANSACTION / ROLLBACK)
Тестируйте DELETE-запросы на тестовой базе
Применяйте WHERE с осторожностью
Настройте политики резервного копирования
Многие думают, что TRUNCATE и DELETE — это одно и то же. На самом деле:
DELETE удаляет строки по одной и записывает каждую операцию в журнал транзакций
TRUNCATE мгновенно очищает таблицу, минуя журнал (кроме некоторых случаев)
При выполнении DELETE:
Блокируются только удаляемые строки
Другие транзакции могут читать незаблокированные данные
В некоторых СУБД возможна эскалация блокировок до уровня таблицы
Не затрагивает структуру индексов
Индексы остаются в прежнем состоянии
Это ускоряет процесс удаления
Не сбрасывает счётчики автоинкремента
Сохраняет текущее значение последовательности
Важно при работе с первичными ключами
Активируются триггеры AFTER DELETE
Можно отслеживать удалённые строки через виртуальную таблицу deleted
Триггеры могут отменить операцию удаления
Чтобы сохранить целостность данных можно использовать ON DELETE CASCADE
Это позволит там, где есть зависимость по внешним ключам произвести удаление зависимых строк.
UPDATE — это команда, с помощью которой мы меняем данные, уже существующие в таблице.
Не добавляем новые строки (как INSERT), не удаляем (как DELETE), а правим то, что есть.
А пока подписывайся на мой канал На связи: SQL Там я публикую посты про особенности и нюансы SQL. Этот канал про то, как не бояться баз данных, понимать, что такое JOIN, GROUP BY и почему NULL ≠ 0. Его я веду с нуля подписчиков. Присоединяйся!
Базовый синтаксис выглядит следующим образом:
UPDATE table_name
SET column1 = value1,
column2 = value2
WHERE condition;
UPDATE table_name → в какой таблице меняем данные.
SET → что именно меняем и на какие значения.
WHERE → условие, чтобы обновить только нужные строки.
Если забыть WHERE → обновятся все строки таблицы. Классический факап новичка.
Меняем телефон у клиента → UPDATE customers SET phone = '...' WHERE id = 5;
Уменьшаем остаток товара после покупки.
Обновляем статус заказа.
Проще говоря: везде, где данные живые и меняются со временем.
Можно обновлять сразу несколько строк:
UPDATE products
SET price = price * 0.9
WHERE category = 'clothes';
В этом запросе нет ограничений по количеству строк, если в таблице есть 10 строк с категорией = 'clothes', то скидка в 10% применится ко всем 10 строкам.
В UPDATE можно использовать JOIN при более сложной логике
Мы делаем обновления в таблице orders, но делаем их для тех покупателей, у которых есть признак ВИП. А признак ВИП хранится в таблице customers.
В этом запросе явно не указан JOIN, но с JOIN запрос выглядел бы вот так:
UPDATE orders o
SET amount = amount * 1.1
FROM orders o2
JOIN customers c ON o2.customer_id = c.id
Здесь больше строк, поэтому многие предпочитают первый вариант.
PostgreSQL, SQL Server → поддерживают UPDATE ... FROM.
MySQL → там другой синтаксис:
UPDATE orders o
JOIN customers c ON o.customer_id = c.id
SET o.amount = o.amount * 1.1
WHERE c.vip = true;
Т.е. в MySQL JOIN присутствует в явном виде.
RETURNING в PostgreSQL
UPDATE orders
SET status = 'done'
WHERE id = 101
RETURNING *;
Сразу получаешь обновлённые строки, без лишнего SELECT. В MySQL и SQL Server такого нет.
Deadlock при UPDATE
Транзакция А захватила строку 1 и ждёт строку 2.
Транзакция B захватила строку 2 и ждёт строку 1.
Обе ждут друг друга бесконечно.
База видит: «ну, так не пойдёт» → и убивает одну из транзакций, чтобы освободить дорогу.
При обычной блокировке ты просто ждёшь, пока другой закончит.
При deadlock оба ждут друг друга, и никто никогда не закончит — тупик.
В UPDATE можно использовать подзапросы:
UPDATE employees
SET salary = salary + 5000
WHERE department_id = (
SELECT id FROM departments WHERE name = 'IT'
);
Здесь UPDATE зависит от результата подзапроса.
UPDATE без SET
В некоторых СУБД (например, SQL Server) можно писать:
UPDATE table_name
SET column = column;
На вид — бесполезно. Но так можно снимать блокировки или триггерить AFTER UPDATE.
Можно использовать TOP и LIMIT при UPDATE
UPDATE TOP (10) users SET active = 0; -- для SQL Server
UPDATE users SET active = 0 LIMIT 10; -- для MySQL
А вот для PostgreSQL такого механизма нет - надо будет через CTE реализовывать.
UPDATE — мощный инструмент. Он нужен везде, где данные живут и меняются. Но:
всегда помни про WHERE,
учитывай блокировки,
и знай фишки СУБД (RETURNING, JOIN, LIMIT).
Напоминаю про свой канал На связи: SQL Присоединяйся для обсуждения вопросов по изучению SQL.