SQL

26 вопросов

SQL - язык запросов к реляционным БД. JOIN'ы, агрегации, CTE, оконные функции, транзакции (ACID), индексы, нормализация, N+1 проблема. Обязательные знания для любого backend-разработчика.

1 Что вернёт LEFT JOIN, если в правой таблице нет совпадений? 🟢 Лёгкий
Ответ: B) Строку из левой таблицы с NULL для полей правой

LEFT JOIN возвращает ВСЕ строки из левой таблицы. Если в правой нет совпадений - поля правой заполняются NULL. INNER JOIN вернул бы пустой результат. RIGHT JOIN - все из правой. FULL JOIN - все из обеих.

Подробнее →
2 Что означает буква I в ACID? 🟢 Лёгкий
Ответ: B) Isolation (изоляция транзакций)

ACID: Atomicity (атомарность - всё или ничего), Consistency (согласованность - данные валидны), Isolation (изоляция - транзакции не мешают друг другу), Durability (надёжность - зафиксированные данные не потеряются). Основа реляционных БД.

Подробнее →
3 Что представляет собой sql.DB в Go? 🟢 Лёгкий
Ответ: B) Пул соединений с БД

sql.DB - пул соединений, а не одно соединение. Автоматически управляет открытием, закрытием, переиспользованием. Не нужно создавать sql.DB для каждого запроса. Настройки: SetMaxOpenConns, SetMaxIdleConns, SetConnMaxLifetime. Один sql.DB на всё приложение.

Подробнее →
4 В чём разница между WHERE и HAVING? 🟢 Лёгкий
Ответ: B) WHERE фильтрует строки до GROUP BY, HAVING - после

WHERE фильтрует строки до агрегации. HAVING фильтрует результаты GROUP BY. Пример: SELECT dept, COUNT(*) FROM employees WHERE salary > 50000 GROUP BY dept HAVING COUNT(*) > 5. WHERE убирает сотрудников с зарплатой <=50к, HAVING - отделы с <=5 сотрудниками.

Подробнее →
5 Чем UNION отличается от UNION ALL? 🟢 Лёгкий
Ответ: B) UNION удаляет дубликаты, UNION ALL оставляет все строки

UNION объединяет результаты и удаляет дубликаты (требует сортировки/хеширования - дороже). UNION ALL просто конкатенирует - быстрее. Если дубликаты невозможны или допустимы, всегда используйте UNION ALL.

Подробнее →
6 Зачем нужен внешний ключ (FOREIGN KEY)? 🟢 Лёгкий
Ответ: B) Для обеспечения ссылочной целостности между таблицами

FOREIGN KEY гарантирует, что значение ссылается на существующую запись в другой таблице. Нельзя вставить заказ с несуществующим customer_id. CASCADE - автоматическое удаление связанных записей. RESTRICT - запрет удаления при наличии ссылок. SET NULL - обнуление ссылки.

Подробнее →
7 Зачем нужны инструменты миграций (goose, golang-migrate)? 🟢 Лёгкий
Ответ: B) Для версионирования и контролируемого применения изменений схемы БД

Миграции - SQL-скрипты с версиями (001_create_users.sql). Инструменты отслеживают применённые и выполняют новые по порядку. Версионирование схемы в git, воспроизведение БД с нуля, безопасное обновление production. В Go популярны goose и golang-migrate.

Подробнее →
8 Какой код уязвим для SQL-инъекции в Go? 🟢 Лёгкий
Ответ: B) db.Query("SELECT * FROM users WHERE id = " + id)

Конкатенация пользовательского ввода (+ id) - SQL-инъекция. Параметризованные запросы ($1, $2) безопасны - драйвер экранирует данные. Всегда используйте плейсхолдеры: $1 для PostgreSQL, ? для MySQL. Никогда не вставляйте данные через fmt.Sprintf в SQL.

Подробнее →
9 Чем db.QueryRow отличается от db.Query в Go? 🟢 Лёгкий
Ответ: A) QueryRow возвращает одну строку, не требует Close(); ошибка (включая sql.ErrNoRows) проверяется через Scan

