Базовое руководство по SQL: от создания таблиц до оптимизации
Введение в SQL
SQL (Structured Query Language) — это язык структурированных запросов, который используется для взаимодействия с реляционными базами данных. Главная задача SQL — предоставлять разработчикам и администраторам простой и мощный инструмент для хранения, изменения и извлечения данных. Давайте разберёмся, чем SQL важен и почему почти любая современная информационная система базируется на реляционных СУБД (системах управления базами данных).
Что такое SQL и почему он важен
SQL был создан для того, чтобы упростить взаимодействие с данными. Если раньше для управления информацией в программах использовали сложные бинарные или текстовые форматы, то с появлением SQL задачи хранения и поиска данных стали решаться гораздо легче и быстрее.
- Универсальность. SQL поддерживается большинством популярных СУБД: MySQL, PostgreSQL, Oracle, Microsoft SQL Server и др.
- Простота. Язык запросов SQL имеет читабельный синтаксис, основанный на английских ключевых словах (SELECT, INSERT, UPDATE, DELETE).
- Гибкость. SQL позволяет выполнять сложные фильтрации, сортировки, агрегатные вычисления и объединения данных из нескольких таблиц.
- Стандартизация. Основная часть синтаксиса во всех СУБД схожа, что упрощает переход с одной системы на другую.
Любое приложение, будь то интернет-магазин, социальная сеть или внутренняя учётная система, чаще всего строится вокруг базы данных. А значит, знания по SQL востребованы в самых разных проектах.
Реляционные базы данных: основные принципы
Реляционная модель баз данных основана на том, что данные хранятся в виде двухмерных таблиц (relation — отсюда и название). В таблице строки (записи) соответствуют объектам, а столбцы (поля) — характеристикам (атрибутам) этих объектов.
Пример: структура таблицы «Пользователи»
ID | Имя | Электронная почта | Дата регистрации |
---|---|---|---|
1 | Иван | ivan@example.com | 2025-01-01 |
2 | Елена | elena@example.com | 2025-01-02 |
3 | Николай | nikolay@example.com | 2025-01-05 |
- Столбцы (колонки) — это поля (ID, Имя, Электронная почта, Дата регистрации).
- Строки (записи) — это конкретные экземпляры данных (собственно, пользователи).
Основные принципы реляционной модели
- Каждой таблице — уникальное имя. Например,
users
,orders
,products
. - Явные структуры. Каждая таблица имеет чётко определённый набор столбцов и соответствующие типы данных.
- Первичный ключ (Primary Key). Чтобы идентифицировать запись, в таблице обычно есть специальный столбец (например,
id
), значения которого уникальны. - Связи (relationship). Таблицы могут быть связаны между собой через внешние ключи (Foreign Keys). Например, заказ
orders
может содержать внешний ключ на пользователя, который сделал заказ.
Таким образом, реляционный подход даёт удобную структуру для управления взаимосвязанными данными, а SQL помогает просто и эффективно манипулировать этими данными.
Архитектура баз данных: как данные хранятся и обрабатываются
Когда мы говорим о реляционной базе данных, обычно имеем в виду СУБД, которая управляет несколькими компонентами:
- Файловая система. Таблицы физически хранятся в файлах специального формата (каждая СУБД имеет собственные внутренние механизмы).
- Серверная часть. Это «движок» базы данных, который обрабатывает входящие SQL-запросы, оптимизирует их, выполняет операции над файлами и возвращает результат.
- Клиент. Наша программа или утилита командной строки, которая отправляет SQL-запросы и принимает ответы.
Примечание: В некоторых случаях логика хранения в СУБД может включать кеширование, репликацию, распределение данных по нескольким серверам и другие сложные механизмы. Но для начального уровня важно понимать, что есть «ядро» (сервер) и есть «клиент» (мы), который с этим сервером взаимодействует посредством SQL.
Простейший цикл работы с базой данных:
- Мы формируем запрос
SELECT * FROM users;
- Запрос отправляется в СУБД
- СУБД анализирует запрос, определяет оптимальный способ его выполнения, обращается к файлам, где хранятся данные, считывает нужные строки и столбцы
- Результат передаётся нам в читаемом виде (набор записей)
Основные операции
Рассмотрим базовые операции, позволяющие извлекать нужные данные из таблиц реляционной базы с помощью SQL. Главным «героем» станет оператор SELECT
, с помощью которого мы можем делать практически всё — от простой выборки до сложной фильтрации и сортировки.
SELECT: основа выборки данных
Самый простой пример запроса — это выборка всех столбцов и всех строк из таблицы:
SELECT *
FROM users;
SELECT
говорит СУБД: «Выбери данные...»*
означает «все столбцы»FROM users
указывает таблицу, из которой нужно получить данные
Примечание: В реальной практике вместо
*
часто указывают конкретные столбцы (например,SELECT name, email FROM users;
) — так запрос обрабатывается быстрее, а излишние данные не грузятся.
WHERE: фильтрация данных
Часто необходимо получать не все данные, а только те, что соответствуют определённому условию. Для этого используется секция WHERE
.
Пример 1: Фильтрация по конкретному значению
SELECT name, email
FROM users
WHERE id = 5;
- Выбираем имя и почту из таблицы
users
, гдеid
равен 5.
Пример 2: Фильтрация по строкам (символьным данным)
SELECT id, name
FROM users
WHERE email = 'elena@example.com';
- Здесь мы получим записи, у которых значение в столбце
email
совпадает сelena@example.com
.
Пример 3: Использование операторов сравнения
SELECT id, total_amount
FROM orders
WHERE total_amount > 1000;
- Оператор
>
позволяет выбрать все заказы, у которых сумма (total_amount
) больше 1000.
Пример 4: Условие с несколькими критериями
SELECT id, product_name, price
FROM products
WHERE price > 1000
AND category = 'Ноутбуки';
- Используем
AND
, чтобы отбирать только ноутбуки и только те, чья цена выше 1000.
ORDER BY: сортировка
Чтобы упорядочить результаты выборки, применяется ключевое слово ORDER BY
. По умолчанию сортировка идет по возрастанию (ASC
), для убывающего порядка добавляется DESC
.
Пример сортировки
SELECT id, product_name, price
FROM products
ORDER BY price ASC;
- Сортируем товары по цене по возрастанию.
SELECT id, product_name, price
FROM products
ORDER BY price DESC;
- Сортируем те же данные по цене по убыванию.
Сортировка по нескольким столбцам
SELECT id, product_name, price
FROM products
ORDER BY category ASC, price DESC;
- Сначала результаты группируются по категории (по возрастанию), внутри каждой категории сортировка идёт по цене (по убыванию).
LIMIT: ограничение количества результатов
Когда в таблице много записей, а вам нужно лишь несколько (например, для вывода на одной странице), применяется оператор LIMIT
:
Пример 1: Получить первые 5 строк
SELECT id, name
FROM users
ORDER BY id ASC
LIMIT 5;
- Запрос вернёт 5 строк, начиная с первой по порядку (учтите, что без
ORDER BY
порядок не гарантируется).
Пример 2: Использование OFFSET
Некоторые СУБД (например, MySQL и PostgreSQL) позволяют дополнительно указывать смещение (OFFSET), чтобы пропустить определённое количество строк и продолжить вывод.
SELECT id, name
FROM users
ORDER BY id ASC
LIMIT 5
OFFSET 5;
- Пропускаем первые 5 строк и берём следующие 5.
- Удобно для пагинации — отображения данных страницами.
Практический сценарий: выбор «активных» пользователей
Предположим, в таблице users
есть столбец is_active
(булево значение или tinyint
), указывающий, активен пользователь или нет. Нам нужно вывести первых 10 активных пользователей, отсортированных по дате регистрации по убыванию (чтобы сначала увидеть самых «свежих»):
SELECT id, name, email, registration_date
FROM users
WHERE is_active = 1
ORDER BY registration_date DESC
LIMIT 10;
WHERE is_active = 1
— выбираем только тех, чье полеis_active
равно единице.ORDER BY registration_date DESC
— сортируем по дате регистрации (новые сверху).LIMIT 10
— берём только 10 пользователей.
Работа с таблицами
В этой главе мы рассмотрим, как создавать структуру базы данных, а именно — таблицы, и какими способами заполнять их данными, а при необходимости обновлять или удалять записи. Также мы поговорим о типах данных и о том, как они влияют на хранение и целостность информации.
Создание таблиц (CREATE TABLE)
Команда CREATE TABLE
позволяет задать название новой таблицы, её столбцы и их типы данных, а также указать ключевые ограничения (например, первичный ключ). Общий синтаксис такой:
CREATE TABLE имя_таблицы (
столбец1 тип_данных [ограничения],
столбец2 тип_данных [ограничения],
...
);
Пример 1: Создадим таблицу «users»
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(255) UNIQUE,
registration_date DATE,
is_active TINYINT DEFAULT 1
);
Что происходит в этом запросе:
- id INT AUTO_INCREMENT PRIMARY KEY
INT
— целочисленный тип.AUTO_INCREMENT
(MySQL) илиSERIAL
(PostgreSQL) означает, что значения будут автоматически увеличиваться на 1 при добавлении каждой новой записи.PRIMARY KEY
определяет столбец, по которому uniquely (уникально) идентифицируется каждая строка.
- name VARCHAR(100) NOT NULL
VARCHAR(100)
— строковый тип переменной длины.NOT NULL
говорит, что этот столбец не может содержать пустое (NULL) значение.
- email VARCHAR(255) UNIQUE
- Аналогично строковый тип, 255 символов.
UNIQUE
означает, что столбец не может содержать повторяющиеся значения — каждый email должен быть уникальным.
- registration_date DATE
- Тип данных для хранения даты без времени.
- is_active TINYINT DEFAULT 1
- Числовой тип, обычно 0 или 1, в качестве флажка состояния (активен / неактивен).
DEFAULT 1
указывает, что если при добавлении пользователя не указать это поле, то по умолчанию будет установлено значение «1».
Типы данных и их влияние
Правильный выбор типа данных важен для производительности и точности хранения. Например:
- INT, BIGINT — для целочисленных значений. Выбор зависит от диапазона (INT vs BIGINT).
- FLOAT, DOUBLE, DECIMAL — для чисел с плавающей точкой и для финансовых расчётов (особенно DECIMAL).
- VARCHAR — для строк переменной длины. Указывая диапазон, мы определяем максимальное количество символов.
- TEXT — если нужно хранить длинные тексты (полноценные статьи, комментарии и т. д.).
- DATE, DATETIME, TIMESTAMP — для хранения даты и/или времени. Выбор зависит от того, нужен ли вам только день или полная метка времени.
Верный тип данных защищает от логических ошибок (например, строка вместо числа) и экономит место в базе.
Вставка данных (INSERT)
Чтобы добавить новую строку в таблицу, используется оператор INSERT
. Синтаксис:
INSERT INTO имя_таблицы (столбец1, столбец2, ...)
VALUES (значение1, значение2, ...);
Пример: Добавим нового пользователя
INSERT INTO users (name, email, registration_date)
VALUES ('Иван', 'ivan@example.com', '2025-01-08');
- Мы указываем столбцы, которые хотим заполнить (
name
,email
,registration_date
). - Столбец
id
заполняется автоматически (AUTO_INCREMENT), аis_active
получает значение по умолчанию (1).
Вставка нескольких строк одновременно
INSERT INTO users (name, email, registration_date)
VALUES
('Елена', 'elena@example.com', '2025-01-09'),
('Николай', 'nikolay@example.com', '2025-01-10');
- СУБД поддерживают добавление сразу нескольких записей одним запросом, что может быть быстрее, чем серия одиночных
INSERT
.
Обновление данных (UPDATE)
Оператор UPDATE
применяется, когда нужно изменить уже существующие записи в таблице. Синтаксис:
UPDATE имя_таблицы
SET столбец1 = новое_значение1, столбец2 = новое_значение2, ...
[WHERE условие];
Пример: Изменим статус активности пользователя
UPDATE users
SET is_active = 0
WHERE email = 'elena@example.com';
- Мы меняем столбец
is_active
на 0, чтобы отметить, что Елена более не активна. - Без
WHERE
оператор обновит все строки в таблице, поэтому лучше всегда указывать условие, чтобы избежать массовых ошибок.
Удаление данных (DELETE)
Удаление записей выполняется командой DELETE
. Синтаксис:
DELETE FROM имя_таблицы
[WHERE условие];
Пример: Удалим пользователя
DELETE FROM users
WHERE email = 'nikolay@example.com';
- Так мы полностью удаляем строку, где
email = 'nikolay@example.com'
. - Опять же, без
WHERE
будут удалены все записи в таблице (очень опасная операция).
Практический сценарий: создание таблицы заказов (orders)
Допустим, мы хотим отслеживать заказы, которые делают пользователи в нашем интернет-магазине. Создадим таблицу orders
, учитывая связи с таблицей users
(через внешний ключ user_id
— при условии, что СУБД поддерживает внешние ключи):
CREATE TABLE orders (
order_id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
order_date DATETIME NOT NULL,
total_amount DECIMAL(10, 2) NOT NULL,
CONSTRAINT fk_user
FOREIGN KEY (user_id)
REFERENCES users (id)
);
- user_id INT NOT NULL — идентификатор пользователя, который сделал заказ.
FOREIGN KEY (user_id) REFERENCES users (id)
устанавливает связь с таблицейusers
, чтобы нельзя было вorders
записать несуществующего пользователя.
Вставка заказа
INSERT INTO orders (user_id, order_date, total_amount)
VALUES (1, '2025-01-08 10:30:00', 1500.50);
- Пользователь с
id = 1
(Иван) сделал заказ на сумму 1500.50 единиц.
Агрегация и объединение данных
В этой главе мы разберём, как можно группировать данные и выполнять вычисления над группами (суммировать, подсчитывать количество, находить минимальные и максимальные значения и т. п.). Также мы рассмотрим механизм объединения данных из нескольких таблиц с помощью операторов JOIN
и UNION
.
Агрегация данных
Агрегация — это способ обобщить набор строк в таблице и вернуть одно сводное значение для каждой группы. Обычно агрегацию используют, чтобы посчитать статистику по определённым критериям. В SQL для этого есть несколько встроенных функций:
COUNT
— подсчитывает количество строк или количество значений в столбце.SUM
— суммирует значения в группе.AVG
— находит среднее арифметическое.MIN
— возвращает минимальное значение.MAX
— возвращает максимальное значение.
Пример 1: Подсчитать общее число пользователей
SELECT COUNT(*) AS total_users
FROM users;
COUNT(*)
вернёт общее количество строк в таблицеusers
.AS total_users
даёт псевдоним столбцу, чтобы результат был подписан более понятно.
Пример 2: Найти среднюю сумму заказа
SELECT AVG(total_amount) AS average_order
FROM orders;
AVG(total_amount)
вычисляет среднюю сумму заказов.- Полезно, когда нужно понять «средний чек» в интернет-магазине.
Группировка (GROUP BY) и фильтрация по группам (HAVING)
Чтобы применить агрегатные функции к определённым подмножествам данных (группам), используется GROUP BY
. Он группирует строки по значениям одного или нескольких столбцов, а для каждой группы можно вывести агрегатные результаты.
Пример 1: Количество заказов у каждого пользователя
SELECT user_id, COUNT(*) AS orders_count
FROM orders
GROUP BY user_id;
- Здесь все заказы группируются по
user_id
, и мы получаем, сколько заказов сделал каждый пользователь. Результат может выглядеть так (пример):
user_id orders_count 1 10 2 3 3 7
Пример 2: Сумма заказов по датам
SELECT DATE(order_date) AS order_day, SUM(total_amount) AS total_sum
FROM orders
GROUP BY DATE(order_date);
- Мы берём только дату (без времени) из
order_date
и суммируем полеtotal_amount
. - Так видим общую сумму продаж за каждый день.
HAVING
используется для фильтрации результатов после группировки (то есть применить условие уже по агрегированным данным). По логике он похож на WHERE
, но действует на итоговые группы.
SELECT user_id, SUM(total_amount) AS total_spent
FROM orders
GROUP BY user_id
HAVING SUM(total_amount) > 1000;
- Сначала группируем заказы по пользователям, считаем сумму покупок
SUM(total_amount)
для каждого пользователя. - Затем с помощью
HAVING
исключаем тех, у кого суммарная сумма покупок 1000 и меньше.
Важно:
WHERE
отбирает строки до группировки, аHAVING
— после.
Объединение данных из нескольких таблиц
В реальной базе данных информация обычно распределена по разным таблицам, которые связаны между собой. Например, есть таблица users
(пользователи) и таблица orders
(заказы), где orders.user_id
ссылается на users.id
. Чтобы получить сводную информацию, нужно объединять таблицы.
JOIN: внутренние и внешние соединения
INNER JOIN
возвращает только те строки, у которых есть соответствия в обеих таблицах.
SELECT
u.name AS user_name,
o.order_id,
o.total_amount
FROM users u
INNER JOIN orders o ON u.id = o.user_id;
- ON u.id = o.user_id указывает условие соединения.
- Каждая запись из
users
соединяется с записью вorders
, если совпадаютid
пользователя иuser_id
заказа. - Если пользователь не делал заказов, то в результате его не будет.
LEFT JOIN
возвращает все строки из левой таблицы (указанной перед словом JOIN) и соответствующие строки из правой таблицы. Если в правой таблице нет совпадений, там будут NULL
:
SELECT
u.name,
o.order_id,
o.total_amount
FROM users u
LEFT JOIN orders o ON u.id = o.user_id;
- Выведутся все пользователи и их заказы, если они есть.
- Для пользователей без заказов поля из
orders
будутNULL
.
RIGHT JOIN
— наоборот, возвращает все строки из правой таблицы и соответствующие строки из левой. Используется реже, так как в большинстве случаев можно «перевернуть» запрос и написать LEFT JOIN
.
Объединение результатов через UNION
Если нужно «сложить» результаты двух запросов «один над другим» (у которых одинаковая структура столбцов), используют оператор UNION
(или UNION ALL
):
SELECT name AS person, email
FROM users
UNION
SELECT supplier_name AS person, supplier_email AS email
FROM suppliers;
- В результате мы получим общий список «людей» (условных) и их email, взятых из двух разных таблиц.
UNION
по умолчанию удаляет дубликаты строк. Если хотим сохранить дубликаты, пишемUNION ALL
.- Столбцы по количеству и типу должны совпадать.
Практический сценарий: суммарная статистика по дням, включая имена пользователей
Допустим, у нас есть таблица orders
, где для каждого заказа прописан user_id
и дата. Мы хотим вывести день, общее количество заказов за этот день и имена всех пользователей, которые в эти дни заказы делали.
Шаг 1: Сгруппируем заказы по дням и получим общее количество заказов.
SELECT DATE(o.order_date) AS order_day, COUNT(*) AS total_orders
FROM orders o
GROUP BY DATE(o.order_date);
Шаг 2: Чтобы добавить имена пользователей, присоединим таблицу users
:
SELECT
DATE(o.order_date) AS order_day,
GROUP_CONCAT(u.name SEPARATOR ', ') AS users_list,
COUNT(*) AS total_orders
FROM orders o
INNER JOIN users u ON o.user_id = u.id
GROUP BY DATE(o.order_date);
GROUP_CONCAT(u.name SEPARATOR ', ')
(MySQL) позволяет «склеить» имена пользователей в одну строку, разделяя их запятыми.- Результат будет показывать, кто оформлял заказы в конкретный день и сколько всего заказов было.
Оптимизация запросов
В этой завершающей главе мы обсудим, как сделать работу с базой данных более удобной и эффективной. Рассмотрим создание представлений (VIEW) и индексы, которые помогают ускорять выборку, а также коснёмся ряда продвинутых приёмов оптимизации.
Создание представлений (VIEW)
Представление (View) — это виртуальная таблица, которая формируется на основе результатов запроса SELECT
. При этом в саму базу данные дублируются не всегда (зависит от СУБД и типа представления), а само представление «складывает» логику выборки в одном месте.
1.1 Зачем нужны представления
- Упрощение сложных запросов. Вместо того чтобы каждый раз повторять громоздкий
SELECT
с несколькими JOIN и фильтрацией, можно один раз создатьVIEW
, а затем обращаться к нему как к обычной таблице. - Безопасность. Можно скрыть некоторые столбцы или фильтровать данные, предоставляя доступ только к представлению, а не к исходной таблице.
- Логическая изоляция. Если структура реальных таблиц меняется, но представление сохраняет интерфейс, приложения могут продолжать использовать представление без модификаций.
1.2 Создание представления
Общий синтаксис:
CREATE VIEW имя_представления AS
SELECT ...
FROM ...
[WHERE ...]
Пример: Создадим представление «active_users»
CREATE VIEW active_users AS
SELECT
id,
name,
email,
registration_date
FROM users
WHERE is_active = 1;
- Теперь, чтобы обратиться к активным пользователям, не нужно каждый раз писать
WHERE is_active = 1
; достаточно делать запрос кactive_users
:
SELECT *
FROM active_users;
- СУБД под капотом выполнит исходный запрос, но для нас всё выглядит, как будто мы работаем с отдельной таблицей.
Индексы: ускорение выборки
2.1 Что такое индекс и как он работает
Индекс — это специальная структура данных (например, B-дерево), которую СУБД хранит отдельно от основной таблицы. С его помощью поиск нужных строк в таблице может выполняться гораздо быстрее (вместо «полного сканирования» всей таблицы).
- Без индекса: СУБД может идти по каждой строке, чтобы проверить, подходит ли она под условие (особенно медленно на больших объёмах данных).
- С индексом: СУБД обращается к специальной структуре, которая организует значения столбца в упорядоченном виде, и мгновенно находит нужные записи.
2.2 Типы индексов
- B-Tree индекс (основной тип во многих СУБД): эффективен для поиска по точному значению, диапазону, сортировке.
- Hash индекс (например, в PostgreSQL): очень быстрый поиск по точному соответствию, но неприменим для упорядочения по диапазону.
- Fulltext индекс (в MySQL): предназначен для быстрого поиска по текстовым полям (например, слова в статьях).
- GIN / GIST индексы (в PostgreSQL): могут использоваться для сложных типов данных (JSON, массивы, геоданные и т. п.).
2.3 Создание индекса
Синтаксис в MySQL (и схожий в других СУБД):
CREATE INDEX имя_индекса
ON имя_таблицы (столбец1, [столбец2, ...]);
Пример: Индекс по email
CREATE INDEX idx_users_email
ON users (email);
- Теперь запрос вида
SELECT * FROM users WHERE email = 'elena@example.com';
будет выполняться быстрее.
Внимание: Создание индекса ускоряет операции чтения (
SELECT
), но может замедлить вставку (INSERT
), обновление (UPDATE
) и удаление (DELETE
), потому что при изменении данных надо обновлять и индекс. Поэтому индексы нужны там, где важна быстрая выборка, а не на каждом столбце.
Продвинутые приёмы оптимизации
3.1 Анализ выполнения запросов (EXPLAIN)
Многие СУБД (MySQL, PostgreSQL, SQL Server, Oracle) имеют инструмент EXPLAIN
, который показывает, как будет выполняться запрос: какие таблицы сканируются, какие индексы используются и т. д.
EXPLAIN SELECT *
FROM users
WHERE email = 'elena@example.com';
- Результат покажет, берёт ли запрос индекс или делает полное сканирование таблицы. Это помогает найти «узкие места» и улучшить их за счёт индексов или переписывания запроса.
3.2 Кеширование запросов
Некоторые СУБД могут кешировать результаты запросов. Также кеширование часто реализуют на уровне приложения (например, Memcached, Redis), чтобы при повторных обращениях не гонять запросы в базу заново.
3.3 Денормализация и шардинг
- Денормализация: сознательное «дублирование» данных в таблицах для уменьшения числа JOIN, если быстрый чтение/запрос важнее строгой структуры.
- Шардинг (Sharding): разделение большой таблицы на несколько фрагментов (шардов), обычно по некому критерию (например, по диапазону
id
). Это помогает распределять нагрузку по нескольким серверам.
3.4 Планировщики запросов и распределённые базы
В крупных системах может использоваться распределённая архитектура (кластеры PostgreSQL, шардирование в MySQL, партиционирование таблиц и т. д.). Это тема для отдельных исследований и выходит за рамки нашего базового курса. Однако понимание индексирования и умение работать с EXPLAIN
— уже огромный шаг в сторону оптимизации.
На этом мы завершаем наш ознакомительный курс по SQL. Теперь вы знаете, как создавать таблицы, манипулировать данными, объединять их, агрегировать и оптимизировать запросы. Этот фундамент позволит вам уверенно двигаться дальше в изучении конкретных СУБД (MySQL, PostgreSQL, SQL Server и пр.) и решать реальные задачи, связанные с базами данных.