# #21: Введение в базы данных: История и SQL

- Выпуск: 21 · Сезон: 1 · Дата: 2023-06-12 · Длительность: 39:41
- Страница: https://apkhmv.xyz/podcast/episode-21/
- Аудио: https://traffic.libsyn.com/secure/173caf0e-b8e8-4056-8a73-2d60d94118ef/21_into_into_databases.mp3
- Ведущий: Александр Пахомов (https://apkhmv.xyz/people/apkhmv/index.md) · Гости: нет
- Темы: базы данных, SQL, история технологий, NoSQL, реляционные СУБД
- Расшифровка: draft · Источник: речь участников выпуска, цитируется как есть

## Кратко

Первый выпуск второго сезона, посвящённого базам данных. Александр Пахомов объясняет, почему глубокое, а не поверхностное понимание баз данных важнее знания конкретного языка, фреймворка или СУБД, прослеживает историю от реляционных систем 70-х через NoSQL и NewSQL до облачных и shared-disk-решений, а затем освежает ключевые концепции SQL: `SELECT`/`WHERE`, агрегаты, `GROUP BY`/`HAVING`, `JOIN`, оконные функции и common table expressions.

## Главное

- Глубокое понимание алгоритмов и внутреннего устройства баз данных ценнее знания конкретного языка, фреймворка или СУБД: подход language-agnostic масштабируется до framework-agnostic и database-agnostic.
- Реляционные базы данных появились в середине 70-х, победили конкурентов к 80-м (Oracle, Ingres → Postgres, Informix, DB2), а в 90-х начался их бум (MS SQL Server, MySQL, Postgres, SQLite).
- `NoSQL` расшифровывается как Not Only SQL; такие системы (документные, key-value) жертвовали схемой и `ACID`-транзакциями ради масштабируемости в эпоху интернет-бума 2000-х.
- NewSQL-системы 2010-х (Google Spanner, CockroachDB, YugabyteDB, Apache Ignite 3) совмещают распределённость, `SQL` и полноценные транзакции.
- В shared-disk-архитектуре (Spark, Snowflake, Redshift, Impala) файловая система (`HDFS`, `S3`) — общая абстракция, а масштабируется отдельный слой compute; это противопоставлено shared-nothing.
- `SQL` — не язык программирования, а стандарт: базовый уровень совместимости — `SQL-92`, последний принятый стандарт — 2016 года, стандарт 2023 добавляет графовые запросы.
- Без явных `DISTINCT` или `ORDER BY` результат `SQL`-запроса не является множеством и не отсортирован — в нём могут быть дубли; сортировка по умолчанию замедлила бы и чтение, и вставку.
- `WHERE` фильтрует строки до агрегации, `HAVING` накладывает предикат уже на результат агрегации; рекурсивные CTE (`WITH RECURSIVE`) по сути задают рекурсивную функцию.

## Главы

- [00:20](https://apkhmv.xyz/podcast/episode-21/?t=20) Вступление: второй сезон про базы данных
- [01:48](https://apkhmv.xyz/podcast/episode-21/?t=108) Базы данных на собеседовании — сложный топик
- [05:58](https://apkhmv.xyz/podcast/episode-21/?t=358) language-, framework- и database-agnostic
- [11:50](https://apkhmv.xyz/podcast/episode-21/?t=710) История баз данных: 60-е — 90-е
- [15:01](https://apkhmv.xyz/podcast/episode-21/?t=901) NoSQL и Not Only SQL
- [16:12](https://apkhmv.xyz/podcast/episode-21/?t=972) NewSQL: распределённость плюс транзакции
- [17:05](https://apkhmv.xyz/podcast/episode-21/?t=1025) Облако и shared-disk-системы
- [20:05](https://apkhmv.xyz/podcast/episode-21/?t=1205) Графовые и time series базы данных
- [22:04](https://apkhmv.xyz/podcast/episode-21/?t=1324) Что их объединяет: SQL
- [23:20](https://apkhmv.xyz/podcast/episode-21/?t=1400) SQL как стандарт и основы запросов
- [28:54](https://apkhmv.xyz/podcast/episode-21/?t=1734) GROUP BY, HAVING, функции и их причуды
- [35:05](https://apkhmv.xyz/podcast/episode-21/?t=2105) ORDER BY, LIMIT, оконные функции, CTE
- [38:42](https://apkhmv.xyz/podcast/episode-21/?t=2322) Итог и анонс следующего выпуска

## Ссылки

- [Apache Ignite — распределённая база данных](https://ignite.apache.org)
- [CockroachDB — distributed SQL database](https://www.cockroachlabs.com)
- [Google Cloud Spanner](https://cloud.google.com/spanner)
- [Neo4j — графовая база данных](https://neo4j.com)
- [Apache Hadoop (HDFS)](https://hadoop.apache.org)

## Похожие выпуски


- [#16: Спэшл: RBAC](https://apkhmv.xyz/podcast/episode-16/index.md) — общие темы: базы данных
- [#22: Архитектура баз данных: компоненты и классификация](https://apkhmv.xyz/podcast/episode-22/index.md) — общие темы: базы данных
- [#23: SSD и HDD: устройство дисков и слотированные страницы](https://apkhmv.xyz/podcast/episode-23/index.md) — общие темы: базы данных
- [#24: Лучшая структура данных: B-tree, B+tree](https://apkhmv.xyz/podcast/episode-24/index.md) — общие темы: базы данных

## Расшифровка


**[00:20]** Здорово! Меня зовут Саша Пахомов, и я инженер, который любит своё дело. Вы слушаете подкаст «Тысяча фичей», и это второй сезон — про базы данных. Пройдя вместе со мной через все выпуски этого сезона, вы поймёте, как на самом деле устроены современные базы данных, почему их так много и как понять, какую базу данных выбрать. Ваш покорный слуга сам разрабатывает одну из таких систем и является коммитером в её open-source-версию. Чтобы писать такой узкоспециализированный код, мне просто необходимо изучать алгоритмы, индексы, сетевое взаимодействие, форматы хранения данных, распределённые транзакции и так далее. А имея подкаст, я подумал: почему бы не осветить процесс изучения здесь? Так что усаживайтесь поудобнее — или сделайте глубокий вдох, если вы гуляете или бегаете. Мы начинаем изучать базы данных. Поехали!

**[01:43]** Всё, теперь точно начинаем.

**[01:48]** Представьте, что вы на стандартном собеседовании — бэкендером или на какую-то другую техническую должность, инженерным менеджером, неважно. Вы, как обычно, знакомитесь, представляетесь, рассказываете о себе, о своём опыте, о том, чем занимались в последнее время, какие задачи на работе решали. Потом, если собеседование на программиста, вы, скорее всего, переходите к обсуждению основ языка программирования, каких-то тонкостей, плавно двигаетесь к современным фреймворкам — что вы используете и в каких ситуациях. В общем, такое стандартное собеседование. И в какой-то момент почти любое собеседование переходит в фазу обсуждения баз данных, или SQL: каждый называет это по-разному, но суть одна. Мы начинаем говорить про транзакции, про распределённые транзакции, про консенсусы, алгоритмы, про уровни изоляции, которых можно достичь.

**[02:44]** И в этот момент у большинства — а раньше иногда и у меня — начинаются небольшие просадки. Потому что база данных, давайте честно, — довольно сложный топик, и к нему не подготовиться вот так быстро: просмотреть статейку на Хабре про Spring, основные вопросы, как устроен transaction, что такое компонент, что такое бин. Нельзя вот так быстренько пробежаться, понять, как работают базы данных, и потом нормально про них разговаривать на собеседовании. Да, можно изучить какие-то базворды — типа ACID, — как-то их объяснить, потом прочитать про CAP-теорему и её тоже как-то объяснить, но это объяснение не содержит под собой никакой глубины. Пара-тройка уточняющих вопросов или заход с другого боку очень часто приводит к тому, что человек просто не понимает, как это работает. В голове нет представления об этом фундаменте: как устроен, например, Multiversion Concurrency Control в транзакциях, что такое уровень изоляции snapshot и чем он отличается от serializable или repeatable read. Тут недостаточно знать терминологию — нужно иметь в голове представление о том, как это действительно работает. И тогда начинается настоящее инженерное понимание систем.

**[04:05]** А ведь подобные системы мы и на работе потом строим: наши сервисы, наши тулы так или иначе в какой-то момент начинают вести себя как базы данных, использовать те же структуры и подходы. И проблемы, с которыми сталкиваются разработчики баз данных, — те же, с которыми сталкиваются обычные прикладные программисты. Поэтому я искренне считаю, что понимание алгоритмов — именно глубокое понимание того, как устроены базы данных, какие в них есть процессы, как строится план запроса, какие файлы, — очень важно. Именно поэтому я решил сделать второй сезон подкаста «Тысяча фичей», в котором попытаюсь разобраться сам и объяснить вам, мои дорогие слушатели, как всё-таки работают современные базы данных. Понять это можно, но процесс долгий — нельзя просто взять и подготовиться быстро. Сначала нужно изучить какие-то структуры данных — деревья, хэш-таблицы, списки и так далее, — чтобы на основе этого знания построить понимание индексов. Именно этим мы и будем заниматься.

**[05:20]** Так что если вы совсем новичок и не понимаете, как парсится запрос, что такое план запроса и чем хэш-индекс отличается от сортированного индекса, — вообще не парьтесь. Мы здесь именно за тем, чтобы всё это рассмотреть. Ну а если вы профессионал, который всё это знает и, может быть, сам собеседует людей, — думаю, вам тоже будет интересно, потому что я как-никак разрабатываю базу данных и иногда сталкиваюсь с проблемами, которыми хочется поделиться.

**[05:58]** Итак, я немного отошёл от темы. Есть такая мысль, к которой я пришёл сам: даже не говоря о базах данных — просто знание основ программирования, computer science. Не языка, а именно алгоритмов и структур данных: оно самое необходимое, без него никуда. Какой язык программирования вы изучаете или используете сейчас на работе, в какой-то момент перестаёт быть определяющим фактором. Типа, я пишу на Java, но в целом могу писать на любом другом языке и иногда это делаю. Язык программирования — это инструмент. А знание основ, структур данных, алгоритмов, основных проблем хранения и представления данных на диске, передачи по сети, сериализации-десериализации — это вообще language-agnostic-штука. Понимание этих вещей, как мне кажется, значит гораздо больше, чем знание тонкостей конкретного языка: в моменте оно вам нужно, а потом не пригодится.

**[07:11]** Если подняться с уровня языка на уровень фреймворков — ситуация абсолютно такая же. Фреймворки разработаны другими программистами, чтобы облегчить какую-то боль. Понимание этих болей — то есть ограничений, с которыми мы сталкиваемся, когда пишем код, — важнее знания конкретного фреймворка. Например, dependency injection, inversion of control, контейнер: эта парадигма пришла из-за того, что людям было сложно управлять зависимостями. Они пришли не к Spring как таковому, а к подходу: наверное, удобнее управлять зависимостями из одного места, а не плодить их по всему коду. А Spring — лишь одна из реализаций этого подхода на Java. Но мы не Spring'ом единым: есть много реализаций dependency injection — Micronaut, Quarkus и другие. Я сейчас, например, пишу не на Spring, а на Micronaut, и переход с одного на другой занял у меня примерно ноль секунд — там даже аннотации одни и те же, стандарт один. Да, есть тонкости и различия в том, как фреймворки работают, но суть у них абсолютно одна.

**[08:35]** Поэтому понимание проблемы dependency injection намного важнее, чем знание того, как работает аннотация `@Autowired` в Spring или `@Inject` в Micronaut. Да почти одинаково они работают. И эти тонкости изучаются быстро — можно спросить у ChatGPT, он объяснит, тут же проверишь; Copilot вообще всё подскажет. Со временем ценность знания конкретного инструмента уходит, а понимание глубокой концепции остаётся с вами навсегда. Тонкости реализации инструментов меняются, а концепции — нет; только новые приходят на основе предыдущих.

**[09:16]** То же самое поднимается на уровень выше — на базы данных. Знать тонкости одной конкретной базы данных круто, но подход language-agnostic и framework-agnostic точно так же масштабируется на database-agnostic. Выбирать между Snowflake и Databricks для аналитического хранилища, или ClickHouse, или думать про Postgres, Oracle — да, нужно понимать, какую базу данных выбрать, где какая лучше. Но это понимание строится не из того, что вы знаете внутрянку Oracle и внутрянку Postgres. Наоборот, оно вам будет мешать: если вы хорошо знаете Oracle, то возьмёте Oracle, потому что будете думать, что Postgres хуже во всём, — что на самом деле не так. А вот понимание верхнеуровневых концепций, основных проблем и решений, которые база данных использует, как мне кажется, намного важнее. Именно за этим я делаю второй сезон — чтобы было это понимание, эта база.

**[10:42]** Ещё одна мысль: собеседования так или иначе всегда касаются баз данных, и в частности — system design. Эта модная часть собеседования тоже упирается в базы данных: базы данных, очереди, CDN. Мы дизайним систему, частью которой стопроцентно будет одна или несколько баз данных. И глубокое понимание того, как они устроены внутри, во-первых, поможет выбрать базу данных под конкретное решение здесь и сейчас. А во-вторых, большие системы издалека очень похожи на то, как устроены внутри сами базы данных, — поэтому знание внутренних концепций помогает сделать правильный system design, потому что вы будете судить о системе глубже и прагматичнее. Это, опять же, моё личное мнение. Вот такой питч — почему стоит изучать, как работают базы данных, и почему этот подкаст вообще имеет смысл. А теперь давайте наконец начнём.

**[11:50]** Начать я хотел бы очень плавно и не спеша, потому что понимаю: аудитория разная. Кто-то студент, кто-то только изучает, а кто-то уже прошаренный сеньор-помидор, individual contributor, который все эти базы данных уже вертел. Но, мне кажется, такое рассмотрение — взгляд назад и плавное погружение — полезно абсолютно для всех. Иногда, когда тебе кажется, что ты всё знаешь, ты вдруг ловишь какую-то мысль и уже по-другому смотришь на технологию. Я за собой это часто замечаю. Давайте плавно знакомиться с миром баз данных — и начнём с их истории.

**[12:30]** Как вы считаете, когда вообще были придуманы базы данных? Началось всё ещё в 60-х годах, когда программисты на COBOL уже сталкивались с проблемами хранения данных и решили как-то это стандартизировать, чтобы не изобретать каждый раз велосипед. Какое-то время ушло на подбор разных реализаций и стандартов, но в середине 70-х начали появляться реляционные базы данных. Именно они показали себя самым прагматичным и удобным для того времени решением, а в 80-х уже победили окончательно. Были и другие, нереляционные модели, но вспоминать о них, наверное, нет смысла — это было давно, и реляционная модель победила. Так вот, в 80-х появился знакомый нам Oracle. Тогда же был Ingres, на основе которого потом построят Postgres, — именно оттуда и пошло название. И ещё несколько решений, о которых вы, возможно, уже не знаете: Informix, DB2, InterBase, Teradata. По сути, из того, что на слуху, с тех времён остался только Oracle.

**[13:51]** В 90-х начался прямо бум — все начали писать базы данных, обязательно реляционные, обязательно на одной машине. Это, естественно, Microsoft SQL Server, MySQL, Postgres, SQLite. Даже те базы данных, которые мы сейчас используем как дефолт — «не знаешь, какую базу выбрать, бери PostgreSQL», — уже тогда, в 90-х, начали закрепляться, разрабатываться и активно использоваться. Потом случаются 2000-е — и происходит интернет. Он обретает широкое распространение, так называемый интернет-бум, и наступает эра big data, data warehouse, всяких Greenplum, MapReduce, Hive, HDFS. В какой-то момент данных, которых в 90-х вполне хватало одной машине, стало слишком много: все начали собирать всё подряд, строить warehouse, и обычных реляционных баз данных в стандартном понимании перестало хватать. К тому же диски тогда были довольно медленными.

**[15:01]** И начали появляться NoSQL-системы. Когда вас спрашивают, что такое SQL и NoSQL, ожидают примерно такой ответ: NoSQL расшифровывается как Not Only SQL — «не только SQL». Раньше — да и сейчас — под SQL понимали реляционные базы данных, а NoSQL — это всё, что нереляционное. Разделение это, как мне кажется, не очень правильное, но давайте считать, что NoSQL — это нереляционная система. У них, как правило, отсутствовала схема, они предоставляли кастомный API, были масштабируемыми и чаще всего документными либо key-value — то есть по-другому представляли форматы данных. Но главный их недостаток, и тогда, и сейчас, — они не поддерживали ACID-транзакции. То есть нельзя было атомарно выполнить логику, задействующую несколько таблиц; нормальных транзакций с нормальным уровнем изоляции просто не было. И это была большая проблема.

**[16:12]** Дальше, если идти в 2010-е — 2015-й, 2017-й, вплоть до нашего времени, — это эра так называемых NewSQL-систем. Опять же, все эти обозначения довольно условны, но суть в том, что такая система как бы говорит: «Мы как NoSQL, но при этом мы и SQL, и транзакции поддерживаем». То есть они и реляционную модель поддерживают, и являются распределёнными, и предоставляют альтернативные клиенты, и умеют в транзакции. Это такие системы, как Google Spanner, CockroachDB, YugabyteDB, — и их ещё очень-очень много. Ignite 3, который я разрабатываю, — одна из таких систем. Они умеют масштабироваться, поддерживают нормальные транзакции и поддерживают SQL.

**[17:05]** Помимо этого мы сейчас наблюдаем бум облачных систем — он, наверное, даже уже прошёл. Это Google Spanner, Amazon DynamoDB, Redshift, Snowflake, Aurora. Все они предоставляют базу данных как сервис. Ведь база данных — это не просто хранилка для записи и чтения данных; это система, которую нужно сопровождать, администрировать, поддерживать: если упала — поднимать, смотреть логи, обновлять. Это очень сложная система, и её maintenance требует нанимать отдельных людей — DBA, которые за базой данных следят. А это стоит денег и экспертизы. И есть другое решение — облако: ты просто платишь деньги, тебе предоставляют интерфейс и какие-то гарантии — что данные не потеряются, что доступность будет условные пять девяток, — а ты пользуешься как обычный пользователь базы данных. Что на самом деле прикольно.

**[18:14]** Ещё есть так называемые shared-disk-системы. До этого мы плюс-минус обсуждали базы данных, которые хранят данные в файлах где-то у себя на диске и через Unix API читают и пишут в них; даже если они распределённые, у каждой ноды свои файлы — это shared-nothing-системы. А в shared-disk файловая система превращается в абстракцию, поверх которой работают распределённые compute-ноды. То есть распределённая часть — это compute, который может масштабироваться, а файловая система — единый пласт, с которым мы работаем только через API. Например, это может быть HDFS — довольно старая распределённая система, предоставляющая файловый API: можно класть невероятное количество файлов, и оно всё будет пережёвываться. Такой бездонный диск в интернете. Более современный бездонный диск — например, S3 от Amazon или аналоги от Azure и Google.

**[19:34]** Идея в том, что файловая система — это шеринговая абстракция, а поверх неё происходит compute, строятся какие-то индексы. По сути, это база данных, только с распределённой файловой системой. Такие системы — например, Apache Spark, Druid, Snowflake, Amazon Redshift, Cloudera Impala — тоже начали распространяться примерно в десятых годах.

**[20:05]** Тогда же появились графовые базы данных. Самая популярная, наверное, — Neo4j, по крайней мере, о ней я слышу больше всего, но вообще их очень много. Нужны они в достаточно редких случаях. Честно говоря, я в жизни графовую базу данных руками на проде не трогал: смотрел демки, изучал язык запросов, но проектировать под эту модель не приходилось. Хотя вполне представляю юзкейсы, когда мы действительно храним графы — вершины и связи между ними — и нам нужно производить поиск, агрегацию, смотреть в этот граф. Если интересно — поизучайте, тема прикольная, и они всё ещё живы. Стоит отметить, что новый стандарт SQL, по-моему 2023 года, добавляет графовую часть в API: можно делать графовые запросы прямо в SQL. Правда, выглядит это не очень удобно, и вряд ли кто-то будет этим пользоваться.

**[21:11]** И последняя группа баз данных, появившихся примерно в то же время, — time series базы данных: TimescaleDB, VictoriaMetrics, InfluxDB, ClickHouse. Как правило, они оптимизированы под хранение метрик или time series данных — условно, данные с какой-нибудь вышки или из Internet of Things, которые постоянно шлют показатели: вот дата, вот момент времени, вот такие значения. Все эти показатели мы скидываем в базу и потом делаем по ним агрегирующие запросы. Достаточно понятный use case и понятные под него решения — а-ля колоночные базы данных; они тоже имеют место быть и получили распространение.

**[22:04]** Но что объединяет все эти базы данных? Из тех, что выжили и сейчас представлены на рынке, почти все — за некоторыми исключениями вроде MongoDB — поддерживают SQL. То есть на популярный вопрос, какой язык программирования изучить новичку, есть непопулярный ответ: SQL. Допустим, вы начали изучать Kotlin: вам безумно облегчили порог входа, убрав невыносимо сложные конструкции из Java вроде `public static void main(String[] args)`. Но чем дольше вы изучаете программирование, тем больше понимаете, что это не ваше, и в конце концов бросаете. Знание Kotlin больше никогда не пригодится. А вот если бы вы изучали SQL, его знание пригодилось бы, даже если вы решите стать аналитиком, продактом или ещё кем-то. Кроме шуток, SQL — самый старый язык, прошедший проверку временем и актуальный до сих пор. Почти все базы данных в какой-то степени поддерживают SQL-интерфейс, и это неспроста. Давайте освежим память и вспомним основные концепции SQL, чтобы плавно погрузиться в мир алгоритмов баз данных.

**[23:20]** Итак, SQL — вообще говоря, такого языка программирования нет. Когда я говорил «первый язык программирования», я немного слукавил: это всё-таки стандарт, который ввели ещё в 1986 году и развивают до сих пор. Последний стандарт — по-моему, 2016 года, а 2023-й на подходе. Этот стандарт специфицирует ключевые слова и результат выдачи по ним, если очень коротко. База данных может называть себя SQL-compatible, если поддерживает стандарт 1992 года — SQL-92. Реляционные базы данных так или иначе все поддерживают SQL-92, но до некоторой степени, до определённых деталей — об этом поговорим чуть позже. Мы не изучаем весь SQL, это не урок по языку запросов; это просто освежение памяти и вводный вокабуляр, чтобы потом было понятно, откуда ноги растут.

**[24:22]** Стандартно, когда мы говорим про SQL, мы имеем в виду наличие таблиц. Скажем, есть таблица студентов — с идентификатором, именем, логином; есть курс, который студент может посещать, — у него тоже есть id и имя. А та самая реляционная часть — это таблица, которая их соединяет: студент ходил на курс, и в таблице хранится id студента, id курса и, может быть, оценка, если был экзамен. Такая стандартная простенькая реляционная модель. Из неё мы можем делать следующие вещи. Язык создания таблиц — `CREATE TABLE`, `ALTER TABLE`, `DROP TABLE`, так называемый Data Definition Language, — мы сейчас опустим. С помощью SQL мы можем создавать таблицы, изменять их, менять форматы колонок, добавлять констрейнты, определять связи. Но в контексте понимания того, как работают базы данных, язык манипуляции схемой не так важен — язык запросов намного важнее. С него и начнём.

**[25:28]** Какой самый банальный SQL-запрос можно написать? Наверное, `SELECT * FROM какая-то_таблица` — просто прочитать таблицу; звёздочка значит, что мы читаем всё. Дальше мы можем сделать фильтрацию — добавляется ключевое слово `WHERE`, где описываем предикат, простой или составной: берём какие-то колонки, вызываем функции и проверяем. Например, возраст больше 18 или дата рождения лежит в каких-то пределах. Здесь поле для фантазии — и поле того, какие функции в `WHERE` поддерживает каждая база данных. Этому можно посвятить целый выпуск: какие-то функции есть в стандарте, а какие-то поддерживаются только в конкретных базах, но идём дальше.

**[26:21]** Также мы можем агрегировать данные. За агрегацию отвечают агрегирующие функции — всякие `AVG`, `MIN`, `MAX`, `SUM`, `COUNT`. В самом простом случае это `SELECT COUNT(*) FROM table` — так мы посчитаем количество всех строк в таблице. Или можем посчитать средний возраст студентов, указав `AVG(age)`. Стоит отметить, что в схеме `SELECT AVG(age) FROM table WHERE ...` — без `GROUP BY` и `HAVING` — агрегат считается по всей таблице, абсолютно по всем строкам. Ещё мы можем исключить дубли при построении агрегата с помощью ключевого слова `DISTINCT`. Например, если мы считаем людей старше 18, то каждое вхождение посчитается столько раз, сколько их есть: если у нас 20 человек в возрасте 19 лет, мы посчитаем их 20 раз. А если нужно посчитать количество уникальных возрастов, используем `DISTINCT`.

**[27:41]** И вообще в SQL очень важно понимать: все эти кортежи, выборки, таблицы и результаты, с которыми мы работаем, — если мы явно не указали `DISTINCT` или сортировку, — не являются множествами. То есть в них могут быть (и, скорее всего, будут) дубли, и они не отсортированы: мы не сортируем данные by default. Это важно понимать, потому что иногда приводит к недопониманиям. Как разработчик баз данных скажу: если бы by default всё было sorted, жизнь была бы намного хуже. Сортировка — операция очень затратная с точки зрения computation-ресурсов; запросы были бы медленнее, если бы мы всегда возвращали отсортированное, даже когда об этом не просят. И вставка в отсортированное множество тоже страдала бы: мы могли бы хранить отсортированные структуры, но тогда при вставке нужно искать место, куда вставить, — а это нетривиальная задача. Но об этом будем говорить дальше.

**[28:54]** Помимо `DISTINCT`, в агрегатах есть ключевые слова `GROUP BY` и `HAVING`. `GROUP BY` мы используем, когда хотим сгруппировать множество по какому-то полю. Например, посчитать не среднее по всему университету — ведь таблица `students` хранит всех студентов всех времён, — а среднее по годам: какой средний возраст был у студентов за 2020-й, за 2021-й, за 2022-й, за 2023-й. Чтобы указать этот год, мы используем `GROUP BY`. Рядом с `GROUP BY` всегда идёт в догонку вопрос про `HAVING`. Всё, что нужно понимать: это тот же `WHERE`, где мы указываем предикат, но работает он после агрегации. В `WHERE` мы можем отфильтровать, например, по возрасту — берём только несовершеннолетних, до 18, — и уже по ним делаем агрегацию, скажем, среднего балла в школе. То есть фильтрация по `WHERE` происходит до агрегации: средний балл посчитается только среди тех, кто действительно младше 18. А если мы хотим отфильтровать по результату агрегата — например, вывести средний балл, но не больше четырёх (посмотреть всех троечников), — вот это условие помещается в `HAVING`, потому что `HAVING` смотрит уже на результат агрегации и накладывает предикат на него. Надеюсь, объяснил просто; но если действительно интересно, можно погуглить или спросить ChatGPT — там всё понятно объясняется.

**[30:48]** Есть ещё всякие функции, которые мы можем вызывать в предикате, — например, конкатенации строк. И вот в функциях начинаются различия между базами данных, потому что каждая поддерживает семантику и синтаксис как ей нравится. Из курьёзов: в MySQL сравнение строк по умолчанию case-insensitive — большая буква или маленькая для него не имеет значения. Хотя в стандарте SQL-92 написано ровно наоборот — case-sensitive, и чтобы сравнить строки case-insensitive, нужно использовать `UPPER` или `LOWER`. А MySQL говорит: «Да, они у нас будут равны». Не знаю, может, они с этим уже исправились, но такая приколюшка есть.

**[31:39]** Также есть ключевое слово `LIKE` — не функция, а оператор, — которое мы тоже используем в предикате. И в `LIKE` есть забавная штука: мы можем матчить строки. Например, хотим всех, у кого имя начинается на `s`, — указываем `s%`. То есть `%` — это любая подстрока: `s%` значит всё, что начинается с `s`, включая само `s`. А нижнее подчёркивание — это один элемент: `s_` означает `s` и ровно один символ за ним. Непонятно, почему нижнее подчёркивание не сделали точкой, а процент — звёздочкой, как в Unix и в регулярных выражениях. Мне кажется, это просто продолб комитета по составлению стандарта; менять его уже никто не будет, но прикол такой.

**[32:32]** Помимо строковых операторов и `LIKE`, мы можем конкатенировать строки — и здесь тоже прикол. В стандарте написано, что конкатенация строк происходит с помощью оператора из двух пайпов — `||`, две вертикальные черты. Почему два пайпа? Я даже представить не могу. Естественно, некоторые разработчики баз данных тоже повозмущались и решили: да блин, конкатенация — это плюс, — и взяли плюс. MS SQL, например, поддерживает `+`. А MySQL поддерживает функцию `CONCAT`: ты должен вызвать функцию и передать в неё строки. Короче, тут тоже начинаются различия — забавно, но вот такой оператор, `||`. А с датой и временем вообще тёмный лес: каждый реализует свои date-time-функции по-своему, стандарт говорит одно, база данных — другое. Просто пропустим.

**[33:43]** Есть ещё ключевые слова вроде вставки — `INSERT`, — и так называемый output redirection: когда мы хотим `SELECT` что-то и указали таблицу. Но чаще всего это выглядит как `CREATE TABLE`: указываем имя таблицы, а дальше в подзапросе — `SELECT` из какой-то другой таблицы, и на основе его результата строим новую таблицу. Так логичнее и понятнее. Тут мы уже коснулись подзапросов: их можно использовать в `CREATE TABLE`, в common table expressions, о которых поговорим позже, в `WHERE` и даже в `JOIN`. Кстати, про `JOIN` — есть такое ключевое слово: мы указываем два ключа и соединяем две или более таблиц. Если хотим соединить три таблицы, нужно два последовательных `JOIN`. Про `JOIN` я, возможно, потом расскажу отдельно, но суть в том, что мы соединяем таблицы по какому-то предикату — на самом деле любому. Какие они бывают: inner, left, right, outer join. Их там четыре вида. Чтобы объяснить это в подкасте, надо изгаляться, — может, потом сделаю отдельную рубрику, а сейчас мы просто обозреваем SQL.

**[35:05]** Что у нас ещё есть? Есть `ORDER BY` — когда мы хотим отсортировать результат по какому-то полю: можем сказать `ORDER BY ... ASC` или `DESC`, то есть в порядке от большего к меньшему. Тут тоже всё плюс-минус понятно. Ещё из простеньких — ключевое слово `LIMIT`: мы можем обрезать выборку и сказать, что нам нужно только 10 записей, или 20, или 100, или 1000, — не все записи, а какое-то первое количество. И рядом есть `OFFSET`: можно сказать «возьми мне 20 записей, начиная с 10-й», — тогда возьмутся записи с 10-й по 30-ю по порядковому номеру.

**[35:50]** И последнее, наверное, что хотел бы сказать, — оконные функции. Это та же агрегация, только результат будет показан для каждого кортежа рядом: для каждой строки выведется результат агрегации. Про оконные функции я, возможно, тоже когда-нибудь запишу отдельную рубрику, потому что к этому нужно прямо подойти, представить и объяснить. Сейчас просто берём за правду, что они есть и используются, — хотя я их в последнее время почти не применяю.

**[36:24]** Сказал, что последнее, но есть ещё одна штука, которую хочется упомянуть, — common table expressions. Мы можем сохранить какой-то запрос в рамках одной сессии во временную вьюху или таблицу — как угодно это называть. По сути, мы задаём алиас какому-то запросу. Например, определяем `adult_students` как запрос — скажем, `SELECT * FROM students WHERE age > 30` — и говорим, что это «старые студенты». И дальше везде, где мы хотим их использовать — в `JOIN`, подзапросах, агрегациях, — обращаемся к этому алиасу и не пишем весь запрос каждый раз. Такое вот сохранение.

**[37:20]** И, конечно, common table expressions позволяют использовать ключевое слово `RECURSIVE` — рекурсивные CTE, которые можно применять очень интересным образом. По сути, они представляют собой рекурсивную функцию: есть условия входа в рекурсию и условия выхода. Тут тоже надо немного мозги повернуть, чтобы понять такой запрос. Но если интересно — загуглите или попросите ChatGPT объяснить, что такое рекурсивные common table expressions. Некоторые говорят, что это делает спецификацию SQL или её реализацию Turing complete. Но я, пообщавшись с ChatGPT, пришёл к выводу, что всё зависит от того, как мы определяем Turing complete. Если принять во внимание, что SQL сам по себе, в отрыве, не существует, а применяется к какой-то базе данных, то база данных рано или поздно стукнет вас по рукам и не позволит определить всю возможную логику — ту самую бесконечную ленту Тьюринга — на SQL. Так что, скорее всего, в моём понимании SQL всё-таки не Turing complete. Но спорить об этом я не хочу — просто интересное наблюдение.

**[38:42]** Итак, мы немного пробежались по истории баз данных, нашли между ними кое-что общее и освежили знания по SQL. В следующем выпуске мы спустимся на уровень ниже и посмотрим, из каких блоков устроена современная база данных, научимся различать их между собой и получим базовое представление о файлах данных и индексных файлах. Следующий выпуск — через неделю: да, теперь вы будете слушать меня в два раза чаще. Не забывайте делиться подкастом с друзьями и коллегами — давайте прокачивать себя и людей вокруг. Ну а на этом всё. Услышимся!