QueryRow ожидает 0 или 1 строку. Не нужен Close(), ошибка возвращается через Scan(). Query возвращает sql.Rows, который нужно закрывать.

Подробнее →
10 Что такое проблема N+1 запросов? 🟡 Средний
Ответ: B) 1 запрос для списка + N запросов для деталей каждого элемента

N+1: один запрос получает список (100 заказов), затем для каждого - отдельный запрос за деталями = 101 запрос. Решение: JOIN (один запрос), IN (... список ID), или preload в ORM. В Go решается через JOIN или batch-запросы.

Подробнее →
11 Что такое CTE (Common Table Expression)? 🟡 Средний
Ответ: B) Именованный подзапрос с WITH, существующий в рамках одного запроса

CTE определяется через WITH: WITH active AS (SELECT * FROM users WHERE active) SELECT * FROM active. Улучшает читаемость, можно ссылаться несколько раз. Рекурсивные CTE (WITH RECURSIVE) позволяют обходить деревья и графы.

Подробнее →
12 Что делают оконные функции (window functions)? 🟡 Средний
Ответ: B) Вычисляют значения по набору строк без группировки

Оконные функции (ROW_NUMBER, RANK, SUM OVER, LAG, LEAD) вычисляют значения по группе строк (окну), сохраняя все строки в результате. В отличие от GROUP BY, не схлопывают строки. Пример: SUM(amount) OVER (PARTITION BY user_id ORDER BY date).

Подробнее →
13 Что такое нормализация базы данных? 🟡 Средний
Ответ: B) Организация таблиц для устранения дублирования данных

Нормализация разбивает данные по таблицам для устранения избыточности. 1NF - атомарные значения. 2NF - зависимость от всего ключа. 3NF - нет транзитивных зависимостей. Денормализация - обратный процесс для ускорения чтения.

Подробнее →
14 Как правильно откатить транзакцию при ошибке в Go? 🟡 Средний
Ответ: B) defer tx.Rollback() сразу после Begin

Паттерн: tx, _ := db.Begin(); defer tx.Rollback(); ...; return tx.Commit(). defer tx.Rollback() безопасен - после успешного Commit() Rollback() ничего не делает (возвращает sql.ErrTxDone). Это гарантирует откат при panic, ранних return и любых ошибках.

Подробнее →
15 Что вернёт SELECT COUNT(column) если column содержит NULL? 🟡 Средний
Ответ: B) Количество строк, где column IS NOT NULL

COUNT(column) считает только НЕ-NULL значения. COUNT(*) считает все строки. SUM, AVG, MIN, MAX тоже игнорируют NULL. Частая ошибка: COUNT(nullable_column) < COUNT(*). Для подсчёта NULL: COUNT(*) - COUNT(column).

Подробнее →
16 Как индексы влияют на производительность записи (INSERT/UPDATE/DELETE)? 🟡 Средний
Ответ: B) Замедляют, потому что индексы нужно обновлять при каждой записи

Каждый индекс - дополнительная структура, обновляемая при записи. INSERT добавляет записи во все индексы. UPDATE может затронуть индексы. DELETE помечает записи. 5-10 индексов на таблицу - ощутимая нагрузка на запись. Баланс: индексы ускоряют чтение, замедляют запись.

Подробнее →
17 Чем шардирование отличается от партиционирования? 🟡 Средний
Ответ: B) Партиционирование внутри одной БД, шардирование - между серверами

Партиционирование разбивает таблицу внутри одной БД (по дате, региону). Запросы и транзакции работают прозрачно. Шардирование - распределение между серверами. Даёт горизонтальное масштабирование, но усложняет JOIN, транзакции и агрегации между шардами.

Подробнее →
18 Для каких операций подходит Hash-индекс в PostgreSQL? 🟡 Средний
Ответ: B) Только оператор = (точное совпадение)

