PostgreSQL дає транзакційний DDL, багату систему типів (масиви, діапазони, справжні ENUM-типи), суворі правила GROUP BY і порівняння типів, RETURNING та екосистему розширень; MySQL фіксує кожен DDL негайно, мовчки приводить типи й порівнює рядки без урахування регістру.
SQL для Middle: питання на співбесіду
8 питань рівня Middle з теми SQL з розгорнутими відповідями, порадами та перевіркою.
Віконна функція рахує агрегат або позицію по групі рядків (вікні), не згортаючи їх у один рядок: ROW_NUMBER і RANK дають нумерацію всередині PARTITION BY, LAG бере значення попереднього рядка, SUM() OVER (ORDER BY …) дає running total — усе це замінює корельовані підзапити й самоджойни одним проходом.
Дивіться на тип доступу до таблиці, оцінку рядків проти фактичної кількості і найдорожчі вузли плану; розбіжність оцінки й факту вказує на застарілу статистику.
OFFSET не вміє «стрибати»: база читає й відкидає всі пропущені рядки, тому час зростає лінійно з номером сторінки. Keyset (seek method) замість номера сторінки передає ключ останнього прочитаного рядка й читає рівно LIMIT записів за постійний час.
CTE - це іменований набір рядків у межах одного запиту: WITH розкладає складну логіку на читабельні кроки, WITH RECURSIVE обходить ієрархію довільної глибини. З PostgreSQL 12 CTE з одним посиланням вбудовується в план, тож барʼєром оптимізації він більше не є за умовчанням.
Складений індекс працює за принципом лівого префікса: індекс (a, b, c) допомагає умовам по a, по a і b, по a, b і c, але не по b чи c окремо.
У PostgreSQL зберігайте jsonb і індексуйте його GIN для пошуку по довільних ключах або B-tree на виразі `(data->>'key')` для конкретного поля; у MySQL індекс на JSON можливий лише через згенеровану колонку, функціональний індекс (8.0.13+) або multi-valued index (8.0.17+). Помилка — тримати в JSON поля, які є в кожному рядку і за якими фільтрують чи джойнять: там потрібні звичайні колонки з типом, NOT NULL і зовнішнім ключем.
З боку бази N+1 виглядає не як повільний запит, а як один нормалізований запит із величезним calls і мікроскопічним mean_exec_time; лікується збором ключів і другим запитом з IN або = ANY, а JOIN підходить лише там, де він не множить рядки.