#21: Введение в базы данных: История и SQL
Первый выпуск второго сезона, посвящённого базам данных. Александр Пахомов объясняет, почему глубокое, а не поверхностное понимание баз данных важнее знания конкретного языка, фреймворка или СУБД, прослеживает историю от реляционных систем 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] Здорово! Меня зовут Саша Пахомов, и я инженер, который любит своё дело. Вы слушаете подкаст «Тысяча фичей», и это второй сезон — про базы данных. Пройдя вместе со мной через все выпуски этого сезона, вы поймёте, как на самом деле устроены современные базы данных, почему их так много и как понять, какую базу данных выбрать. Ваш покорный слуга сам разрабатывает одну из таких систем и является коммитером в её 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. В следующем выпуске мы спустимся на уровень ниже и посмотрим, из каких блоков устроена современная база данных, научимся различать их между собой и получим базовое представление о файлах данных и индексных файлах. Следующий выпуск — через неделю: да, теперь вы будете слушать меня в два раза чаще. Не забывайте делиться подкастом с друзьями и коллегами — давайте прокачивать себя и людей вокруг. Ну а на этом всё. Услышимся!