😮 SQL: от Тетриса до ИИ – неожиданные возможности языка баз данных
Рекурсивные общие табличные выражения (CTE) превратили SQL в полный по Тьюрингу язык. Разберемся, как энтузиасты создают на нем сложные алгоритмы – от визуализации фракталов до полноценных игр.
Долгое время SQL использовали лишь для запросов и изменения записей в базах данных – для полноценного программирования в привычном смысле слова он не подходил. Однако добавление рекурсивных общих табличных выражений (CTE) сделало SQL полным по Тьюрингу. Рекурсивные CTE состоят из двух частей:
- Нерекурсивная часть (базовый случай) – создает начальные данные.
- Рекурсивная часть – может выполняться много раз, каждый раз используя результат предыдущего шага.
Благодаря CTE на SQL можно при желании реализовать любой алгоритм. Энтузиасты уже сделали:
- Визуализацию множества Мандельброта с помощью ASCII-графики.
- 3D-движок для рисования объемных фигур.
- GPT на 500 строках SQL-кода. Подробная статья о реализации этого проекта опубликована здесь.
- Трассировку лучей (это метод создания реалистичных изображений).
На прошлой неделе коллекция крутых SQL-проектов пополнилась еще одной интересной разработкой – версией «Тетриса».
Эта реализация демонстрирует несколько нестандартных SQL-техник, о которых стоит знать, даже если вы используете SQL только по прямому назначению:
- Игровой цикл. В основе игры – рекурсивное общее табличное выражение (CTE). Оно создает бесконечный цикл, который инициализирует состояние игры, обновляет его на основе ввода пользователя и отрисовывает игру. Цикл продолжается, пока игра не закончится.
- Вывод игры. Поскольку SQL-запрос обычно возвращает результат только после завершения, для отображения игры в реальном времени используется команда RAISE NOTICE. Эта команда выводит информацию в стандартный вывод, позволяя отображать игровое поле и другую информацию в процессе игры.
- Пользовательский ввод. Обработка пользовательского ввода реализована через отдельную таблицу в базе данных. Эта таблица служит коммуникационной шиной между игрой и пользователем. Пользователь добавляет команды в эту таблицу, а игра их считывает.
- Решение проблемы изоляции данных. Чтобы игра могла видеть новые команды, добавленные в таблицу ввода во время выполнения запроса, используется расширение dblink. Оно позволяет создавать новое подключение к базе данных с новым снимком данных, что дает возможность считывать самые последние команды пользователя.
- Предотвращение кэширования. Чтобы PostgreSQL не кэшировал результаты запроса к таблице ввода, к запросу добавляется уникальный идентификатор итерации. Это заставляет базу данных выполнять запрос заново на каждой итерации игрового цикла.
Для управления состоянием игры и обработки игровых событий используются подзапросы и агрегатные функции:
- Подзапросы позволяют вычислить новое состояние игры после каждого хода, включая позицию фигуры, состояние поля, счет и т. д. К примеру, рекурсивный подзапрос используется для вычисления максимального количества линий, на которые может упасть текущая фигура.
- Агрегации применяются для подсчета очищенных линий, выбора фигуры с наименьшим рангом, обновления счета и уровня игры. Например,
BOOL_OR()
применяется для определения, есть ли хотя бы одно столкновение между фигурой и занятыми клетками на поле.
Какой самый необычный проект на SQL вы встречали или реализовывали сами?