SQL Portal | Базы Данных
14.6K subscribers
829 photos
112 videos
44 files
647 links
Присоединяйтесь к нашему каналу и погрузитесь в мир баз данных

Связь: @devmangx

РКН: https://clck.ru/3H4Wo3
Download Telegram
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6
Cтатья про ACID-свойства.

Не только теория, но и практическая реализация на PostgreSQL с примерами из реальной жизни.

Этот пост отлично зайдет новичкам, которые хотят начать разбираться в базах данных, и он же нужен как база перед моей серией про внутренности PostgreSQL.

👉 @SQLPortal
Please open Telegram to view this post
VIEW IN TELEGRAM
😢😢😢

👉 @SQLPortal
Please open Telegram to view this post
VIEW IN TELEGRAM
😁20
Поток данных в Postgres для чтения/записи:

Приложение --> (опционально) пулер --> отдельный backend/клиентское соединение --> shared buffers --> кэш ОС (page cache) --> физический диск.

При чтении к кэшу ОС или диску обращается только если нужных страниц нет в shared buffers.

👉 @SQLPortal
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
This media is not supported in your browser
VIEW IN TELEGRAM
Open-source инструмент Text2SQL, который превращает запросы на естественном языке в SQL, используя понимание схемы на базе графа. Задавай вопросы к базе простым языком, а QueryWeaver сам сделает всю склейку и соберет нужный SQL.

https://github.com/FalkorDB/QueryWeaver

👉 @SQLPortal
Please open Telegram to view this post
VIEW IN TELEGRAM
5
Не используй CASE в WHERE

Один из самых частых SQL анти-паттернов это пихать CASE прямо в WHERE, чтобы разрулить условную логику для диапазонов или фильтров.

Классический пример:

SELECT * FROM products
WHERE
CASE WHEN category = 'Electronics' THEN price < 500 ELSE price < 100 END;


Работает, но часто убивает производительность, потому что CASE делает предикат не SARGable в большинстве популярных СУБД. Оптимизатор уже не может нормально сделать index seek по индексу на price (или по составному индексу (category, price)). В итоге нередко получаешь full table scan, даже если индексы отличные.

Лучше переписать так:

SELECT * FROM products
WHERE price < 100
OR (category = 'Electronics' AND price < 500);


Этот вариант лучше, потому что логика становится прямолинейной и без лишних условий. Запрос проще читать, проще поддерживать, и оптимизатору проще рассуждать о предикатах, потому что они “голые”, без обертки в логическое ветвление.

👉 @SQLPortal
Please open Telegram to view this post
VIEW IN TELEGRAM
👍92
Вопрос по SQL

Нужно получить:

- все товары дешевле 100
- плюс товары из категории Electronics дешевле 500

Какое условие корректно вернет такой результат?

A)

price < 100 AND category = 'Electronics' OR price < 500


B)

price < 100 OR (category = 'Electronics' AND price < 500)


C)

(price < 100 OR category = 'Electronics') AND price < 500


👉 @SQLPortal
Please open Telegram to view this post
VIEW IN TELEGRAM
Если используешь COUNT вместе с CASE, не ставь ELSE 0

Пример:

SELECT 
Department,
COUNT(CASE WHEN Status = 'Active' THEN EmployeeID ELSE 0 END) AS ActiveCount
FROM Employees
GROUP BY Department;


Это неправильно и даст некорректный результат. Когда ты пишешь ELSE 0, COUNT начинает считать нули как значения. COUNT игнорирует только NULL, а ноль это не NULL. Поэтому если в отделе 10 активных и 5 неактивных сотрудников, запрос вернет 15 для ActiveCount, потому что посчитает все 15 строк (10 EmployeeID + 5 нулей).

Лучше так: убрать ELSE 0:

SELECT 
Department,
COUNT(CASE WHEN Status = 'Active' THEN EmployeeID END) AS ActiveCount
FROM Employees
GROUP BY Department;


ELSE тут не нужен. Если условие не выполняется, выражение вернет NULL, а COUNT NULL-ы не считает. Еще более явно можно написать так:

SELECT 
Department,
COUNT(CASE WHEN Status = 'Active' THEN 1 END) AS ActiveCount
FROM Employees
GROUP BY Department;


Единственный случай, когда ELSE 0 будет уместен, это если вместо COUNT ты используешь SUM.

👉 @SQLPortal
Please open Telegram to view this post
VIEW IN TELEGRAM
4👍2
Какие отношения существуют между этими тремя таблицами и как вы это узнаете?

👉 @SQLPortal
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
Использование SKIP LOCKED в Postgres в запросах или процессах позволяет строить очереди задач, гарантируя, что отдельные строки обрабатываются внутри транзакции без блокировки при одновременной работе нескольких экземпляров джобы.

👉 @SQLPortal
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
Всегда добавляйте неагрегированные столбцы в GROUP BY

Это одна из тех практик, которые новички часто игнорируют, особенно если работают с базой данных, которая не навязывает строгие правила группировки, например SQLite.