Hash-индекс поддерживает только оператор =. B-tree поддерживает =, <, >, <=, >=, BETWEEN, IN, ORDER BY, LIKE 'prefix%'. Hash может быть чуть быстрее для = на очень больших таблицах, но на практике B-tree почти всегда предпочтительнее.

Подробнее →
19 Для чего нужны SetMaxOpenConns и SetMaxIdleConns у sql.DB в Go? 🟡 Средний
Ответ: B) Управляют пулом соединений: максимум открытых и максимум простаивающих соединений к БД

sql.DB - это пул соединений. SetMaxOpenConns ограничивает общее число (по умолчанию безлимитно), SetMaxIdleConns - сколько держать открытыми в ожидании (по умолчанию 2).

Подробнее →
20 Что произойдет, если не вызвать rows.Close() после db.Query() в Go? 🟡 Средний
Ответ: B) Утечка соединения из пула sql.DB; при достижении MaxOpenConns новые запросы будут блокироваться

rows держит соединение из пула. Без Close() соединение не вернется. При MaxOpenConns это приведет к блокировке всех новых запросов.

Подробнее →
21 Когда PostgreSQL может НЕ использовать индекс? 🔴 Сложный
Ответ: D) Оба B и C

Планировщик может предпочесть sequential scan: 1) запрос возвращает >5-10% таблицы - последовательное чтение быстрее random I/O; 2) таблица маленькая - помещается в пару страниц; 3) устаревшая статистика (нужен ANALYZE). EXPLAIN покажет выбранный план.

Подробнее →
22 Когда EXISTS эффективнее подзапроса с IN? 🔴 Сложный
Ответ: B) Когда подзапрос возвращает много строк

EXISTS останавливается при первом совпадении (short-circuit). IN загружает весь результат подзапроса. При большом подзапросе EXISTS эффективнее. NOT EXISTS лучше NOT IN из-за NULL-семантики: NOT IN с NULL в подзапросе ничего не вернёт.

Подробнее →
23 Чем оптимистическая блокировка отличается от пессимистической? 🔴 Сложный
Ответ: B) Пессимистическая блокирует строку (SELECT FOR UPDATE), оптимистическая проверяет версию

Пессимистическая: SELECT ... FOR UPDATE блокирует строки до конца транзакции. Оптимистическая: читаем версию, при UPDATE проверяем WHERE version = old_version. Если кто-то изменил - повторяем. Оптимистическая лучше при редких конфликтах, пессимистическая - при частых.

Подробнее →
24 Составной индекс (a, b, c) - какой запрос НЕ будет его использовать? 🔴 Сложный
Ответ: C) WHERE b = 2 AND c = 3

Составной индекс работает по принципу leftmost prefix - запрос должен начинаться с первого столбца. (a), (a,b), (a,b,c) - индекс будет использован. (b,c) без a - не будет, потому что индекс отсортирован сначала по a. Порядок столбцов критически важен.

Подробнее →
25 Что такое phantom read? 🔴 Сложный
Ответ: B) Повторный SELECT возвращает новые строки, вставленные другой транзакцией

Phantom read: транзакция выполняет SELECT WHERE дважды и во второй раз видит новые строки от другой транзакции. Dirty read - чтение незакоммиченных данных. Non-repeatable read - та же строка изменилась. Read Committed защищает от dirty read, Repeatable Read - от phantom read.

Подробнее →
26 Что такое LATERAL JOIN в PostgreSQL? 🔴 Сложный
Ответ: B) Подзапрос, который может ссылаться на предшествующие таблицы в FROM

LATERAL позволяет подзапросу обращаться к столбцам таблиц, перечисленных ранее в FROM. SELECT * FROM users u, LATERAL (SELECT * FROM orders WHERE user_id = u.id LIMIT 3). Для каждой строки выполняет подзапрос. Мощная замена коррелированным подзапросам.

Подробнее →
🧠Квиз 🏆Лидеры 🎯Собесед. 📖Вопросы 📚База зн.