Представьте: вы написали приложение, которое хранит заказы в JSON-файле. Двадцать заказов — всё работает. Потом их становится десять тысяч, и вам нужно найти все заказы конкретного клиента, отсортированные по дате, с суммой больше тысячи рублей и только те, что не отменены. Что происходит? Читаете весь файл в память, фильтруете вручную в коде, сортируете — при каждом запросе. Медленно. А если два пользователя одновременно пишут в один файл? Данные повреждены.
Именно для этого придумали базы данных. СУБД — система управления базами данных — берёт на себя хранение, поиск, сортировку и параллельный доступ. Вы говорите «дай мне вот это» на языке SQL, она отдаёт результат. Никакого чтения всего файла, никакой ручной фильтрации.
Мы будем работать с реляционными базами данных. Слово пугает, идея простая.
Представьте таблицу — как в Excel. Строки и колонки. Каждая строка — одна запись: один заказ, один пользователь, один товар. Каждая колонка — одно свойство: имя, сумма, статус. Вот таблица orders:
id | customer_name | amount | status ---|---------------|--------|------------ 1 | Алия | 3500 | paid 2 | Максим | 1200 | cancelled 3 | Наташа | 800 | processing 4 | Алия | 4200 | paid 5 | Сергей | 650 | paid
У каждой таблицы обычно есть первичный ключ (primary key, PK) — уникальный идентификатор строки. В нашем случае это id. Два заказа не могут иметь одинаковый id. Это как паспорт: у каждого свой, неповторимый.
Таблицы могут быть связаны между собой — отсюда слово «реляционная» (от relation — связь). Допустим, у вас есть таблица customers с клиентами. В заказе вместо того чтобы дублировать имя клиента в каждой строке, вы храните customer_id — ссылку на строку в таблице customers. Такая ссылка называется внешний ключ (foreign key, FK).
customers orders ───────────── ───────────────────── id (PK) ◄──────────── customer_id (FK) name id (PK) email amount status
Зачем так делать? Допустим, клиент поменял email. Если имя и email хранились в каждом заказе — придётся обновить тысячи строк. При связи через FK — меняете одну строку в customers, и всё.
Возможно, у вас уже возник вопрос: хорошо, в orders есть customer_id, но как тогда получить имя клиента в одном запросе? Ответ — через JOIN: конструкция, которая соединяет таблицы по ключу. Подробно разберём её в SQL-3. Пока главное — понять зачем FK существует: чтобы не дублировать данные и менять их в одном месте.
Пять терминов, которые нужно запомнить: таблица, строка, колонка, первичный ключ (PK), внешний ключ (FK). Они будут везде.
Реляционных СУБД много, но на практике вы встретите три.
| СУБД | Тип | Когда использовать |
|---|---|---|
| SQLite | Встроенная | Мобильные приложения, тесты. Не требует сервера — всё в одном файле. |
| PostgreSQL | Серверная | Бэкенд, API. Наиболее популярная база данных среди разработчиков — её используют 48,7% опрошенных (Stack Overflow Developer Survey 2024). |
| MySQL | Серверная | Legacy-проекты, PHP-стек. В новых проектах чаще выбирают PostgreSQL. |
Ключевое: SQL на базовом уровне у всех трёх практически одинаковый. То, что вы изучаете сегодня, работает везде — и в SQLite внутри вашего Android-приложения, и в PostgreSQL на сервере. Меняется только обёртка: ORM, библиотека, фреймворк. Сам язык запросов — тот же.
Для практики используем dbfiddle.uk — SQL прямо в браузере, ничего устанавливать не нужно. Скопируйте этот скрипт в редактор и выполните — он создаст тестовую таблицу с данными:
CREATE TABLE orders ( id INTEGER PRIMARY KEY, customer_name TEXT NOT NULL, amount REAL NOT NULL, status TEXT NOT NULL ); INSERT INTO orders VALUES (1, 'Алия', 3500, 'paid'), (2, 'Максим', 1200, 'cancelled'), (3, 'Наташа', 800, 'processing'), (4, 'Алия', 4200, 'paid'), (5, 'Сергей', 650, 'paid');
Теперь у нас есть с чем работать.
Самая главная команда в SQL — SELECT. Это чтение данных. Большинство запросов к базе — именно чтение.
Самый простой запрос — взять всё из таблицы:
SELECT * FROM orders;
Звёздочка означает «все колонки». Вернёт:
id | customer_name | amount | status 1 | Алия | 3500 | paid 2 | Максим | 1200 | cancelled ...
Удобно когда нужно быстро посмотреть что в таблице. Но SELECT * — плохая практика для реального кода: вы тянете все колонки, включая те, что не нужны. Если в таблице двадцать колонок, а вам нужны две — зачем грузить остальные восемнадцать? В мобильной разработке это ещё и лишние данные по сети.
Правильно — перечислять конкретные колонки:
SELECT customer_name, amount FROM orders; -- вернёт: -- customer_name | amount -- Алия | 3500 -- Максим | 1200 -- ...
Нам почти никогда не нужны все строки — мы ищем что-то конкретное. Для этого есть WHERE.
SELECT customer_name, amount FROM orders WHERE status = 'paid'; -- вернёт только заказы со статусом paid
Важный момент — зафиксируйте раз и навсегда: в SQL знак равенства одинарный =. Стандарт SQL не знает == — в PostgreSQL и MySQL это синтаксическая ошибка. SQLite технически принимает == как нестандартный псевдоним =, но ваш код может перестать работать при смене СУБД. Пишите всегда =.
Разберём операторы по одному — от простых к составным.
Кроме = работают все операторы сравнения: != (не равно), >, <, >=, <=. Например, заказы с суммой больше тысячи:
SELECT customer_name, amount FROM orders WHERE amount > 1000;
AND и OR позволяют комбинировать условия. AND — оба условия выполняются одновременно. OR — хотя бы одно:
-- Заказы больше 1000 рублей И не отменённые SELECT customer_name, amount FROM orders WHERE amount > 1000 AND status != 'cancelled'; -- Статус paid ИЛИ processing SELECT customer_name, amount FROM orders WHERE status = 'paid' OR status = 'processing';
Когда вариантов несколько, вместо цепочки OR удобнее IN:
SELECT customer_name, amount FROM orders WHERE status IN ('paid', 'processing'); -- результат тот же, читается чище
BETWEEN задаёт диапазон. Оба края включены:
SELECT customer_name, amount FROM orders WHERE amount BETWEEN 500 AND 2000; -- найдёт заказы от 500 до 2000 рублей включительно -- это эквивалентно: WHERE amount >= 500 AND amount <= 2000
LIKE — поиск по шаблону. Процент % — подстановочный знак, «любые символы». Паттерн '%ов%' (проценты с обеих сторон) найдёт строку «Иванов» — потому что «ов» встречается в любом месте. А вот 'ов%' — только строки, которые начинаются с «ов». «Иванов» начинается с «И», поэтому с паттерном 'ов%' не найдётся. Типичная ошибка: забыть ведущий % когда нужно найти вхождение в любом месте строки.
-- Только строки, НАЧИНАЮЩИЕСЯ с «ов» (Иванов не найдётся) WHERE customer_name LIKE 'ов%'; -- Строки, содержащие «ов» В ЛЮБОМ МЕСТЕ (Иванов найдётся) WHERE customer_name LIKE '%ов%';
ORDER BY сортирует результат. По умолчанию — по возрастанию (ASC). Для убывания — DESC:
SELECT customer_name, amount FROM orders WHERE status != 'cancelled' ORDER BY amount DESC; -- самые крупные заказы идут первыми
LIMIT ограничивает количество строк в результате. Полезно для пагинации и выборки топ-N:
SELECT customer_name, amount FROM orders WHERE status != 'cancelled' ORDER BY amount DESC LIMIT 5; -- пять самых крупных заказов, которые не отменены
Посмотрите на структуру последнего запроса — это фиксированный порядок:
SELECT → FROM → WHERE → ORDER BY → LIMIT
Если переставить — синтаксическая ошибка. Запишите себе: этот порядок нужен в каждом запросе.
Проверка понимания материала
Доступно после входаПройдите квиз и сразу закрепите тему, когда откроете интерактивный режим урока.
Синтаксис SQL одинаковый везде — меняется только обёртка.
Android / Room. В DAO-интерфейсе вы пишете:
@Query("SELECT * FROM orders WHERE status != 'cancelled' ORDER BY amount DESC") fun getActiveOrders(): Flow<List<OrderEntity>>
Строка внутри @Query — это буквально тот же SELECT. Room передаёт её в SQLite под капотом.
QA. После того как тест выполнил действие, вы проверяете состояние базы напрямую:
SELECT COUNT(*) FROM orders WHERE customer_name = 'Алия' AND status = 'paid';
Если вернулось 2 — тест прошёл, данные в базе совпадают с ожидаемым. Это database assertion: намного надёжнее, чем верить только тому, что показывает UI.
Backend. Запрос к PostgreSQL для API-эндпоинта «топ покупателей»:
SELECT customer_name, SUM(amount) AS total FROM orders WHERE status = 'paid' GROUP BY customer_name ORDER BY total DESC LIMIT 5;
Готовый датасет — передаёте в ответ.
AI. Выборка последних релевантных документов перед формированием ответа агента:
SELECT content, created_at FROM documents WHERE category = 'policy' ORDER BY created_at DESC LIMIT 10;
Это часть RAG-пайплайна: агент сначала достаёт нужные данные из базы, потом формирует ответ на их основе.
Проверка понимания материала
Доступно после входаПройдите квиз и сразу закрепите тему, когда откроете интерактивный режим урока.
Вы теперь понимаете, что реляционная БД — это набор таблиц со строками и колонками. У каждой строки есть первичный ключ, таблицы связаны через внешние ключи. Это позволяет не дублировать данные и менять их в одном месте.
Вы знаете три основных СУБД: SQLite — для мобильного и тестов, PostgreSQL — для бэкенда, MySQL — в legacy-проектах. SQL у них практически одинаковый.
И вы умеете читать данные через SELECT: перечислять конкретные колонки вместо *, фильтровать через WHERE с операторами =, !=, >, <, AND, OR, IN, BETWEEN, LIKE, сортировать через ORDER BY и ограничивать результат через LIMIT. Порядок ключевых слов фиксированный: SELECT → FROM → WHERE → ORDER BY → LIMIT.
В следующем уроке разберём как создавать таблицы и изменять данные — INSERT, UPDATE, DELETE. Это полный CRUD, и вы увидите как он выглядит в Room, Exposed и SQLAlchemy.