Оператор GROUP BY обычно используется вместе с агрегатными функциями вроде COUNT(), SUM() или AVG(). Эти функции сводят данные, а столбцы в GROUP BY определяют, как именно данные группируются. Иначе говоря, агрегированные результаты вычисляются для каждой группы, заданной неагрегированными столбцами.

Если столбец присутствует в SELECT, но при этом не агрегируется и не включён в GROUP BY, база данных может не понимать, какое именно значение из этого столбца нужно вернуть для группы. Вот как может выглядеть плохо написанный запрос:

SELECT
Department,
EmployeeID,
COUNT(*) AS EmployeeCount
FROM Employees
GROUP BY Department;


Почему наивный подход здесь проблемный? Когда вы используете агрегатные функции, такие как COUNT(), SUM() или AVG(), SQL группирует строки по столбцам, перечисленным в GROUP BY. Поэтому каждая строка в результате представляет одну группу.

Если неагрегированный столбец присутствует в списке SELECT, но не включён в GROUP BY, движок базы данных не знает, какое значение из этого столбца должно представлять всю группу. Иначе говоря, внутри одной группы может быть несколько строк, и в каждой из них может быть разное значение этого столбца.

Если в такой ситуации ваша база данных выбрасывает ошибку, вам на самом деле повезло, потому что это заставляет исправить запрос. Но некоторые базы данных позволят такому запросу выполниться и просто вернут произвольное значение из группы. Это может привести к непоследовательным или вводящим в заблуждение результатам. Особенно опасно то, что запрос с виду работает нормально, при этом молча выдавая некорректный результат. Вот как писать правильно:

SELECT
Department,
EmployeeID,
COUNT(*) AS EmployeeCount
FROM Employees
GROUP BY EmployeeID, Department;


Теперь у нас и EmployeeID, и Department включены в GROUP BY. Это правильный способ. Добавление всех неагрегированных столбцов в GROUP BY гарантирует, что каждая строка в результате соответствует чётко определённому уровню агрегации. Это убирает неоднозначность и делает запрос логически корректным и переносимым между разными СУБД.

👉 @SQLPortal
🔥1
Избегайте использования IN с NULL

Оператор IN относится к тем конструкциям, которые легко вносят тихие баги в запрос, если использовать его неправильно. Когда вы включаете NULL в список IN, сравнение никогда не даст TRUE для части с NULL. В результате строки, содержащие NULL, не матчятся так, как многие ожидают. SQL использует трёхзначную логику: TRUE, FALSE и UNKNOWN. Сравнения с NULL не возвращают ни TRUE, ни FALSE; они возвращают UNKNOWN. Вот наивный вариант использования IN:

SELECT *
FROM Employees
WHERE DepartmentID IN (1, 2, NULL);


Поскольку NULL даёт UNKNOWN, запрос выше выполнится без ошибок, но гарантированно вернёт пустой результат.

Правильный способ обрабатывать NULL - использовать IS NULL. IS NULL явно учитывает то, как SQL работает с отсутствующими значениями. Это помогает запросу корректно различать реальные значения и неизвестные значения, что предотвращает тихие логические ошибки. Вот как этот запрос лучше писать:

SELECT *
FROM Employees
WHERE DepartmentID IN (1, 2)
OR DepartmentID IS NULL;


👉 @SQLPortal
👍8🔥2
Используйте EXISTS вместо COUNT(*) для проверки существования

Если вам нужно проверить, существует ли конкретная запись в данных, не стоит использовать COUNT() с фильтром (WHERE).

COUNT(*) заставляет базу данных посчитать все подходящие строки, прежде чем определить, есть ли вообще хоть одна строка. Из-за этого база делает лишнюю работу, особенно на больших таблицах. Как однажды пошутили: использовать COUNT() для проверки существования, это как проверять, есть ли кто-нибудь дома, по данным переписи населения.

Вот наивный вариант:

SELECT *
FROM Customers c
WHERE (
SELECT COUNT(*)
FROM Orders o
WHERE o.CustomerID = c.CustomerID
) > 0;


База данных должна посчитать все заказы для каждого клиента, прежде чем проверить условие.

Вместо COUNT() правильнее использовать EXISTS. Оператор EXISTS прекращает поиск сразу после того, как находит первую подходящую строку. Это позволяет движку БД сделать short-circuit при поиске, а сам запрос становится эффективнее и лучше масштабируется.

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

-- Создайте индекс, чтобы ускорить поиск
CREATE INDEX idx_orders_customerid
ON Orders(CustomerID);

-- Используйте EXISTS, чтобы проверить, есть ли хотя бы один заказ
SELECT *
FROM Customers c
WHERE EXISTS (
SELECT 1
FROM Orders o
WHERE o.CustomerID = c.CustomerID
);


Когда индекс уже есть, база данных может быстро найти подходящие строки в таблице Orders и остановить поиск сразу после первого совпадения. Это делает запрос заметно эффективнее, особенно при работе с большими наборами данных.

👉 @SQLPortal
👍12
🎤🎤🎤

👉 @SQLPortal
Please open Telegram to view this post
VIEW IN TELEGRAM
😁14
Можно ли восстановить данные после DELETE?

В PostgreSQL операция DELETE не стирает данные с диска мгновенно. Механизм MVCC сохраняет удалённые строки в виде dead tuples, и чтение этих dead tuples является одним из рабочих способов восстановления данных.

Однако у этого подхода есть очевидное ограничение по времени: как только autovacuum завершает очистку, dead tuples физически удаляются, и методы восстановления, основанные на файлах данных, перестают работать.

В этот момент альтернативный путь восстановления даёт WAL (Write-Ahead Log). В частности, основой этого подхода служит механизм FPW (Full Page Write) внутри WAL. Инструменты восстановления после DELETE в PostgreSQL, включая PDU (PostgreSQL Data Unloader), опираются именно на эту технику. Её ключевое свойство такое: пока файлы WAL, созданные в период удаления, всё ещё существуют, данные можно полностью восстановить независимо от того, сколько времени прошло.

В этой статье этот путь восстановления разбирается по функциям, шаг за шагом, на основе исходного кода PostgreSQL 18.

👉 @SQLPortal
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
🔥 Подписка на easyoffer PRO на 1 год со скидкой 70%

easyoffer – сайт для подготовки к собеседованию на программиста, тестировщика и другие IT-профессии становится еще доступнее со скидкой 70% до 10 марта.

⚙️ Актуальные функции:
1. База вопросов из реальных технических собеседований с вероятностью встречи и примерами ответов.
2. База задач с этапа live-coding.
3. База видеозаписей 1100+ реальных собеседований, в том числе в топовые компании (Сбер, Авито, Яндекс, WB, OZON, МТС и др.) на позиции Junior/Middle/Senior.
4. База 400+ тестовых заданий от компаний.
5. Аналитика ТОП-требований из вакансий для лучшего написания резюме по ключевым словам.
6. Тренажеры для подготовки к собеседованию. В том числе тренажер «Реальное собеседование» со сценарием вопросов под конкретную компанию.

Акция до 10 марта (включительно) на PRO-тариф.
– Подписка действует 1 год
– Доступ ко всем профессиями сразу

👉 Смотри подробности тарифа и покупай на easyoffer
🔥1
Аргумент «SQL не масштабируется» — это ленивое и абсурдное обобщение.

Настоящий вопрос не в том, масштабируется SQL или NoSQL. Вопрос в том, соответствует ли база данных вашим требованиям. Если какая-то функция критична для вашего кейса и есть только в одной базе данных, будете ли вы выбирать что-то другое? Конечно нет.

Любая база данных и масштабируется, и не масштабируется одновременно. Всё зависит от того, какой use case вы на ней строите. Если у вас 100 запросов в секунду, зачем вообще заморачиваться с шардингом? Одного инстанса на одном узле будет более чем достаточно.

Масштабируемость — лишь один из факторов при выборе базы данных, а не единственный. Всегда смотрите на ключевые свойства, потребности и требования вашего кейса, а затем выбирайте подходящую БД. Но будьте готовы жить с её недостатками и ограничениями.

Если кто-то говорит вам, что SQL не масштабируется, спросите, что именно он имеет в виду.

👉 @SQLPortal
Please open Telegram to view this post
VIEW IN TELEGRAM
👍31
This media is not supported in your browser
VIEW IN TELEGRAM
Как создать трекер прогресса в Excel

👉 @SQLPortal
Please open Telegram to view this post
VIEW IN TELEGRAM
6👍3
Твой индекс в MySQL не медленный, он просто фрагментирован :)

Базы данных вроде MySQL, которые хранят данные в B+-деревьях, страдают от фрагментации индексов, и это серьезно влияет на производительность. Сейчас объясню.

Фрагментация индекса возникает, когда страницы индекса B+-дерева содержат значительное количество свободного пространства вместо плотного размещения данных. Но почему так происходит?

В MySQL движок InnoDB хранит данные в кластеризованных индексах (организованных по первичному ключу). Когда кластеризованный индекс фрагментируется из-за случайных вставок по первичному ключу (например, UUID), производительность чтения данных ухудшается.

Поскольку движок старается сохранять порядок листовых узлов, при вставке строки в середину происходит split страницы. Разделение страницы выполняется, когда в ней недостаточно свободного места или превышен порог split’а. Со временем повторяющиеся случайные вставки вызывают всё больше таких разделений, из-за чего тратится лишнее пространство.

Фрагментация индекса напрямую влияет на производительность запросов и использование памяти. Когда индексы фрагментированы, базе данных приходится читать больше страниц с диска для выполнения того же запроса, что увеличивает количество I/O-операций и снижает пропускную способность.

Кстати, при последовательных вставках фрагментация минимальна, поскольку InnoDB просто создаёт новые страницы без разделения существующих и размещает их вдоль крайнего правого пути дерева. Это помогает сохранять оптимальную плотность страниц.

Управлять фрагментацией индексов можно, настроив параметр innodb_fill_factor и/или выполнив следующий запрос:

ALTER TABLE tbl_name FORCE;


Надеюсь, было интересно :)

👉 @SQLPortal
Please open Telegram to view this post
VIEW IN TELEGRAM
2