
Посвящается
Юре, Оксане, Екатерине, Лене, Константину,
Михаилу, Екатерине, Елене, Антону
и другим моим коллегам по ОТП Банку.
Чалышев Максим 2026 год.
Введение
Приветствую, уважаемый читатель!
Позвольте мне немного рассказать о своём профессиональном опыте. Я работаю в сфере информационных технологий уже более 30 лет. Занимался тестированием, разработкой, но главным делом моей жизни были и остаются базы данных. Именно в этой области моя основная специализация, хотя я также неизбежно сталкивался с Java, SAS, Python, веб-разработкой (JavaScript, Node. js).
Эта книга — мой обобщённый опыт работы с базами данных на основе СУБД PostgreSQL.
В последнее время системы искусственного интеллекта получили очень широкое распространение. В создании данной книги мне помогал ИИ DeepSeek. Он сохранял структуру книги, помогал с формулировками отдельных главы. Мы проработали несколько стилей, и я выбрал наиболее понятный с точки зрения обучения.
Данную книгу можно воспринимать как учебник: если последовательно заниматься и выполнять практические задания, то можно в кратчайшие сроки стать хорошим специалистом по PostgreSQL.
При методичном, последовательном выполнении заданий вы, уважаемый читатель, быстро освоите PostgreSQL или улучшите уже имеющиеся навыки.
Книгу также можно использовать как справочник по СУБД PostgreSQL, поскольку практически все ключевые и сложные темы в ней подробно раскрыты, с понятными примерами и практическими задачами.
Если в процессе чтения у вас возникнут вопрос связаться с автором можно по адресу электронной почты atvcross@rambler.ru или через социальную сеть ВК https://vk.com/maxandmouse
Шаг 1. Что это за книга. Как работать с этой книгой. PostgreSQL
Ваш путь от первого SELECT до профессионала
ДЛЯ КОГО ЭТА КНИГА
— Для начинающих — если вы только знакомитесь с базами данных и хотите сразу начать писать запросы, а не учить теорию.
— Для тех, кто переходит с другой СУБД — если вы работали с MySQL, Oracle или SQL Server и хотите освоить PostgreSQL.
— Для тех, кто хочет систематизировать знания — если вы уже пишете запросы, но чувствуете, что в знаниях есть пробелы.
ЖИВОЙ ПРИМЕР
Вы держите в руках книгу, которая не похожа на скучный учебник.
Здесь нет занудных определений и сухих лекций.
Есть живой разговор, реальные примеры и чёткий путь — 100 шагов, которые превратят вас из человека, который слышал про SQL, в специалиста, уверенно работающего с PostgreSQL.
КАК УСТРОЕНА КНИГА
Книга построена как 100 шагов.
Каждый шаг — это одна тема, один приём, одна конструкция.
Структура шага:
— Живой пример — зачем это нужно в реальной работе.
— Основной материал — кратко, без воды.
— Примеры — код с пояснениями.
— Типичные ошибки — что может пойти не так.
— Тонкости и нюансы — для тех, кто хочет глубже.
— Из опыта — личная история автора.
— Вопросы и ответы — разбор частых недоумений.
— Попробуй сам — задания для закрепления.
— Задачи на повторение — чтобы не забывать пройденное.
КАК РАБОТАТЬ С ЭТОЙ КНИГОЙ
— Идите по порядку.
— Шаги построены от простого к сложному. Пропускать их не стоит.
— Выполняйте задания.
— Теория без практики забывается. Каждый шаг содержит задания 'Попробуй сам' — делайте их.
— Не бойтесь ошибок.
— Ошибки — это нормально. В книге есть раздел 'Типичные ошибки', чтобы вы знали, на что обратить внимание.
— Возвращайтесь к примерам.
— Если что-то забыли — не листайте всё сначала. Книга построена так, что к любому шагу можно вернуться.
— Используйте демонстрационную схему.
— Все примеры в книге построены на одной схеме — 'Доставка цветов'.
— Сначала вы установите эту схему, а затем будете с ней работать на протяжении всей книги.
— Старайтесь понять синтаксис.
— Вы видите перед собой запрос — попробуйте понять, что этот запрос означает на человеческом языке. Это поможет вам научиться переводить свои мысли в язык SQL.
— Повторяйте.
— Выполняйте задания на повторение и перечитывайте шаги, которые вы уже прошли.
— Иногда издательские системы заменяются кавычки на другие значки в запросах следует использовать именно одинарные кавычки
ЧТО НУЖНО ДЛЯ РАБОТЫ
— Компьютер с Windows, macOS или Linux.
— Установленная PostgreSQL (шаг 2).
— pgAdmin (устанавливается вместе с PostgreSQL).
— Желание разбираться и пробовать.
ОБОЗНАЧЕНИЯ В КНИГЕ
— Код — выделен моноширинным шрифтом.
— Что тут происходит — пояснение к примеру.
— Внимание! — важные предупреждения.
— опыт — личный опыт автора.
ЧТО ТАКОЕ БАЗЫ ДАННЫХ, НАЗНАЧЕНИЕ БАЗ ДАННЫХ
Трудно себе представить, что всего тридцать лет назад вся информация размещалась на бумажных носителях, и архивы документов занимали подчас целые здания.
Сейчас это кажется неважным, но обычный смартфон может вместить несколько тысяч книг.
Огромное количество систем современного хранения информации работает под управлением баз данных. Спектр применения систем управления базами данных неограничен. Сейчас практически ни одна отрасль не обходится без систем баз данных. Даже такая далёкая от ИТ отрасль, как сельское хозяйство и фермерство, повсеместно автоматизируется с использованием передовых информационных разработок.
База данных — это организованная структура, предназначенная для хранения, изменения и обработки взаимосвязанной информации. Простыми словами: база данных — это электронное хранилище, где информация структурирована и к ней можно быстро получить доступ.
НАЗНАЧЕНИЕ ЯЗЫКА SQL, НЕОБХОДИМОСТЬ ИЗУЧЕНИЯ SQL
Structured Query Language (SQL) — язык структурированных запросов.
Язык запросов SQL — универсальный язык для работы с данными базы. Язык запросов SQL используется для управления массивами данных в БД, множествами.
Язык SQL предоставляет возможность для вывода структурированной заданной информации из базы. SQL также применяется для изменения данных, добавления данных в базу.
Язык SQL относится к функциональным языкам программирования. Он отличается от алгоритмических языков. Основу языка составляет не алгоритм как таковой, а совокупность команд, определяющих взаимоотношения информационных множеств и подмножеств.
Следует отметить, что системы управления базами данных — СУБД — имеют различные реализации, такие как PostgreSQL, Oracle, MS SQL, MySQL.
Язык SQL в разных СУБД имеет небольшие отличия, например в детальном синтаксисе описания операторов. Такие отличия присутствуют в специальных функциях, относящихся к той или иной СУБД, но всё же в основном язык — это общий синтаксис, практически идентичный для любой СУБД.
В данном курсе мы будем рассматривать общепринятый синтаксис SQL PostgreSQL. Данная книга, как я ранее писал, обучает диалекту PostgreSQL, так как PostgreSQL — одна из самых популярных и распространённых СУБД на сегодняшний день.
Кроме того, бесплатная версия данной СУБД всегда доступна для скачивания, и её установка не представляет никакой сложности и не требует значительных ресурсов.
ИТОГО: ЧТО ТАКОЕ СУБД, POSTGRESQL И SQL
СУБД (Система управления базами данных) — это программа, которая хранит, организует и даёт доступ к данным. Примеры: PostgreSQL, MySQL, Oracle, SQL Server.
PostgreSQL — одна из самых популярных и мощных СУБД в мире. Её используют крупные компании (Сбербанк), государственные организации и стартапы. PostgreSQL — бесплатная, с открытым исходным кодом, надёжная и поддерживает самые современные возможности SQL.
SQL (Structured Query Language — язык структурированных запросов) — это язык, на котором вы 'разговариваете' с базой данных. С помощью SQL вы даёте команды: 'покажи всех клиентов', 'найди заказы за последний месяц', 'посчитай средний чек'. SQL — стандарт для всех реляционных баз данных. Выучив его один раз, вы сможете работать и с PostgreSQL, и с MySQL, и с другими СУБД.
Почему PostgreSQL?
— Бесплатный и с открытым кодом
— Соответствует стандартам SQL лучше большинства других СУБД
— Поддерживает продвинутые возможности: JSON, полнотекстовый поиск, оконные функции, свои типы данных
— Надёжный и производительный
— Большое сообщество и множество расширений
ИЗ ОПЫТА
Когда я только начинал изучать SQL, мне казалось, что это невозможно запомнить.
Но оказалось, что достаточно понять логику, а синтаксис приходит с практикой.
Именно так и построена эта книга: сначала вы понимаете, зачем нужна конструкция, а потом учитесь её использовать.
ВОПРОСЫ И ОТВЕТЫ
В: Нужно ли читать всё подряд?
О: Да, если вы новичок. Если вы уже знакомы с SQL, можете выборочно проходить шаги, но лучше не пропускать.
В: Можно ли пропустить установку и сразу начать с запросов?
О: Можно, но тогда вы не сможете выполнять примеры. Установка PostgreSQL и pgAdmin подробно описана в шаге 2, и она займёт не больше 15 минут.
В: Что делать, если я застрял на каком-то шаге?
О: Вернитесь к примерам, перечитайте 'Типичные ошибки'. Если не помогает — перейдите к следующему шагу и вернитесь позже.
ПОПРОБУЙ САМ
Пока ничего не нужно устанавливать и писать.
Просто откройте книгу и посмотрите, как устроены шаги.
Обратите внимание на структуру: везде повторяется один и тот же формат.
Шаг 2. Что нам понадобится. Установка БД PostgreSQL. Установка pgAdmin
Готовим рабочее место
ЖИВОЙ ПРИМЕР
Для учёбы, выполнения тестовых примеров и учебных задач вам потребуется установить СУБД PostgreSQL на локальный компьютер.
В этом шаге мы разберём установку PostgreSQL и pgAdmin — графического инструмента для работы с базами данных.
ПОДГОТОВКА К УСТАНОВКЕ
Для корректной установки вам понадобится:
— 64-разрядный компьютер или ноутбук
— оперативная память не менее 2 ГБ
— свободное место на диске не менее 2 ГБ
— на компьютере должна быть установлена операционная система Windows. Если у вас другая операционная система (macOS или Linux), то при установке необходимо это учитывать — процесс установки будет отличаться, но официальный сайт содержит инструкции для всех основных платформ.
СКАЧИВАНИЕ ДИСТРИБУТИВА
Перейдите на официальный сайт PostgreSQL:
https://www.postgresql.org/download/
На странице выберите вашу операционную систему.
Если у вас Windows, нажмите на значок Windows в верхней части страницы.
Вы попадёте на страницу загрузки для Windows:
https://www.postgresql.org/download/windows/
Нажмите Download the installer.
В открывшемся списке версий выберите последнюю (на момент написания — 18.3).
В колонке Windows x86—64 нажмите значок скачивания.
Через несколько секунд начнётся загрузка дистрибутива.
Если загрузка не началась, используйте ссылку Click me.
УСТАНОВКА СУБД POSTGRESQL
В этом разделе описывается стандартная установка PostgreSQL. Если вы забыли пароль после установки, воспользуйтесь инструкцией по сбросу пароля (описано в шаге 88).
Запустите скачанный файл.
Обычно он находится в папке 'Загрузки' и называется примерно postgresql-18.3-1-windows-x64.exe.
Программа проверит наличие необходимых пакетов и, если потребуется, загрузит их.
Может понадобиться повторный запуск или перезагрузка компьютера.
После этого запустится мастер установки.
1. Выбор папки установки.
Оставьте настройки по умолчанию и нажмите Next.
2. Выбор компонентов.
Оставьте настройки по умолчанию и нажмите Next.
3. Выбор папки для данных.
Оставьте настройки по умолчанию и нажмите Next.
4. Установка пароля администратора.
Внимание! Это очень важный шаг. Задайте пароль для доступа к СУБД.
Рекомендуется использовать простой пароль, например manager или admin.
Вы можете выбрать свой, но обязательно запишите его.
Введите пароль дважды и нажмите Next.
5. Выбор порта.
Оставьте порт по умолчанию (5432) и нажмите Next.
6. Выбор локали.
Оставьте значение по умолчанию (обычно DEFAULT) и нажмите Next.
7. Просмотр настроек.
Проверьте параметры установки и нажмите Next.
8. Установка.
Нажмите Next для начала установки.
Процесс может занять от 5 до 15 минут.
9. Завершение.
Снимите галочку Launch Stack Builder and Exit и нажмите Finish.
Если вы забыли пароль: Процедура сброса пароля подробно описана в шаге 88 (раздел 'Сброс забытого пароля пользователя PostgreSQL').
ПРОВЕРКА УСТАНОВКИ
В меню Пуск найдите и запустите pgAdmin 4.
В левой части окна раскройте Servers → PostgreSQL 18 (версия может отличаться).
Введите пароль, который вы задали при установке.
Рекомендуется снять галочку, чтобы не вводить пароль каждый раз.
Если соединение успешно, вы увидите структуру сервера в левой панели.
Иногда требуется повторить подключение дважды — первый запуск может занять немного времени.
ПЕРВОЕ ЗНАКОМСТВО С pgAdmin
Для работы с запросами используется Query Tool:
Выберите в меню Tools → Query Tool.
(скриншот: pgadmin4.png)
Откроется окно, в котором можно писать и выполнять SQL-запросы.
ТИПИЧНЫЕ ОШИБКИ
Ошибка 1. Забыть пароль администратора.
Если пароль потерян, придётся переустанавливать PostgreSQL или воспользоваться инструкцией по сбросу пароля (шаг 88).
Ошибка 2. Выбрать не ту версию для скачивания.
Убедитесь, что скачиваете дистрибутив для вашей операционной системы и разрядности.
Ошибка 3. Игнорировать требования к системе.
Если оперативной памяти или места на диске недостаточно, установка может не завершиться.
ИЗ ОПЫТА
При первой установке я выбрал сложный пароль и тут же забыл его.
Пришлось открыть эту книгу на шаге 88 и сбросить пароль, как написано.
С тех пор я использую простой пароль и записываю его в надёжное место.
ВОПРОСЫ И ОТВЕТЫ
В: Можно ли установить PostgreSQL на macOS или Linux?
О: Да. На официальном сайте есть инструкции для всех основных операционных систем.
В: Обязательно ли устанавливать pgAdmin?
О: Нет. Есть и другие инструменты (например, DBeaver, DataGrip), но pgAdmin идёт в комплекте и отлично подходит для начала.
В: Что делать, если установка зависла?
О: Попробуйте перезапустить компьютер и начать установку заново.
Шаг 3. Что где находится. Как работать с pgAdmin
Ваш главный инструмент для работы с PostgreSQL
ЖИВОЙ ПРИМЕР
Вы уже установили PostgreSQL и pgAdmin. Теперь перед вами окно pgAdmin — множество различных элементов интерфейса, сложное меню, различные кнопки, настройки — и, возможно, вы уже чувствуете лёгкое замешательство: Что здесь к чему? Где здесь писать запросы? Как посмотреть перечень таблиц? Как узнать, какие есть данные в нужной таблице?
В этом шаге мы подробно разберём интерфейс pgAdmin, чтобы вы чувствовали себя уверенно. Этот инструмент станет вашим главным помощником на протяжении всей книги.
НАЗНАЧЕНИЕ ПРОГРАММЫ pgAdmin
pgAdmin — это графический интерфейс для работы с PostgreSQL.
Он позволяет:
— просматривать структуру баз данных
— выполнять SQL-запросы
— редактировать данные в таблицах
— управлять серверами и пользователями
— и выполнять множество других задач
Простыми словами: pgAdmin — это окно в ваш PostgreSQL.
ЗАПУСК ПРОГРАММЫ pgAdmin
Нажмите Пуск (Windows) или откройте Launchpad (macOS).
Найдите pgAdmin 4 и запустите его.
Откроется интерфейс pgAdmin.
ПРОСМОТР ДЕРЕВА СЕРВЕРОВ, БАЗ, СХЕМ, ТАБЛИЦ
В левой панели pgAdmin находится дерево объектов.
Разберитесь с иерархией:
— Servers — здесь отображаются подключения к серверам PostgreSQL.
— PostgreSQL 18 (или ваша версия) — ваш локальный сервер.
— Databases — список баз данных.
— flower_delivery (ваша учебная база) — раскройте её.
— Schemas → public — здесь находятся таблицы, представления, функции.
— Tables — список таблиц в схеме public.
(скриншот: pgadmin_tree.png — дерево объектов)
Совет: чтобы найти нужную таблицу, используйте поиск (Ctrl + G).
СОЕДИНЕНИЕ С СЕРВЕРОМ БАЗЫ ДАННЫХ
После установки PostgreSQL и pgAdmin необходимо подключиться к серверу базы данных, чтобы начать работу.
Открытие сервера
В левой панели pgAdmin найдите Servers и раскройте его. Вы увидите сервер PostgreSQL 18 (или вашу версию).
Подключение к серверу
Нажмите на сервер левой кнопкой мыши. Откроется окно ввода пароля.
Ввод пароля
Введите пароль, который вы задали при установке PostgreSQL (рекомендуется снять галочку 'Store password', чтобы не вводить пароль каждый раз).
Успешное подключение
После ввода правильного пароля сервер станет активным — вы увидите структуру баз данных, и зелёная иконка сервера станет активной.
(скриншот: pgadmin_connected.png — сервер подключён)
Если подключиться не удалось
Проверьте:
— Запущен ли PostgreSQL (служба postgresql в Windows или systemctl status postgresql в Linux)
— Правильно ли введён пароль
— Не блокирует ли подключение брандмауэр
СОЗДАДИМ ТАБЛИЧКУ ДЛЯ ПРИМЕРА
Прежде чем изучать работу с таблицами, давайте создадим простую таблицу, с которой будем экспериментировать.
— В левой панели найдите вашу базу данных flower_delivery
— Раскройте Schemas → public → Tables
— Нажмите правой кнопкой на Tables → Create → Table
В открывшемся окне:
— В поле Name введите test_table
— На вкладке Columns добавите колонку id с типом integer и отметьте NOT NULL
— Добавьте колонку name с типом text
(скриншот: pgadmin_create_table_setup.png — настройка таблицы)
Нажмите Save. Таблица создана. Теперь она появится в списке Tables.
РАБОТА С ТАБЛИЦАМИ
Посмотреть данные:
Нажмите правой кнопкой мыши на таблицу → View/Edit Data → All Rows.
Откроется окно с содержимым таблицы.
Посмотреть структуру:
Раскройте таблицу → Columns. Там указаны все колонки, их типы и ограничения.
Добавление данных в тестовую таблицу:
— Откройте таблицу test_table через View/Edit Data → All Rows
— В появившемся окне нажмите на значок + (Add row) или кликните в последнюю пустую строку
— Заполните поля:
— В колонку id введите 1
— В колонку name введите Тестовая запись 1
— Нажмите Save Data Changes (иконка дискеты) или F6
(скриншот: pgadmin_insert_row.png — вставка строки)
Добавьте ещё две строки:
Сохраните изменения воспользовавшись кнопкой Save Data Changes
(скриншот: pgadmin_save_data.png — сохранение введенных данных)
TOOLS, ИНСТРУМЕНТЫ pgAdmin
В верхнем меню есть раздел Tools — здесь находятся основные инструменты:
— Query Tool — главное место для написания и выполнения SQL-запросов. Именно этот инструмент мы будем использовать для обучения на протяжении всей книги.
— Search Objects (Ctrl + G) — поиск по объектам базы данных.
— Import/Export — импорт и экспорт данных (например, из CSV).
ОПИСАНИЕ РАБОТЫ QUERY TOOL
Query Tool — это сердце pgAdmin. Здесь вы будете писать почти все запросы. Именно этот инструмент мы будем использовать для обучения на протяжении всей книги.
Как открыть:
Нажмите правой кнопкой на базе данных → Query Tool.
Или выберите базу → Tools → Query Tool.
Интерфейс:
— Верхняя часть — редактор кода (пишете запрос).
— Нижняя часть — результат выполнения (таблицы, сообщения, время выполнения).
— Кнопка Execute (>) или клавиша F5 — выполнить запрос.
— Кнопка Open File — загрузить запрос из файла.
— Кнопка Save — сохранить запрос.
Пример:
sql
SELECT * FROM test_table;
Напишите этот запрос в редакторе, нажмите Execute — и увидите данные из нашей тестовой таблицы (три добавленные строки).
НАСТРОЙКИ ПРОГРАММЫ pgAdmin
Чтобы изменить внешний вид или поведение pgAdmin:
В верхнем меню выберите File → Preferences.
Здесь можно настроить:
— тему (тёмная / светлая)
— размер шрифта в редакторе
— поведение при выполнении запросов (например, автоматически открывать результат в новой вкладке)
ТИПИЧНЫЕ ОШИБКИ
Ошибка 1. Не могу найти Query Tool.
Убедитесь, что выбрана база данных, и только потом открывайте Query Tool.
Ошибка 2. В левой панели нет сервера.
Проверьте, запущен ли PostgreSQL. В меню Пуск найдите SQL Shell (psql) и попробуйте подключиться. Если не запущен — перезапустите службу PostgreSQL.
ТОНКОСТИ И НЮАНСЫ
— В Query Tool можно открыть несколько вкладок — удобно, если работаете с разными запросами.
Посвящается:
— Результат запроса можно экспортировать в CSV — нажмите на иконку дискеты в панели результатов.
— Если запрос выполняется долго, можно остановить его кнопкой Cancel ([-]).
ИЗ ОПЫТА
Когда я только начинал работать с pgAdmin, я долго искал, где выполнить запрос.
Оказывается, нужно сначала выбрать базу данных, а потом открывать Query Tool.
С тех пор я всегда проверяю, какая база выбрана в дереве, перед тем как открыть Query Tool.
ВОПРОСЫ И ОТВЕТЫ
В: Можно ли работать с PostgreSQL без pgAdmin?
О: Да. Есть консольный клиент psql, но для новичков pgAdmin удобнее.
В: Как сменить язык интерфейса?
О: В File → Preferences → General → Language можно выбрать русский (если поддерживается).
ПОПРОБУЙ САМ
— Запустите pgAdmin.
— Найдите в дереве тестовую таблицу test_table.
ЗАДАЧИ НА ПОВТОРЕНИЕ
— Создайте таблицу test_table2 с помощью pgAdmin (через графический интерфейс). Добавьте колонки id (целое число, первичный ключ) и description (текст).
Шаг 4. Типы данных. Как создать свою первую таблицу и заполнить её данными
Первый шаг в мир структурированных данных
ЖИВОЙ ПРИМЕР
Вы уже установили PostgreSQL и научились открывать Query Tool.
Теперь самое время создать свою первую таблицу и заполнить её данными.
Это будет таблица для хранения цветов — простая, но показывающая все основные моменты.
В этом шаге мы рассмотрим два способа:
— Создание таблицы и вставка данных через визуальный интерфейс pgAdmin.
— Создание таблицы и вставка данных через SQL-запросы.
ЧТО ТАКОЕ ТАБЛИЦЫ В БАЗЕ ДАННЫХ
Таблица в базе данных — это как таблица в Excel или Word, но с одним важным отличием: каждая колонка имеет строго определённый тип данных.
Вы не можете в одну колонку записать и число, и текст — тип задаётся один раз при создании таблицы, и все данные в этой колонке должны соответствовать этому типу.
Правила именования таблиц и колонок:
Колонки таблиц в базе всегда имеют название, таблицы — тоже. Названия таблиц не должны повторяться в рамках одной схемы данных.
Наименования таблиц и колонок должны отвечать следующим базовым требованиям:
— Не должны начинаться с цифры
— Не должны содержать спецсимволы (допустимы только буквы, цифры и подчёркивание)
— Не должны совпадать с зарезервированными словами SQL (например, SELECT, TABLE, INSERT)
Примеры правильных имён:
sql
— правильно
CREATE TABLE employees (…);
CREATE TABLE orders_2026 (…);
CREATE TABLE user_profiles (…);
Примеры неправильных имён:
sql
— неправильно
CREATE TABLE 1employees (…); — начинается с цифры
CREATE TABLE employees-list (…); — содержит дефис
CREATE TABLE SELECT (…); — зарезервированное слово
ЧТО ТАКОЕ ТИПЫ ДАННЫХ
Тип данных — это правило, которое говорит базе данных: 'в этой колонке могут храниться только такие-то значения'.
Это помогает:
— не ошибаться — база не даст записать текст в колонку с числами
— экономить место — числа хранятся компактнее, чем текст
— быстро искать — базе проще сравнивать числа, чем длинные строки
Основные типы данных, которые мы будем использовать на первых шагах:
ЧТО ТАКОЕ ПЕРВИЧНЫЙ КЛЮЧ (PRIMARY KEY)
Первичный ключ — это уникальный идентификатор каждой строки в таблице.
Зачем он нужен:
— Чтобы каждая запись была уникальной.
— Чтобы можно было быстро найти строку.
— Чтобы другие таблицы могли ссылаться на эту запись (внешние ключи).
Свойства первичного ключа:
— Не может содержать NULL (пустое значение).
— Значения должны быть уникальными.
— В таблице может быть только один первичный ключ.
Примеры первичных ключей в реальной жизни:
— Номер заказа в интернет-магазине.
— ИНН человека.
— Номер телефона человека.
Что используют в качестве первичного ключа:
— Последовательность чисел (1, 2, 3, …) — самый распространённый подход.
— GUID / UUID — специальный набор символов, который гарантированно уникален во всём мире.
В таблице, которую мы создадим — employees (служащие), первичным ключом будет колонка id.
СПОСОБ 1. СОЗДАНИЕ ТАБЛИЦЫ ЧЕРЕЗ ВИЗУАЛЬНЫЙ ИНТЕРФЕЙС pgADMIN
Мы уже создавали таблицу в предыдущем шаге, но для закрепления повторим процесс ещё раз.
— Откройте базу данных.
— В левой панели pgAdmin раскройте Servers → PostgreSQL 18 → Databases.
— Выберите базу, с которой работаете (например, flower_delivery).
— Перейдите в раздел Tables.
— Раскройте Schemas → public → Tables.
— Создайте таблицу.
— Нажмите правой кнопкой мыши на Tables и выберите Create → Table.
— Задайте имя таблицы.
— В открывшемся окне в поле Name введите employees.
— Добавьте колонки.
— Перейдите на вкладку Columns и нажмите + для добавления колонок.
Добавьте следующие колонки:
— id — тип serial. Это будет первичный ключ. В разделе Constraints отметьте PRIMARY KEY.
— head_id — тип integer. В разделе Constraints отметьте NOT NULL.
— name — тип text. В разделе Constraints отметьте NOT NULL.
— position — тип text. Ограничения не требуются.
— salary — тип numeric. Ограничения не требуются.
— Сохраните таблицу.
— Нажмите Save.
СПОСОБ 1. ЗАПОЛНЕНИЕ ТАБЛИЦЫ ЧЕРЕЗ ВИЗУАЛЬНЫЙ ИНТЕРФЕЙС pgADMIN
Как и на предыдущем шаге, заполним таблицу данными через визуальный интерфейс.
— Откройте таблицу для редактирования.
— В левой панели pgAdmin найдите созданную таблицу employees.
— Нажмите на неё правой кнопкой мыши и выберите View/Edit Data → All Rows.
— Добавьте строки.
— В открывшемся окне вы увидите пустую таблицу.
— Нажмите на значок + (Add row) или кликните в последнюю строку (там, где отображается (NULL)).
— Заполните поля.
— Для каждой строки введите значения:
— Сохраните изменения.
— Нажмите Save Data Changes (иконка дискеты) или нажмите F6.
СПОСОБ 2. СОЗДАНИЕ ТАБЛИЦЫ ЧЕРЕЗ SQL
А теперь сделаем то же самое, но через SQL-запросы — этот способ профессиональнее и быстрее.
— Откройте Query Tool.
— В pgAdmin выберите базу данных (например, flower_delivery), затем в меню Tools → Query Tool.
— Напишите запрос на создание таблицы.
sql
CREATE TABLE employees (
id SERIAL PRIMARY KEY,
head_id INTEGER NOT NULL,
name TEXT NOT NULL,
position TEXT,
salary NUMERIC
);
Что тут происходит
— CREATE TABLE — команда, которая создаёт новую таблицу в базе данных.
— employees — имя таблицы.
— id SERIAL PRIMARY KEY — автоматически увеличиваемый уникальный номер, первичный ключ.
— head_id INTEGER NOT NULL — идентификатор руководителя, не может быть пустым.
— name TEXT NOT NULL — имя, не может быть пустым.
— position TEXT — должность, может быть пустой.
— salary NUMERIC — зарплата, может быть пустой.
— Выполните запрос.
— Нажмите кнопку Execute (▶) или клавишу F5.
СПОСОБ 2. ЗАПОЛНЕНИЕ ТАБЛИЦЫ ЧЕРЕЗ SQL
— Откройте Query Tool.
— В pgAdmin выберите базу данных (например, flower_delivery), затем в меню Tools → Query Tool.
— Напишите запрос на вставку данных.
sql
INSERT INTO employees (id, head_id, name, position, salary) VALUES
(1, 1, 'Анна', 'Директор', 100000),
(2, 1, 'Борис', 'Менеджер', 70000),
(3, 1, 'Виктор', 'Менеджер', 70000),
(4, 2, 'Галина', 'Продавец', 50000),
(5, 2, 'Дмитрий', 'Продавец', 50000),
(6, 3, 'Елена', 'Продавец', 50000),
(7, 2, 'Жанна', 'Продавец', 50000),
(8, 3, 'Зоя', 'Продавец', 50000),
(9, 1, 'Иван', 'Аналитик', 60000),
(10, 9, 'Кирилл', 'Стажёр', 30000);
Что тут происходит
— INSERT INTO employees — команда для добавления данных в таблицу.
— В скобках после имени таблицы перечислены колонки, в которые мы вставляем значения.
— VALUES — список строк с данными. Каждая строка в своих скобках.
— Выполните запрос.
— Нажмите кнопку Execute (▶) или клавишу F5.
— Проверьте результат.
sql
SELECT * FROM employees;
Что тут происходит
SELECT * FROM employees — выводит все строки из таблицы employees. Звёздочка * означает 'все колонки'.
ТИПИЧНЫЕ ОШИБКИ
Ошибка 1. Забыть указать NOT NULL для колонки, которая не должна быть пустой.
Неверно:
sql
CREATE TABLE employees (
id SERIAL PRIMARY KEY,
name TEXT
);
Если не указать NOT NULL, в колонку name можно вставить пустое значение (NULL), что часто приводит к проблемам.
Верно:
sql
CREATE TABLE employees (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL
);
Ошибка 2. Забыть указать PRIMARY KEY для колонки, которая должна быть уникальным идентификатором.
Без первичного ключа в таблице могут появиться дубликаты, и на неё будет сложно ссылаться из других таблиц.
Неверно:
sql
CREATE TABLE employees (
id SERIAL,
name TEXT
);
Верно:
sql
CREATE TABLE employees (
id SERIAL PRIMARY KEY,
name TEXT
);
Ошибка 3. Попытаться вставить строку с уже существующим значением первичного ключа.
Неверно:
sql
INSERT INTO employees (id, name) VALUES (1, 'Анна');
INSERT INTO employees (id, name) VALUES (1, 'Борис');
PostgreSQL выдаст ошибку: duplicate key value violates unique constraint.
Верно:
sql
INSERT INTO employees (id, name) VALUES (1, 'Анна');
INSERT INTO employees (id, name) VALUES (2, 'Борис');
Ошибка 4. Забыть кавычки для текстовых значений.
Неверно:
sql
INSERT INTO employees (name) VALUES (Анна);
PostgreSQL подумает, что Анна — это имя колонки или переменной, и выдаст ошибку.
Верно:
sql
INSERT INTO employees (name) VALUES ('Анна');
Ошибка 5. Использовать двойные кавычки для строк.
В SQL строки записываются в одинарных кавычках. Двойные кавычки используются для имён таблиц и колонок (если они содержат пробелы или совпадают с ключевыми словами).
Неверно:
sql
INSERT INTO employees (name) VALUES ('Анна');
Верно:
sql
INSERT INTO employees (name) VALUES ('Анна');
ТОНКОСТИ И НЮАНСЫ
— SERIAL — это не тип данных, а сокращение. На самом деле PostgreSQL создаёт последовательность (SEQUENCE) и устанавливает значение по умолчанию для колонки. При вставке, если не указать id, он заполнится автоматически.
— Кавычки. Для строк — одинарные: 'Анна'. Для имён таблиц и колонок — двойные: 'employees' (обычно не нужны, если имя не совпадает с ключевым словом). Для чисел — никаких кавычек: 100000.
— Порядок колонок в INSERT. Если вы перечисляете колонки, порядок не важен, главное — чтобы значения соответствовали. Если не перечисляете, значения должны идти в том же порядке, что и при создании таблицы.
sql
— порядок колонок не важен
INSERT INTO employees (name, salary, id) VALUES ('Анна', 100000, 1);
— порядок важен — значения должны идти в порядке колонок таблицы
INSERT INTO employees VALUES (1, 'Анна', 100000);
— NOT NULL и PRIMARY KEY. PRIMARY KEY автоматически подразумевает NOT NULL, но не наоборот. Можно создать колонку, которая не может быть пустой, но не является первичным ключом.
— Что делать, если таблица уже существует. Если вы уже создали таблицу, повторный запуск CREATE TABLE вызовет ошибку. Чтобы этого избежать, можно использовать CREATE TABLE IF NOT EXISTS:
sql
CREATE TABLE IF NOT EXISTS employees (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL
);
Что тут происходит
Команда CREATE TABLE IF NOT EXISTS проверяет, существует ли таблица с таким именем. Если таблицы нет — создаёт её. Если таблица уже есть — ничего не делает и не выдаёт ошибку. Это удобно, когда скрипт может выполняться несколько раз (например, при повторной настройке базы данных).
ИЗ ОПЫТА
Когда я только начинал работать с PostgreSQL, я создал таблицу employees без первичного ключа.
Через некоторое время в ней появились дубликаты — два сотрудника с одинаковым id.
Я долго не мог понять, почему при выборке по id возвращается две строки.
С тех пор я всегда добавляю PRIMARY KEY для таблиц, где это нужно, и обязательно проверяю, что колонки, которые не должны быть пустыми, помечены NOT NULL.
ВОПРОСЫ И ОТВЕТЫ
В: Можно ли создать таблицу с другим именем?
О: Да. Имя должно быть уникальным в рамках одной схемы. Старайтесь выбирать осмысленные имена, например employees, products, orders.
В: Что делать, если ошибка в синтаксисе?
О: PostgreSQL выдаст сообщение об ошибке. Внимательно прочитайте его — там обычно указано, где примерно ошибка и что не так. Проверьте кавычки, запятые, названия колонок.
В: Как удалить таблицу?
О: Используйте команду:
sql
DROP TABLE employees;
Что тут происходит
DROP TABLE удаляет таблицу и все данные в ней. Будьте осторожны — восстановить данные будет сложно.
В: Можно ли добавить колонку в уже существующую таблицу?
О: Да. Например:
sql
ALTER TABLE employees ADD COLUMN email TEXT;
Что тут происходит
ALTER TABLE изменяет структуру таблицы. В данном случае добавляется новая колонка email с типом TEXT.
ПОПРОБУЙ САМ
— Создайте таблицу flowers_first с колонками:
— id (SERIAL, PRIMARY KEY)
— name (TEXT, NOT NULL)
— price (NUMERIC)
— Вставьте в неё 3–4 цветка (например, Роза, Тюльпан, Лилия) с разными ценами.
— Выведите все строки из таблицы.
— (По желанию) Создайте такую же таблицу через визуальный интерфейс pgAdmin.
Шаг 5. Установка демонстрационной схемы: вручную и через GitHub
Готовим полигон для экспериментов
ЖИВОЙ ПРИМЕР
Представьте, что у вас реальная служба доставки цветов. Какая информация вам нужна для работы? Клиенты (кто заказывает), заказы (что, кому, когда), цветы (розы, тюльпаны, лилии), оттенки (красный, белый, розовый). Всё это есть в нашей демонстрационной схеме.
Вы уже создали свою первую таблицу и заполнили её данными.
Теперь настало время развернуть полноценную учебную базу — схему 'Доставка цветов'.
Все примеры в книге построены на этой схеме — как на настоящей базе данных для реальной службы доставки.
В ней есть клиенты, заказы, цветы, оттенки и города.
Вы можете установить её двумя способами:
— Вручную — выполнить SQL-скрипт, который создаст все таблицы и заполнит их данными.
— Через GitHub — скачать готовый скрипт из репозитория и выполнить его.
ЧТО ВХОДИТ В ДЕМОНСТРАЦИОННУЮ СХЕМУ
Схема состоит из пяти основных таблиц:
— flowers — цветы (название, цена)
— colors — оттенки (код, название)
— cities — города (название, население)
— clients — клиенты (телефон, имя, город, дата рождения)
— orders — заказы (клиент, цветок, оттенок, количество, дата, статус)
Все данные уже подготовлены: 8 цветов, 8 оттенков, 13 городов, 14 клиентов, 21 заказ.
Как это работает и как таблицы связаны между собой:
— Клиенты (clients) делают заказы. Каждый клиент живёт в каком-то городе (city_id ссылается на cities.id).
— Заказы (orders) содержат информацию о том, какой клиент (client_phone ссылается на clients.phone), какой цветок (flower_id ссылается на flowers.id), какой оттенок (color_code ссылается на colors.code), сколько цветов (quantity) и когда был сделан заказ (order_ts).
— Цветы (flowers) — это ассортимент (Роза, Тюльпан, Лилия и т.д.). У каждого цветка есть цена.
— Оттенки (colors) — это возможные цвета цветов (красный, белый, розовый и т.д.).
— Города (cities) — это города, в которых живут клиенты.
Схема связей:
text
clients.city_id → cities.id
orders.client_phone → clients.phone
orders. flower_id → flowers.id
orders.color_code → colors.code
Таким образом, чтобы получить полную информацию о заказе (имя клиента, название цветка, оттенок, город), нужно соединить несколько таблиц через эти связи.
СПОСОБ 1. УСТАНОВКА СХЕМЫ ВРУЧНУЮ
— Откройте Query Tool.
— В pgAdmin выберите базу данных (например, flower_delivery), затем в меню Tools → Query Tool.
— Скопируйте и выполните SQL-скрипт.
Ниже приведён полный скрипт для создания схемы.
Вы можете скопировать его и вставить в окно Query Tool, затем нажать Execute (>) или F5.
Если вы пользуетесь печатной версией книги, вы можете воспользоваться способом установки из GitHub или набрать текст от руки, но стоит учесть, что это трудоёмкий процесс.
sql
— =============================================
— Демонстрационная схема: Доставка цветов
— =============================================
— Удаление таблиц, если они существуют
DROP TABLE IF EXISTS orders;
DROP TABLE IF EXISTS clients;
DROP TABLE IF EXISTS cities;
DROP TABLE IF EXISTS colors;
DROP TABLE IF EXISTS flowers;
— Таблица цветов
CREATE TABLE flowers (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
price NUMERIC (10,2) NOT NULL CHECK (price> 0)
);
— Таблица оттенков
CREATE TABLE colors (
code CHAR (2) PRIMARY KEY,
name TEXT NOT NULL UNIQUE
);
— Таблица городов
CREATE TABLE cities (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL UNIQUE,
population INTEGER CHECK (population> 0)
);
— Таблица клиентов
CREATE TABLE clients (
phone TEXT PRIMARY KEY CHECK (phone ~ '^ [0—9] {9} $'),
name TEXT NOT NULL,
city_id INTEGER NOT NULL,
birth_date DATE
);
— Таблица заказов
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
client_phone TEXT NOT NULL,
flower_id INTEGER NOT NULL,
color_code CHAR (2) NOT NULL,
quantity INTEGER NOT NULL CHECK (quantity> 0),
order_ts TIMESTAMP NOT NULL,
status TEXT NOT NULL DEFAULT 'новый' CHECK (status IN ('новый', 'в доставке', 'доставлен', 'отменён'))
);
— Наполнение данными
INSERT INTO flowers (name, price) VALUES
('Роза', 150), ('Тюльпан', 90), ('Хризантема', 110),
('Лилия', 130), ('Пион', 200), ('Гербера', 80),
('Орхидея', 250), ('Ромашка', 60);
INSERT INTO colors (code, name) VALUES
('re', 'красный'), ('wh', 'белый'), ('pi', 'розовый'),
('ye', 'жёлтый'), ('pu', 'фиолетовый'), ('or', 'оранжевый'),
('bo', 'бордовый'), ('li', 'сиреневый');
INSERT INTO cities (name, population) VALUES
('Москва', 13000000), ('Санкт-Петербург', 5600000),
('Казань', 1300000), ('Новосибирск', 1600000),
('Екатеринбург', 1500000), ('Нижний Новгород', 1200000),
('Самара', 1150000), ('Ростов-на-Дону', 1130000),
('Уфа', 1120000), ('Красноярск', 1100000),
('Пермь', 1050000), ('Воронеж', NULL), ('Саратов', NULL);
INSERT INTO clients (phone, name, city_id, birth_date) VALUES
('991234567', 'Николай', 1, '1980-05-10'),
('112345678', 'Елена', 2, '1992-08-22'),
('993456789', 'Дмитрий', 1, '1985-11-03'),
('434567890', 'Анна', 3, NULL),
('115678901', 'Алексей', 2, '1978-03-15'),
('836789012', 'Ольга', 4, '1995-07-19'),
('437890123', 'Сергей', 5, NULL),
('998901234', 'Татьяна', 1, '1988-12-01'),
('439012345', 'Андрей', 3, '1991-04-25'),
('310123456', 'Мария', 6, '1983-09-12'),
('991112233', 'Ирина', 1, '1985-06-15'),
('112223344', 'Павел', 2, '1990-11-20'),
('433334455', 'Светлана', 3, NULL),
('834445566', 'Виктор', 4, NULL);
INSERT INTO orders (client_phone, flower_id, color_code, quantity, order_ts, status) VALUES
— Первые 9 — доставленные
('991234567', 2, 're', 5, '2026-02-10 10:15:00', 'доставлен'),
('112345678', 1, 'pi', 3, '2026-02-12 14:30:00', 'доставлен'),
('993456789', 5, 'wh', 7, '2026-02-15 09:45:00', 'доставлен'),
('991234567', 3, 'ye', 3, '2026-02-18 16:20:00', 'доставлен'),
('434567890', 2, 're', 11, '2026-02-20 12:10:00', 'доставлен'),
('115678901', 4, 'pu', 5, '2026-02-21 18:05:00', 'доставлен'),
('112345678', 6, 'or', 9, '2026-02-23 11:40:00', 'доставлен'),
('836789012', 1, 're', 7, '2026-02-25 13:25:00', 'доставлен'),
('437890123', 7, 'bo', 11, '2026-02-26 15:55:00', 'доставлен'),
— Следующие 6 — новые
('993456789', 2, 'ye', 11, '2026-02-27 08:15:00', 'новый'),
('998901234', 5, 'wh', 3, '2026-03-01 17:30:00', 'новый'),
('439012345', 3, 'pi', 5, '2026-03-02 10:45:00', 'новый'),
('434567890', 8, 'wh', 15, '2026-03-03 12:00:00', 'новый'),
('310123456', 1, 're', 5, '2026-03-05 19:20:00', 'новый'),
('112345678', 4, 'pu', 3, '2026-03-07 14:35:00', 'новый'),
— Дополнительные — в доставке и отменённые
('991234567', 3, 'ye', 3, '2026-03-10 11:20:00', 'в доставке'),
('112345678', 5, 'wh', 1, '2026-03-11 13:45:00', 'в доставке'),
('993456789', 1, 're', 3, '2026-03-12 09:10:00', 'в доставке'),
('434567890', 4, 'pu', 3, '2026-03-13 16:30:00', 'отменён'),
('115678901', 2, 're', 3, '2026-03-14 12:00:00', 'отменён'),
('836789012', 7, 'bo', 1, '2026-03-15 14:15:00', 'отменён');
— Проверочный запрос
SELECT COUNT (*) AS total_orders FROM orders;
— Проверьте результат.
— После выполнения в нижней части окна появится результат проверочного запроса — количество заказов (должно быть 21).
СПОСОБ 2. УСТАНОВКА СХЕМЫ ЧЕРЕЗ GITHUB
— Перейдите в репозиторий книги.
— Откройте браузер и перейдите по ссылке:
— https://github.com/atvcross/posgreesqlbook
(скриншот: github_repo.png)
— Скачайте скрипт.
Вариант А. Скачать отдельный файл.
Нажмите на файл schema/flower_delivery. sql, затем на кнопку Raw (или Download Raw File).
Сохраните файл на компьютер.
Вариант Б. Скачать весь репозиторий ZIP-архивом.
На главной странице репозитория нажмите зелёную кнопку Code, выберите Download ZIP.
Распакуйте архив, затем найдите файл schema/flower_delivery. sql в распакованной папке.
— Выполните скрипт в pgAdmin.
— Откройте Query Tool.
— Нажмите Open File (иконка папки) и выберите скачанный файл.
— Нажмите Execute (▶) или F5.
ТИПИЧНЫЕ ОШИБКИ
Ошибка 1. Забыть выбрать правильную базу данных перед выполнением скрипта.
Убедитесь, что в Query Tool выбрана нужная база (например, flower_delivery).
Ошибка 2. Выполнить скрипт повторно, не удалив старые таблицы.
Если таблицы уже существуют, скрипт может выдать ошибку. В нашем скрипте есть DROP TABLE IF EXISTS, поэтому он безопасен для повторного выполнения.
Ошибка 3. Ошибки в кодировке.
Если при вставке данных возникают проблемы с русским текстом, проверьте кодировку базы данных (должна быть UTF8).
Создать базу с правильной кодировкой можно командой:
sql
CREATE DATABASE flower_delivery WITH ENCODING 'UTF8';
ТОНКОСТИ И НЮАНСЫ
— Почему DROP TABLE IF EXISTS? Это делает скрипт безопасным для повторного выполнения. Если таблицы уже есть, они удаляются и создаются заново. Без этой проверки при повторном запуске скрипт выдал бы ошибку.
— Порядок удаления таблиц важен. Сначала удаляются таблицы, на которые никто не ссылается (orders), потом те, на которые ссылаются (clients, flowers, colors, cities). Если попытаться удалить clients раньше orders, PostgreSQL выдаст ошибку, потому что orders ссылается на clients.
— CHECK в колонках. Например, CHECK (price> 0) гарантирует, что цена не может быть отрицательной. Если попытаться вставить цветок с ценой -100, PostgreSQL выдаст ошибку.
— Почему в заказах только нечётное количество цветов? В России чётное количество цветов дарят только на похороны. Чтобы не вызывать неприятных ассоциаций, в схеме используются только нечётные значения.
ИЗ ОПЫТА
Когда я только начинал работать с PostgreSQL, я часто выполнял скрипты, не глядя на порядок удаления таблиц.
Из-за этого возникали ошибки типа 'таблица не может быть удалена, потому что на неё ссылаются'.
С тех пор я всегда использую DROP TABLE IF EXISTS и указываю таблицы в правильном порядке.
А однажды я забыл про кодировку и вставил русский текст в базу с кодировкой SQL_ASCII.
Текст отображался кракозябрами. Пришлось пересоздавать базу с UTF8.
ВОПРОСЫ И ОТВЕТЫ
В: Можно ли установить схему без удаления старых данных?
О: Да, если вы уверены, что таблицы не существуют, можно убрать DROP TABLE IF EXISTS. Но для чистоты эксперимента лучше выполнить скрипт как есть.
В: Что делать, если скрипт выдаёт ошибку?
О: Прочитайте сообщение об ошибке. Чаще всего проблема в том, что выбрана не та база данных или уже есть таблицы с другими ограничениями. Если ошибка непонятна, попробуйте выполнять скрипт построчно (по одному оператору).
В: Нужно ли каждый раз удалять и создавать схему заново?
О: Нет. Если вы уже установили схему, можете не выполнять скрипт повторно. Если данные испортились — можно выполнить ещё раз.
В: Почему в скрипте есть DROP TABLE IF EXISTS, а не просто DROP TABLE?
О: DROP TABLE IF EXISTS не вызывает ошибку, если таблицы нет. Это делает скрипт безопасным для повторного выполнения.
ПОПРОБУЙ САМ
— Установите демонстрационную схему одним из способов.
— Выполните проверочный запрос: SELECT COUNT (*) FROM orders; — должно быть 21.
— Посмотрите структуру таблиц в pgAdmin: раскройте Schemas → public → Tables и изучите колонки каждой таблицы.
Шаг 6. Как использовать демонстрационную схему
Начинаем работать с готовой базой данных
ЖИВОЙ ПРИМЕР
Вы установили PostgreSQL, создали свою первую таблицу и развернули демонстрационную схему 'Доставка цветов'.
Теперь у вас есть готовая база данных с клиентами, заказами, цветами и городами.
Эта схема будет использоваться на протяжении всей книги.
Все примеры, от простого SELECT до сложных аналитических функций, будут построены на этих данных.
Вы сможете выполнять запросы, экспериментировать, видеть результат сразу — и постепенно осваивать язык SQL.
В этом шаге мы разберём, как с ней работать:
— как открыть Query Tool и выполнить запрос
— как посмотреть данные в таблицах
— как быстро найти нужную таблицу
— как вернуться к схеме, если что-то забыли
ПЕРВЫЕ ШАГИ В PGADMIN
— Подключитесь к базе данных.
— В левой панели pgAdmin раскройте Servers → PostgreSQL 18 → Databases.
— Найдите вашу базу postgres или flower_delivery. Если нужной базы нет — создайте новую (см. раздел 'Как создать новую базу данных' ниже).
— Откройте Query Tool.
— Нажмите правой кнопкой мыши на базе данных и выберите Query Tool.
— Или выберите базу, затем в меню Tools → Query Tool.
— Напишите и выполните простой запрос.
sql
SELECT * FROM flowers;
Нажмите Execute (>) или F5. В нижней части окна появится список всех цветов.
КАК СОЗДАТЬ НОВУЮ БАЗУ ДАННЫХ В PGADMIN
Если базы данных flower_delivery нет в списке, вы можете создать её самостоятельно.
Способ 1. Через визуальный интерфейс pgAdmin:
— В левой панели pgAdmin раскройте Servers → PostgreSQL 18 (или ваша версия)
— Нажмите правой кнопкой мыши на Databases
— Выберите Create → Database…
(скриншот: create_database_menu.png)
— В открывшемся окне в поле Database введите имя: flower_delivery
— В поле Owner оставьте postgres (или выберите другого пользователя)
— Нажмите Save
(скриншот: create_database_window.png)
Новая база данных flower_delivery появится в списке Databases.
Способ 2. Через SQL-запрос в Query Tool:
— Откройте Query Tool (подключившись к любой существующей базе, например postgres)
— Выполните команду:
sql
CREATE DATABASE flower_delivery;
Будет создана база данных с именем flower_delivery.
Дополнительные параметры (необязательно):
sql
CREATE DATABASE flower_delivery
OWNER postgres
ENCODING 'UTF8'/* */
LC_COLLATE 'ru_RU. UTF-8' /* */
LC_CTYPE 'ru_RU. UTF-8';
Создаёт базу с владельцем postgres, кодировкой UTF8 и русской локалью.
После создания базы:
— Обновите список баз (нажмите F5 или правой кнопкой на Databases → Refresh)
— Выберите новую базу flower_delivery
— Выполните скрипт установки демонстрационной схемы из шага 5
КАК ПОСМОТРЕТЬ ДАННЫЕ В ТАБЛИЦЕ
Способ 1. Через Query Tool.
Напишите SELECT * FROM имя_таблицы; и выполните.
Способ 2. Через визуальный интерфейс.
В левой панели раскройте Schemas → public → Tables.
Нажмите правой кнопкой мыши на нужную таблицу и выберите View/Edit Data → All Rows.
КАК БЫСТРО НАЙТИ НУЖНУЮ ТАБЛИЦУ
В pgAdmin есть поиск по объектам:
— Нажмите Ctrl + G (или в меню Tools → Search Objects).
— Введите название таблицы (например, flowers).
— Нажмите Enter — pgAdmin покажет найденные объекты.
ЕСЛИ ЗАБЫЛИ СТРУКТУРУ ТАБЛИЦЫ
Способ 1. Через Query Tool.
Выполните SELECT * FROM имя_таблицы LIMIT 0; — вы увидите только названия колонок и их типы.
Способ 2. Через визуальный интерфейс.
В левой панели найдите таблицу, раскройте её → Columns.
Там отображаются все колонки с типами и ограничениями.
КАК ВЕРНУТЬСЯ К СХЕМЕ, ЕСЛИ ЧТО-ТО ЗАБЫЛИ
— В шаге 7 подробно описана структура всех таблиц (колонки, типы, связи).
— В репозитории GitHub есть файл schema/README.md с кратким описанием схемы.
— Всегда можно выполнить SELECT * FROM таблица LIMIT 5;, чтобы посмотреть примеры данных.
КРАТКОЕ ОПИСАНИЕ ДЕМОНСТРАЦИОННОЙ СХЕМЫ — ДОСТАВКА ЦВЕТОВ
Таблицы и колонки:
Таблица employees является дополнительной и используется только в некоторых шагах (например, для демонстрации иерархических запросов и самообъединений).
Номера телефонов в таблице clients сгенерированы случайным образом и не соответствуют реальным абонентам.
Связи между таблицами:
— clients.city_id → cities.id
— orders.client_phone → clients.phone
— orders. flower_id → flowers.id
— orders.color_code → colors.code
Особенности:
— Статусы заказов: 'новый', 'в доставке', 'доставлен', 'отменён'.
ТИПИЧНЫЕ ОШИБКИ
Ошибка 1. Выбрать не ту базу данных перед выполнением запроса.
Убедитесь, что в Query Tool указана правильная база (например, flower_delivery).
Это видно в верхней части окна Query Tool.
Ошибка 2. Забыть точку с запятой в конце запроса.
PostgreSQL позволяет выполнять запросы без точки с запятой, но если запросов несколько — без неё будет ошибка.
Ошибка 3. Пытаться открыть таблицу, которая ещё не создана.
Если вы не выполнили скрипт из шага 5, таблиц не будет. Вернитесь к шагу 5 и установите схему.
ТОНКОСТИ И НЮАНСЫ
— SELECT * FROM имя_таблицы LIMIT 0; — полезный приём, чтобы посмотреть структуру таблицы без загрузки данных.
— В Query Tool можно сохранять запросы. Нажмите Save (иконка дискеты) и дайте файлу имя — например, my_queries. sql.
— Вкладки в Query Tool. Можно открыть несколько вкладок, чтобы работать с разными запросами.
ИЗ ОПЫТА
Когда я только начинал, я часто путал базы данных и выполнял запросы не в той базе.
С тех пор я всегда проверяю имя базы в верхней части Query Tool перед выполнением.
А ещё я сохраняю часто используемые запросы в файлы — это экономит время.
ВОПРОСЫ И ОТВЕТЫ
В: Можно ли работать с базой данных без pgAdmin?
О: Да. Есть консольный клиент psql, но для начала pgAdmin удобнее.
В: Что делать, если я забыл пароль от базы данных?
О: При установке PostgreSQL вы задавали пароль. Если забыли — можно сбросить его по инструкции в шаге 88.
В: Можно ли выполнять запросы, не открывая Query Tool?
О: Можно через консольный клиент psql, но в рамках книги мы используем pgAdmin.
ПОПРОБУЙ САМ
— Откройте Query Tool и выполните SELECT * FROM flowers;.
— Посмотрите данные в таблице clients через визуальный интерфейс (View/Edit Data).
— Найдите таблицу orders через поиск (Ctrl + G).
— Посмотрите структуру таблицы orders с помощью SELECT * FROM orders LIMIT 0;.
Шаг 7. Подробное описание демонстрационной схемы
Знакомство с Доставкой цветов
ЖИВОЙ ПРИМЕР
Вы уже установили PostgreSQL, создали свою первую таблицу и заполнили её данными.
Теперь настало время познакомиться с главным учебным полигоном этой книги — схемой 'Доставка цветов'.
Все примеры в книге построены на этой схеме.
В ней есть клиенты, заказы, цветы, оттенки и города.
Она достаточно проста, чтобы быстро в ней разобраться, и достаточно богата, чтобы показывать сложные запросы.
ТАБЛИЦЫ СХЕМЫ
Схема состоит из пяти основных таблиц и одной дополнительной.
1. flowers — цветы
2. colors — оттенки
3. cities — города
4. clients — клиенты
5. orders — заказы
6. employees — сотрудники (дополнительная таблица)
Таблица employees используется только в некоторых шагах (например, для демонстрации иерархических запросов и самообъединений).
СВЯЗИ МЕЖДУ ТАБЛИЦАМИ
— clients.city_id → cities.id — каждый клиент живёт в каком-то городе.
— orders.client_phone → clients.phone — заказ сделан клиентом.
— orders. flower_id → flowers.id — в заказе указан цветок.
— orders.color_code → colors.code — в заказе указан оттенок.
— employees. head_id → employees.id — иерархия сотрудников (кто кому подчиняется).
ДАННЫЕ В ТАБЛИЦАХ
Схема заполнена тестовыми данными:
— Цветы: 8 наименований с ценами от 60 до 250 рублей.
— Оттенки: 8 вариантов (красный, белый, розовый и т.д.).
— Города: 13 городов, в том числе с NULL-населением.
— Клиенты: 14 клиентов, у некоторых нет даты рождения.
— Заказы: 21 заказ с разными статусами и датами.
— Сотрудники: 10 сотрудников с иерархией подчинения.
Номера телефонов в таблице clients сгенерированы случайным образом и не соответствуют реальным абонентам.
ПОЧЕМУ ВЫБРАНА ИМЕННО ЭТА СХЕМА
— Понятна без объяснений — все знают, что такое цветы, заказы, клиенты.
— Достаточно сложна — есть связи один-ко-многим (клиент → заказы), многие-ко-многим (заказы → цветы через flower_id).
— Позволяет показывать все конструкции SQL — от простого SELECT до оконных функций и LATERAL.
ТИПИЧНЫЕ ОШИБКИ
Ошибка 1. Путать внешние ключи.
Например, orders.client_phone ссылается на clients.phone, а не на clients.id.
Ошибка 2. Забывать про NULL в birth_date и population.
В таблицах clients и cities есть NULL-значения. Это важно учитывать при написании запросов.
ТОНКОСТИ И НЮАНСЫ
— phone — первичный ключ в таблице clients. Это необычно (обычно первичный ключ — числовой), но так сделано для простоты демонстрации.
— status в orders имеет ограничение CHECK — только четыре допустимых значения.
ИЗ ОПЫТА
Когда я учил SQL, мне постоянно попадались абстрактные схемы вроде table1, table2.
Это мешало сосредоточиться на языке — я постоянно забывал, что за данные в какой таблице.
Поэтому для этой книги я выбрал схему, которая сразу понятна.
'Доставка цветов' — это живая, простая и в то же время полноценная база данных.
ВОПРОСЫ И ОТВЕТЫ
В: Нужно ли запоминать все колонки и связи?
О: Нет. Со временем вы запомните главные. В любой момент можно вернуться к этому шагу и освежить память.
В: Можно ли изменить схему или добавить свои данные?
О: Да, это ваша база данных. Но для работы с примерами из книги лучше оставить схему как есть.
В: Для чего нужна таблица employees?
О: Она используется в некоторых продвинутых шагах для демонстрации иерархических запросов (например, поиск всех подчинённых сотрудника).
ПОПРОБУЙ САМ
— Откройте pgAdmin и подключитесь к базе данных.
— Найдите таблицу flowers. Посмотрите, какие в ней колонки.
— Найдите таблицу orders. Посмотрите, на какие таблицы она ссылается.
— Нарисуйте на бумаге или в заметках схему связей между таблицами.
ЗАДАЧИ НА ПОВТОРЕНИЕ
— С помощью pgAdmin посмотрите структуру всех таблиц демо-схемы.
— Раскройте Schemas → public → Tables, изучите колонки каждой таблицы (имя, тип, ограничения). Объясните для себя, что значит каждая колонка и каждая таблица, для каких данных предназначена, какой тип данных используется.
Шаг 8. Учимся выбирать. Простой оператор SELECT
Выбираем очень много данных
ЖИВОЙ ПРИМЕР
В предыдущих главах мы разбирали структуру таблиц, типы данных и вставку информации. Но как эти данные оттуда достать?
Ведь даже если мы правильно создали таблицы и соблюли все правила, тонкости и нюансы, информация в базе останется просто бесполезной, если не показать её на экране.
Хорошая новость: в языке SQL есть замечательный оператор, который позволит нам не только извлекать данные, но и выполнять с ними сложные преобразования перед выводом.
Это оператор SELECT — и он просто незаменим.
Он почти магический: может быть как простым, так и многообразным. И, что важно, полностью удовлетворяет требованиям пользователя по выбору и обработке информации из базы.
Извлечение данных из базы называется запросом (query).
Постепенно мы научимся писать большие запросы с группировками, подзапросами и аналитическими функциями. Но мы пойдём тем же путём, что и всегда: начнём с самого простого.
Напишем простой оператор SELECT — запрос, который выберет все данные о клиентах нашей службы доставки.
Напоминаю: данные для демонстрационной схемы сгенерированы случайным образом.
Комментарии в коде
Комментарии не влияют на выполнение запроса, но помогают понять, что делает код.
Однострочный комментарий:
два символа минус подряд без пробела
sql
— –Это комментарий
SELECT * FROM flowers
Многострочный комментарий:
sql
/*
Многострочный
комментарий
*/
SELECT * FROM flowers;
Когда использовать:
— объяснить сложную логику
— временно отключить часть запроса
— оставить пометку для себя или коллег
ПРОСТОЙ SELECT
sql
SELECT phone, name FROM clients
Что тут происходит
— SELECT phone, name — перечисляем колонки, которые хотим получить
— FROM clients — указываем таблицу, из которой извлекаем данные
ПСЕВДОНИМЫ (АЛИАСЫ) КОЛОНОК
Иногда названия колонок в таблице слишком длинные или не совсем понятны в выводе.
Или мы хотим дать столбцу более удобное имя прямо в результатах запроса.
Для этого используются псевдонимы (алиасы) — временные имена, которые мы назначаем колонкам.
Синтаксис:
sql
SELECT имя_колонки AS псевдоним
FROM имя_таблицы
Или без AS (сокращённо):
sql
SELECT имя_колонки псевдоним
FROM имя_таблицы
Пример:
sql
SELECT name AS flower_name, price AS flower_price
FROM flowers
Результат:
text
flower_name flower_price
Роза 150
Тюльпан 90
Хризантема 110
Лилия 130
Пион 200
Гербера 80
Орхидея 250
Ромашка 60
Псевдонимы flower_name и flower_price временно заменяют имена колонок в выводе
Правила наименования псевдонимов:
— Не должен содержать спецсимволы (пробелы, знаки препинания, кроме подчёркивания _)
— Не должен начинаться с цифры
— Не должен совпадать с зарезервированными командами PostgreSQL (SELECT, FROM, WHERE, AND, OR, NOT и т.д.)
Правильно:
sql
SELECT name AS flower_name, price AS flower_price FROM flowers;
SELECT name AS name_1, price AS price_1 FROM flowers;
Неправильно:
sql
SELECT name AS flower name, price AS flower price FROM flowers; — содержит пробел
SELECT name AS 1name, price AS 2price FROM flowers; — начинается с цифры
SELECT name AS SELECT, price AS FROM FROM flowers; — зарезервированное слово
Если очень нужно использовать пробел или спецсимвол, можно взять псевдоним в двойные кавычки:
sql
SELECT name AS 'flower name', price AS 'flower price' FROM flowers;
Но на практике лучше так не делать — код становится менее читаемым.
ВЫБОР ВСЕХ КОЛОНОК (ЗВЁЗДОЧКА *)
Иногда нам требуется показать все данные из всех колонок некоторой таблицы. Для этого используется специальный символ * (звёздочка).
sql
SELECT * FROM cities
sql
SELECT * FROM colors
Звёздочка * заменяет перечень полей. Вместо того чтобы перечислять все колонки вручную, мы просто говорим: дай всё
РАБОТА В PGADMIN
Для выполнения запросов в pgAdmin используется специальный интерфейс Query Tool.
— Воспользуйтесь меню Tools → Query Tool
— Напишите запрос в открывшейся форме ввода
— Для выполнения нажмите кнопку в виде стрелки вправо или значка Play (>) в верхней части интерфейса
— Если у вас несколько запросов в окне, выделите нужный и нажмите Play — выполнится именно он
— После нажатия данные появятся в нижней части формы
ПРИМЕРЫ
Пример 1. Наименование и стоимость цветов
sql
SELECT name, price
FROM flowers
Результат (фрагмент):
text
name price
Роза 150.00
Тюльпан 90.00
Хризантема 110.00
…
Пример 2. Оттенки цветов
sql
SELECT name FROM colors
Пример 3. Города и население
sql
SELECT name, population
FROM cities
Результат (фрагмент):
text
name population
Москва 13000000
Санкт-Петербург 5600000
Казань 1300000
…
ТИПИЧНЫЕ ОШИБКИ
Ошибка 1. Путать колонки с одинаковыми именами из разных таблиц.
В примерах выше мы выбирали колонку name из таблиц flowers, colors и cities. Это колонки разных таблиц, и данные в них, соответственно, разные. Имя колонки само по себе не означает, что в ней хранится одно и то же.
Ошибка 2. Забыть точку с запятой в конце запроса.
В pgAdmin это не всегда критично, но если запросов несколько, без точки с запятой будет ошибка.
ТОНКОСТИ И НЮАНСЫ
— SELECT * удобен для быстрого просмотра, но в реальных проектах лучше перечислять колонки явно. Это делает запрос понятнее и защищает от изменений в структуре таблицы.
— Запросы SELECT можно писать как строчными, так и заглавными буквами — синтаксис PostgreSQL не имеет ограничений. Но рекомендуется выделять ключевые слова (SELECT, FROM, WHERE и другие) заглавными буквами. Это делает код более читабельным.
— Запросы можно переносить на несколько строк — синтаксис это допускает.
ИЗ ОПЫТА
Запросы SELECT можно писать как строчными, так и заглавными буквами — синтаксис PostgreSQL не имеет ограничений. Но я рекомендую выделять ключевые слова (SELECT, FROM, WHERE и другие) заглавными буквами.
Это делает код более читабельным, удобным для быстрого сканирования, а поиск ошибок становится заметно быстрее.
Также запросы можно переносить на несколько строк — синтаксис это допускает и даже приветствует.
ВОПРОСЫ И ОТВЕТЫ
В: Как посмотреть все колонки, которые есть в таблице, зная её название?
О: Используйте запрос:
sql
SELECT * FROM название_таблицы
Он покажет все данные, а заодно и все колонки — их названия будут в заголовках результата
ПОПРОБУЙ САМ
— Выберите названия и цены цветов из таблицы flowers. Используйте псевдонимы: flower_name и flower_price.
— Выберите из таблицы clients номера телефонов и имена клиентов. Дайте столбцам псевдонимы client_phone и client_name.
— Выберите все данные об оттенках.
— Выберите названия городов и численность населения.
— Выберите все данные о заказах.
— Выберите только даты заказов.
ЗАДАЧИ НА ПОВТОРЕНИЕ
— Изучите самостоятельно интерфейс программы pgAdmin.
Шаг 9. Куда же без WHERE?
Простые условия
ЖИВОЙ ПРИМЕР
Мы научились извлекать данные из таблиц с помощью простых запросов. Но в реальной работе нам почти всегда встречаются таблицы с большим количеством строк — тысячи, десятки тысяч или даже миллионы записей.
Человек не в силах проанализировать такие объёмы информации вручную. Гораздо удобнее ограничить вывод строк некоторыми условиями.
Для этого служит оператор
WHERE
Именно с его помощью мы задаём условия отбора строк, которые должны попасть в результат запроса.
В условиях, заданных в операторе WHERE, можно использовать:
— математические выражения: +, -, *, /
— операции сравнения:>, <, =,!=,> =, <=
Синтаксис запроса с условием выглядит так:
sql
SELECT перечень_полей
FROM имя_таблицы
WHERE условие_отбора
Например, нам срочно нужно вывести только те строки из таблицы flowers, где цена цветка больше ста рублей:
sql
SELECT *
FROM flowers
WHERE price> 100
А теперь выведем данные из таблицы clients, где имя клиента — Андрей:
sql
SELECT *
FROM clients
WHERE name = 'Андрей'/* */
ПРИМЕРЫ
Пример 1. Выведем названия городов, где проживает более миллиона человек:
sql
SELECT name
FROM cities
WHERE population> 1000000
Выбираем названия городов, у которых население превышает 1 000 000 человек.
Результат:
text
name
Москва
Санкт-Петербург
Казань
…
Пермь
Пример 2. Выведем информацию о клиентах с именем Анна:
sql
SELECT *
FROM clients
WHERE name = 'Анна' /* */
Выбираем все колонки из таблицы clients, где имя клиента равно 'Анна'.
Результат:
text
phone name city_id birth_date
434567890 Анна 3 NULL
Пример 3. Узнаем стоимость цветка 'Ромашка':
sql
SELECT price
FROM flowers
WHERE name = 'Ромашка' /* */
Выбираем цену цветка, название которого 'Ромашка'.
Результат:
text
price
60
Пример 4. Выведем все цветы, которые стоят меньше ста рублей:
sql
SELECT *
FROM flowers
WHERE price <100
Выбираем все колонки из таблицы flowers, где цена меньше 100 рублей.
Результат:
text
id name price
2 Тюльпан 90
6 Гербера 80
8 Ромашка 60
Пример 5. Выведем заказы, где количество цветов больше десяти:
sql
SELECT *
FROM orders
WHERE quantity> 10
Выбираем все колонки из таблицы orders, где количество цветов больше 10.
Результат (фрагмент):
text
id client_phone flower_id color_code quantity order_ts status
5 434567890 2 re 11 2026-02-20 12:10:00 доставлен
9 437890123 7 bo 11 2026-02-26 15:55:00 доставлен
10 993456789 2 ye 11 2026-02-27 08:15:00 новый
13 434567890 8 wh 15 2026-03-03 12:00:00 новый
ТИПИЧНЫЕ ОШИБКИ
Ошибка 1. Пропуск кавычек при работе со строками.
Неверно:
sql
SELECT *
FROM clients
WHERE name = Андрей
PostgreSQL воспримет Андрей как имя колонки, а не как значение.
Верно:
sql
SELECT *
FROM clients
WHERE name = 'Андрей' /* */
Ошибка 2. Лишние кавычки при работе с числами.
Неверно:
sql
SELECT *
FROM flowers
WHERE price> '100' /* */
Верно:
sql
SELECT *
FROM flowers
WHERE price> 100
Ошибка 3. Использование сравнений 'больше' или 'меньше' для строк.
Неверно:
sql
SELECT *
FROM clients
WHERE name> 'Виктор' /* */
Верно:
sql
SELECT *
FROM clients
WHERE name = 'Виктор' /* */
ИЛИ
sql
SELECT *
FROM clients
WHERE name!= 'Виктор' /* */
Важно помнить: номер телефона в нашей схеме тоже хранится как строка (тип TEXT). Это особенность нашей демонстрационной базы — в других системах может быть иначе.
ТОНКОСТИ И НЮАНСЫ
Часто нам нужны только первые несколько строк таблицы. Это бывает полезно при работе с большими объёмами данных или для быстрой проверки запроса.
В PostgreSQL для этого используется ключевое слово LIMIT:
sql
SELECT *
FROM clients
LIMIT 4
Можно сочетать LIMIT с условием WHERE:
sql
SELECT *
FROM cities
WHERE population> 100000
LIMIT 5
ИЗ ОПЫТА
Старайтесь объяснять самому себе, что делает каждый SQL-запрос, простыми словами. Например:
'Выбери все заказы, где количество больше десяти'.
Это не только ускоряет обучение, но и помогает быстрее находить ошибки.
ВОПРОСЫ И ОТВЕТЫ
В: Можно ли использовать несколько условий в WHERE?
О: Да, но мы рассмотрим это в следующем шаге.
В: Можно ли в условии отбора использовать колонки, которые не будут показаны в результате?
О: Конечно. Пример:
sql
SELECT status
FROM orders
WHERE quantity> 10
Здесь мы выводим только статус заказов, но условие накладывается на колонку quantity, которая в результат не попадает.
В: Как выбрать все значения, кроме заданного?
О: Используйте оператор неравенства!=. Например, чтобы выбрать всех клиентов, кроме Виктора:
sql
SELECT *
FROM clients
WHERE name!= 'Виктор' /* */
ПОПРОБУЙ САМ
— Выберите названия первых трёх городов, где население превышает один миллион человек.
— Выберите всю информацию о клиенте с номером телефона 993456789. Помните: номер телефона — строка, поэтому в условии WHERE его нужно заключать в кавычки.
— Выберите телефоны всех клиентов, кроме тех, кого зовут Ольга.
— Выберите все города, где население меньше полутора миллионов человек.
ЗАДАЧИ НА ПОВТОРЕНИЕ
— Выберите все данные о заказах из таблицы orders. Используйте SELECT *.
— Выберите названия и цены цветов из таблицы flowers. Используйте псевдонимы: flower_name и flower_price.
— Выберите из таблицы clients номера телефонов и имена клиентов. Дайте столбцам псевдонимы client_phone и client_name.
Шаг 10. Одно условие WHERE — скучно. Добавляем AND, OR, NOT, IN, BETWEEN
Когда одного условия становится мало
ЖИВОЙ ПРИМЕР
Вы уже умеете фильтровать данные с помощью WHERE. Но что, если нужно отобрать заказы со статусом 'новый' или 'в доставке'? Или найти города с населением от 1 до 5 миллионов? Или исключить клиентов с определёнными именами?
Для этого в SQL есть логические операторы AND, OR, NOT, а также операторы IN и BETWEEN. Они позволяют комбинировать условия и делать фильтрацию гибкой и точной.
AND — логическое И
AND выбирает строку только тогда, когда все условия верны.
sql
SELECT name, population
FROM cities
WHERE population> 1000000 AND population <1500000
Выводим города с населением больше миллиона и меньше полутора миллионов
sql
SELECT *
FROM orders
WHERE status = 'новый' AND quantity <15
Заказы со статусом 'новый' и количеством цветов меньше 15
OR — логическое ИЛИ
OR выбирает строку, если хотя бы одно из условий истинно.
sql
SELECT *
FROM orders
WHERE status = 'новый' OR status = 'в доставке' /* */
Заказы со статусом 'новый' или 'в доставке'
IN — проверка вхождения в список
IN позволяет отобрать строки, где значение входит в заданный список.
sql
SELECT *
FROM flowers
WHERE name IN ('Хризантема', 'Тюльпан', 'Пион')
Цветы из списка: Хризантема, Тюльпан или Пион
sql
SELECT *
FROM clients
WHERE city_id IN (1, 2, 3)
Клиенты из городов с id 1, 2 или 3
BETWEEN — проверка диапазона
BETWEEN выбирает строки, где значение попадает в указанный диапазон (включая границы).
sql
SELECT name, population
FROM cities
WHERE population BETWEEN 1000000 AND 1500000
Города с населением от 1 миллиона до 1.5 миллионов (включительно)
sql
SELECT *
FROM orders
WHERE order_ts BETWEEN '2026-02-01'/* */
AND '2026-02-28'/* */
Заказы за февраль 2026 года
NOT — логическое отрицание
NOT делает условие противоположным. Используется с IN, BETWEEN, LIKE и другими операторами.
sql
SELECT *
FROM flowers
WHERE name NOT IN ('Хризантема', 'Тюльпан', 'Пион')
Все цветы, кроме перечисленных
sql
SELECT name, population
FROM cities
WHERE population NOT BETWEEN 1000000 AND 1500000
Города с населением меньше 1 миллиона или больше 1.5 миллионов
ПРИМЕРЫ С КОМБИНАЦИЯМИ
Пример 1. Заказы со статусом 'новый' или 'в доставке' с количеством меньше 10
sql
SELECT *
FROM orders
WHERE (status = 'новый' OR status = 'в доставке') AND quantity <10
Скобки определяют порядок вычисления условий
Результат (фрагмент):
text
id client_phone status quantity
17 112345678 в доставке 1
18 993456789 в доставке 3
…
Пример 2. Клиенты с именем Дмитрий, Анна или Алексей, а также клиент с телефоном 998901234
sql
SELECT *
FROM clients
WHERE name IN ('Дмитрий', 'Анна', 'Алексей') OR phone = '998901234' /* */
Результат (фрагмент):
text
phone name
112345678 Елена
434567890 Анна
993456789 Дмитрий
115678901 Алексей
998901234 Татьяна
…
Пример 3. Города с населением от 1 до 1.5 миллионов или более 5 миллионов
sql
SELECT name, population
FROM cities
WHERE population BETWEEN 1000000 AND 1500000
OR population> 5000000
Результат (фрагмент):
text
name population
Москва 13000000
Санкт-Петербург 5600000
Казань 1300000
Новосибирск 1600000
…
ТИПИЧНЫЕ ОШИБКИ
Ошибка 1. Путать AND и OR.
Неверно:
sql
SELECT * FROM clients WHERE name = 'Анна' AND name = 'Дмитрий' /* */
Ни одна строка не может быть одновременно Анной и Дмитрием
Верно:
sql
SELECT * FROM clients WHERE name = 'Анна' OR name = 'Дмитрий' /* */
Начинающие часто путают AND и OR, ожидая, что AND вернёт строки, где имя равно любому из значений. На самом деле AND требует выполнения всех условий одновременно, OR — хотя бы одного.
Ошибка 2. Забывать скобки при смешивании AND и OR.
Неверно:
sql
SELECT * FROM orders WHERE status = 'новый' OR status = 'в доставке' AND quantity <10
AND имеет приоритет выше, чем OR. Без скобок условие интерпретируется как: status = 'новый' OR (status = 'в доставке' AND quantity <10)
Верно:
sql
SELECT * FROM orders WHERE (status = 'новый' OR status = 'в доставке') AND quantity <10
Ошибка 3. Путать IN со списком строк и BETWEEN с диапазоном.
IN работает с перечислением значений, BETWEEN — с диапазоном.
ТОНКОСТИ И НЮАНСЫ
— BETWEEN включает границы диапазона. WHERE date BETWEEN '2026-02-01' AND '2026-02-28' включает заказы за 1 и 28 февраля.
— Для работы с датами без времени используйте приведение типов: order_ts::date BETWEEN '2026-02-01' AND '2026-02-28' /* */
— IN можно использовать с подзапросами (об этом в следующих шагах)
— NOT IN с подзапросами, возвращающими NULL, может дать неожиданный результат (все строки будут исключены)
ИЗ ОПЫТА
Бывают задачи, когда из запроса необходимо убрать некий ненужный перечень значений. Для этого наиболее рационально использовать логический оператор NOT в сочетании с IN.
sql
SELECT *
FROM flowers
WHERE name NOT IN ('Ромашка', 'Тюльпан', 'Лилия')
А однажды я забыл скобки в сложном условии с AND и OR и получил не те данные, которые ожидал. С тех пор я всегда использую скобки, даже если кажется, что они не нужны.
ВОПРОСЫ И ОТВЕТЫ
В: Можно ли комбинировать более двух условий?
О: Да, можно комбинировать столько условий, сколько необходимо.
sql
SELECT * FROM orders
WHERE quantity> 10 AND status = 'новый' AND order_ts> '2026-03-01' /* */
В: Для чего используются скобки в логических выражениях?
О: Скобки определяют порядок вычисления условий. Оператор AND имеет более высокий приоритет, чем OR, поэтому без скобок условия могут вычисляться не так, как вы ожидаете.
В: Можно ли сочетать IN и BETWEEN?
О: Да.
sql
SELECT name, population
FROM cities
WHERE population BETWEEN 1000000 AND 1500000
AND name IN ('Казань', 'Новосибирск', 'Екатеринбург')
ПОПРОБУЙ САМ
— Выберите из таблицы городов (cities) все города, кроме Москвы, Казани, Новосибирска. Используйте NOT IN.
— Выберите из таблицы заказов (orders) только те заказы, где количество цветов больше 5 и меньше или равно 10. Используйте BETWEEN.
— Выберите одним запросом из таблицы клиентов всех клиентов, которых зовут Анна или Андрей, а также клиента с номером телефона 993456789.
— Выберите из таблицы оттенков (colors) красный, белый, жёлтый оттенки. Используйте OR.
— Выберите из таблицы цветов (flowers) цветы, которые стоят от 80 до 130 рублей, а также 'Орхидею'. Используйте BETWEEN и OR.
— Выберите из таблицы цветов (flowers) цветы, которые не стоят от 80 до 130 рублей. Используйте NOT BETWEEN.
— Выберите из таблицы заказов (orders) только те заказы, где количество цветов равно 3, 4 или 15 и статус 'новый'. Используйте IN и AND.
Шаг 11. WHERE и LIKE. Ищем неточное совпадение
Когда вроде бы точно знаешь, но не до конца
ЖИВОЙ ПРИМЕР
Иногда нам нужно найти что-то, но мы не знаем точного значения.
Например: 'Найди всех клиентов, у которых в имени есть буква „и“'.
Мы не знаем, какое имя, но знаем, что в нём есть эта буква.
Для таких случаев в SQL есть оператор LIKE.
LIKE
Простой, но мощный оператор для работы с текстом.
Он ищет по шаблону (образцу), а не по точному совпадению.
Как это работает
В LIKE используются два специальных символа:
— % — соответствует любому количеству символов (даже нолю)
— _ — соответствует ровно одному символу
[***] — ШАБЛОНЫ LIKE (краткая таблица)
ПРИМЕРЫ
Пример 1. Выберите всех клиентов, чей телефон начинается с 9:
sql
SELECT *
FROM clients
WHERE phone LIKE '9%' /* */
Мы говорим базе: 'Найди всех, у кого телефон начинается с 9, а дальше может быть что угодно'.
Результат:
text
phone name city_id birth_date
991234567 Николай 1 1980-05-10
993456789 Дмитрий 1 1985-11-03
998901234 Татьяна 1 1988-12-01
991112233 Ирина 1 1985-06-15
Пример 2. Выберите всех клиентов, чей телефон заканчивается на 33:
sql
SELECT *
FROM clients
WHERE phone LIKE '%33' /* */
Шаблон '%33' означает: 'сначала любые символы, а в конце обязательно 33'.
Результат:
text
phone name city_id birth_date
991112233 Ирина 1 1985-06-15
Пример 3. Выберите всех клиентов, чьё имя содержит букву 'и':
sql
SELECT *
FROM clients
WHERE name LIKE '%и%' /* */
Шаблон '%и%' ищет букву 'и' в любом месте строки: до неё может быть что угодно, и после неё — тоже.
Результат:
text
phone name city_id birth_date
991234567 Николай 1 1980-05-10
993456789 Дмитрий 1 1985-11-03
310123456 Мария 6 1983-09-12
991112233 Ирина 1 1985-06-15
834445566 Виктор 4 NULL
Пример 4. Выберите города, в названии которых есть буква 'д' или 'Д':
sql
SELECT name, population
FROM cities
WHERE name LIKE '%д%' OR name LIKE '%Д%' /* */
LIKE чувствителен к регистру, поэтому нужны оба варианта.
Результат:
text
name population
Нижний Новгород 1200000
Ростов-на-Дону 1130000
Пример 5. Выберите города, в названии которых нет буквы 'у':
sql
SELECT name, population
FROM cities
WHERE name NOT LIKE '%у%' /* */
Оператор NOT LIKE исключает строки, которые соответствуют шаблону.
Результат:
text
name population
Москва 13000000
Казань 1300000
Новосибирск 1600000
Нижний Новгород 1200000
Екатеринбург 1500000
Пермь 1050000
Воронеж NULL
Саратов NULL
ТИПИЧНЫЕ ОШИБКИ
Ошибка 1. Путать LIKE и регулярные выражения.
LIKE ищет по простым шаблонам с % и _. Регулярные выражения — это отдельный язык. Они не взаимозаменяемы.
Ошибка 2. Забыть про регистр.
LIKE чувствителен к регистру. Если ищете 'москва', а в базе 'Москва' — LIKE не найдёт. Для поиска без учёта регистра используйте ILIKE.
Ошибка 3. Слишком сложный шаблон.
Новички иногда пишут очень длинные шаблоны. Если нужно сложное правило, лучше использовать регулярные выражения.
ТОНКОСТИ И НЮАНСЫ
— LIKE работает только с текстовыми типами данных. Если применить к числу, PostgreSQL выдаст ошибку.
— ILIKE — версия LIKE без учёта регистра. Работает так же, но не чувствительна к регистру.
— Чтобы найти сам символ % или _, их нужно экранировать. Например, LIKE '100\%' найдёт строку 100%. По умолчанию символ экранирования — обратная косая черта.
ИЗ ОПЫТА
Однажды я искал заказы, у которых номер телефона начинался на определённые цифры.
Я использовал LIKE и получил ошибку, потому что забыл, что LIKE работает только с текстом.
Пришлось преобразовывать колонку через::text.
С тех пор, когда вижу LIKE и числа, сразу вспоминаю про преобразование типов.
ВОПРОСЫ И ОТВЕТЫ
В: Можно ли использовать LIKE с числами?
О: Нет. Нужно сначала преобразовать число в текст: phone::text LIKE '9%'.
В: Что ищет шаблон '%'?
О: Все строки (пустые и непустые). Потому что % соответствует любому количеству символов, включая ноль.
В: Как найти строку, содержащую символ %?
О: Экранировать: LIKE '100\%'. По умолчанию символ экранирования — \.
ПОПРОБУЙ САМ
— Найди всех клиентов, у которых в имени есть буква 'а'.
— Найди города, название которых начинается на 'Н'.
— Найди города, которые заканчиваются на 'бург'.
— Найди всех клиентов, у которых в номере телефона есть две одинаковые цифры подряд (например, 11, 22, 33).
— Подсказка: используй LIKE с % несколько раз.
Шаг 12. Запросы и вычисления
Математика, математикой, а запросы пишутся всегда
ЖИВОЙ ПРИМЕР
У вас есть таблица заказов. Вам нужно посчитать общую стоимость заказа, умножив количество цветов на их цену.
Цена хранится в одной таблице (flowers), количество — в другой (orders).
Как вывести результат прямо в запросе?
В SQL можно вычислять новые значения прямо в запросе.
Для этого используются:
— арифметические операции (+, -, *, /, %)
— математические функции (ABS, ROUND, POWER и другие)
— логические выражения (AND, OR, NOT)
АРИФМЕТИЧЕСКИЕ ОПЕРАЦИИ
ПРИМЕРЫ
Пример 1. Увеличить цену цветка на 10%
sql
SELECT name,
price,
price * 1.1 AS price_with_vat
FROM flowers;
Вычисляем цену с наценкой 10% и выводим в отдельной колонке.
Результат (фрагмент):
text
name price price_with_vat
Роза 150 165.00
Тюльпан 90 99.00
Пример 2. Посчитать стоимость заказа
sql
SELECT o.id,
o. quantity,
f.price,
o. quantity * f.price AS total
FROM orders o
JOIN flowers f ON o. flower_id = f.id;
Умножаем количество цветов на цену и выводим как отдельную колонку.
Пример 3. Остаток от деления (проверка на чётность)
sql
SELECT id,
quantity,
quantity % 2 AS remainder
FROM orders;
Вычисляем остаток от деления количества на 2.
Результат (фрагмент):
text
id quantity remainder
1 5 1
2 3 1
3 7 1
4 2 0
Пример 4. Сложное выражение в WHERE
sql
SELECT name, price
FROM flowers
WHERE price * 1.2> 200;
Отбираем цветы, цена которых с наценкой 20% превышает 200 рублей.
Пример 5. Использование математических функций
sql
SELECT name,
price,
ROUND (price * 0.9, 2) AS price_with_discount,
ABS (price — 100) AS diff_from_100
FROM flowers;
ROUND — округляет до двух знаков. ABS — возвращает абсолютное значение (модуль).
ТИПИЧНЫЕ ОШИБКИ
Ошибка 1. Деление целых чисел.
В PostgreSQL деление целых чисел даёт целый результат (остаток отбрасывается).
Неверно:
sql
SELECT 5 / 2; — вернёт 2, а не 2.5
Верно:
sql
SELECT 5 / 2.0; — вернёт 2.5
Ошибка 2. Смешивать типы данных в вычислениях.
Если сложить число и текст, PostgreSQL выдаст ошибку.
Неверно:
sql
SELECT price + '10' FROM flowers;
Верно:
sql
SELECT price +10 FROM flowers;
Ошибка 3. Забывать, что NULL в вычислениях даёт NULL.
Если в вычислении участвует NULL, результат будет NULL.
ТОНКОСТИ И НЮАНСЫ
— Порядок операций такой же, как в математике: сначала умножение и деление, потом сложение и вычитание. Скобки меняют порядок.
— Математических функций в PostgreSQL очень много: ABS, CEIL, FLOOR, ROUND, POWER, SQRT, EXP, LOG, SIN, COS, TAN и другие.
— Логические выражения возвращают TRUE, FALSE или NULL.
— Вычисляемые колонки можно использовать в ORDER BY и GROUP BY.
ИЗ ОПЫТА
Однажды я считал стоимость заказа, но забыл, что цена хранится в NUMERIC, а количество — в INTEGER.
Всё работало, но когда я попытался разделить сумму на количество, получил неожиданный результат из-за целочисленного деления.
С тех пор я всегда привожу числа к NUMERIC перед делением.
ВОПРОСЫ И ОТВЕТЫ
В: Можно ли использовать вычисляемую колонку в WHERE?
О: Да, но нужно повторить выражение. Алиас в WHERE использовать нельзя.
sql
SELECT price * 1.1 AS price_with_vat
FROM flowers
WHERE price * 1.1> 200;
В: Как получить остаток от деления?
О: Используйте оператор %.
В: Что такое ROUND (price, 2)?
О: Округляет число до двух знаков после запятой.
ПОПРОБУЙ САМ
— Выведи цену цветка, увеличенную на 20%.
— Выведи стоимость заказа (количество × цена) для каждого заказа.
— Выведи только те заказы, где количество цветов нечётное.
— Найди цветы, цена которых после скидки 15% меньше 100 рублей.
— Выведи квадратный корень из цены для каждого цветка.
Шаг 13. А еще мы можем использовать подзапросы WHERE IN и WHERE EXISTS
Куда же без маленького запроса внутри большого
ЖИВОЙ ПРИМЕР
Представьте, что вы работаете с таблицей заказов. Вам нужно найти всех клиентов, которые делали заказы в определённый период.
Но имя клиента хранится в другой таблице. Как это сделать?
В SQL для этого есть подзапросы — запросы внутри запросов.
Они позволяют использовать результат одного запроса в условии другого.
ПОДЗАПРОС С IN
IN проверяет, входит ли значение в список, полученный из подзапроса.
Задача: вывести клиентов, которые живут в городах с населением больше 5 миллионов.
sql
SELECT name
FROM clients
WHERE city_id IN (SELECT id FROM cities WHERE population> 5000000)
Внутренний запрос находит ID городов-миллионников (Москва, Санкт-Петербург). Внешний запрос выбирает клиентов, чей city_id есть в этом списке.
ПОДЗАПРОС С EXISTS
EXISTS проверяет наличие строк в подзапросе. Он не сравнивает значения, а отвечает на вопрос: Есть ли хоть одна строка, которая подходит под условие?
Задача: вывести клиентов, у которых есть хотя бы один отменённый заказ.
sql
SELECT name
FROM clients c
WHERE EXISTS (SELECT 1 FROM orders o WHERE o.client_phone = c.phone AND o.status = 'отменён')
Для каждого клиента выполняется подзапрос к таблице заказов. Если находится хотя бы один заказ с его телефоном и статусом 'отменён' — EXISTS возвращает TRUE. Такой клиент попадает в результат.
ПРИМЕРЫ
Пример 1. IN с текстовыми значениями
sql
SELECT name
FROM flowers
WHERE name IN ('Роза', 'Тюльпан', 'Лилия')
Что тут происходит
— IN ('Роза', 'Тюльпан', 'Лилия') — проверяет, содержится ли значение колонки name в указанном списке
— Запрос вернёт только те цветы, название которых совпадает с одним из трёх значений
— Это краткая форма записи вместо нескольких условий через OR
Результат:
text
name
Роза
Тюльпан
Лилия
Пример 2. EXISTS с дополнительным условием
sql
SELECT name
FROM clients c
WHERE EXISTS (
SELECT 1
FROM orders o
WHERE o.client_phone = c.phone
AND o. quantity> 10
)
Что тут происходит
— Внешний запрос перебирает всех клиентов из таблицы clients
— Для каждого клиента выполняется подзапрос, который ищет заказы этого клиента с количеством цветов больше 10
— Если подзапрос находит хотя бы один такой заказ — EXISTS возвращает TRUE
— Клиент попадает в результат, если EXISTS вернул TRUE
Результат (фрагмент):
text
name
Николай
Дмитрий
Анна
Пример 3. NOT EXISTS — клиенты без заказов
sql
SELECT name
FROM clients c
WHERE NOT EXISTS (SELECT 1 FROM orders o WHERE o.client_phone = c.phone)
Что тут происходит
— NOT EXISTS — противоположность EXISTS
— Для каждого клиента выполняется подзапрос, который ищет его заказы
— Если подзапрос не находит ни одного заказа — NOT EXISTS возвращает TRUE
— В результат попадают только клиенты, у которых нет ни одного заказа
Результат (фрагмент):
text
name
Светлана
Виктор
Пример 4. NOT IN — цветы, которые не заказывали
sql
SELECT name
FROM flowers
WHERE id NOT IN (SELECT DISTINCT flower_id FROM orders)
Что тут происходит
— Внутренний подзапрос (SELECT DISTINCT flower_id FROM orders) возвращает список ID цветов, которые хоть раз заказывали
— NOT IN проверяет, что ID цветка не входит в этот список
— В результат попадают только цветы, которые ни разу не заказывали
Результат (фрагмент):
text
name
Гербера
Ромашка
Пример 5. Вложенный IN (два уровня)
sql
SELECT name
FROM clients
WHERE city_id IN (
SELECT id FROM cities
WHERE population> 1000000 AND name LIKE 'М%'/* */
)
Что тут происходит
— Самый глубокий подзапрос (SELECT id FROM cities WHERE population> 1000000 AND name LIKE 'М%') находит ID городов-миллионников, названия которых начинаются на букву 'М' (например, Москва)
— Результат этого подзапроса — список ID городов
— Внешний запрос выбирает клиентов, чей city_id есть в этом списке
Результат (фрагмент):
text
name
Николай
Дмитрий
Татьяна
В ЧЁМ РАЗНИЦА МЕЖДУ IN И EXISTS
ТОНКОСТИ И НЮАНСЫ
— EXISTS можно использовать с любым подзапросом — неважно, что он возвращает. Достаточно того, что он вернул хотя бы одну строку.
— IN требует, чтобы подзапрос возвращал ровно один столбец — это принципиальное отличие.
— Планировщик PostgreSQL часто оптимизирует IN и EXISTS одинаково, но при больших объёмах данных EXISTS может оказаться быстрее, потому что он останавливается после первого найденного совпадения.
ИЗ ОПЫТА
Однажды я писал отчёт по заказам и использовал IN с подзапросом, который возвращал 50 000 ID клиентов. Запрос выполнялся больше минуты.
Я заменил на EXISTS — время упало до 0.3 секунды.
Почему? EXISTS останавливается после первого же совпадения. IN сначала собирает весь список, а потом его проверяет.
С тех пор моё правило:
— короткие списки — IN
— проверка наличия связей — EXISTS
ВОПРОСЫ И ОТВЕТЫ
В: Можно ли использовать IN с текстовыми значениями?
О: Да. Главное, чтобы типы в подзапросе и в основном запросе совпадали.
В: А если подзапрос ничего не вернул?
О: Тогда IN вернёт FALSE, и ни одна строка не будет выбрана. EXISTS тоже вернёт FALSE.
В: Какой оператор быстрее?
О: Зависит от данных. В большинстве случаев современный PostgreSQL оптимизирует оба варианта хорошо. Но если один из них работает медленно — попробуйте другой.
ПОПРОБУЙ САМ
— Выведи всех клиентов, которые заказывали цветы с ценой больше 200 рублей.
Подсказка: используй IN и два вложенных подзапроса.
— Выведи всех клиентов, у которых есть заказы со статусом 'в доставке'.
Подсказка: используй EXISTS.
— Найди города, в которых нет ни одного клиента.
Подсказка: используй NOT EXISTS.
— Выведи названия цветов, которые ни разу не заказывали.
Подсказка: используй NOT IN.
ЗАДАЧИ НА ПОВТОРЕНИЕ
— Выведите цену цветка, увеличенную на 20%. (шаг 12)
— Выведите стоимость заказа (количество × цена) для каждого заказа. (шаг 12)
Шаг 14. Поговорим о сортировке ORDER BY
Как навести порядок во вселенском хаосе, или почему без ORDER BY нельзя
ЖИВОЙ ПРИМЕР
Представьте, что вы смотрите список клиентов в базе данных. Вы просите базу:
'Покажи всех клиентов'.
База честно выводит всех. Но в каком порядке?
А никаком. Просто в том, в каком они были добавлены. Это как если бы вам выдали телефонную книгу, где страницы перемешаны.
Чтобы этого избежать, в SQL есть сортировка. Оператор ORDER BY наводит порядок.
ПРОСТАЯ СОРТИРОВКА
Задача: вывести всех клиентов, отсортировав их по имени.
sql
SELECT name, phone
FROM clients
ORDER BY name;
Сортирует по полю name в алфавитном порядке (A → Я).
Результат (фрагмент):
text
name phone
Алексей 115678901
Андрей 439012345
Анна 434567890
Виктор 834445566
Дмитрий 993456789
…
СОРТИРОВКА ПО УБЫВАНИЮ
По умолчанию ORDER BY сортирует по возрастанию (ASC). Если нужно наоборот — от последнего к первому, используем DESC.
Задача: вывести всех клиентов, отсортированных по дате рождения от самых молодых к самым старшим.
sql
SELECT name, birth_date
FROM clients
WHERE birth_date IS NOT NULL
ORDER BY birth_date DESC;
DESC означает 'по убыванию'. Самые поздние даты (молодые) будут первыми.
СОРТИРОВКА ПО НЕСКОЛЬКИМ ПОЛЯМ
Иногда одного поля мало. Например: сначала все клиенты из Москвы, а внутри Москвы — по алфавиту.
Задача: вывести клиентов, сначала из Москвы, потом из Санкт-Петербурга, а внутри каждого города — по имени.
sql
SELECT name, city_id
FROM clients
ORDER BY city_id, name;
*Сначала сортируем по city_id (Москва — 1, Санкт-Петербург — 2), а внутри каждого города — по имени.*
СОРТИРОВКА ПО НОМЕРУ СТОЛБЦА
Вместо имени колонки можно указать её номер в списке SELECT.
sql
SELECT name, phone
FROM clients
ORDER BY 2;
2 означает 'второй столбец в SELECT' — то есть phone.
Внимание! Такой способ иногда используют, чтобы не писать длинные имена. Но он делает запрос менее понятным, поэтому лучше так не злоупотреблять.
СОРТИРОВКА И LIMIT
Очень часто сортировку используют вместе с LIMIT, чтобы выбрать 'топ' или 'первые'.
Задача: вывести трёх самых молодых клиентов.
sql
SELECT name, birth_date
FROM clients
WHERE birth_date IS NOT NULL
ORDER BY birth_date DESC
LIMIT 3;
Сначала убираем клиентов без даты рождения. Сортируем по дате рождения от молодых к старшим. Берём только первые три строки.
Задача: вывести пять самых больших заказов по количеству цветов.
sql
SELECT id, quantity
FROM orders
ORDER BY quantity DESC
LIMIT 5;
Сортируем заказы по количеству от большего к меньшему и берём первые пять.
ТИПИЧНЫЕ ОШИБКИ
Ошибка 1. Путать ASC и DESC.
sql
ORDER BY name DESC — сортировка от Я до А
ORDER BY name ASC — сортировка от А до Я (по умолчанию)
Ошибка 2. Забыть ORDER BY перед LIMIT.
sql
SELECT * FROM orders LIMIT 5
Это просто первые пять строк в том порядке, как они лежат в базе. Если нужен топ, обязательно нужен ORDER BY.
Ошибка 3. Сортировка по номеру столбца при изменении SELECT.
Если вы измените порядок колонок в SELECT, сортировка по номеру может перестать работать так, как ожидалось.
ТОНКОСТИ И НЮАНСЫ
Сортировка и NULL
NULL — это 'неизвестно'. В PostgreSQL при сортировке по возрастанию NULL идут в конце, при убывании — в начале.
Если нужно изменить это поведение, используют NULLS FIRST или NULLS LAST.
sql
SELECT name, birth_date
FROM clients
ORDER BY birth_date NULLS LAST;
ИЗ ОПЫТА
Однажды я писал отчёт, где нужно было вывести самые популярные цветы. Я отсортировал по количеству заказов, но забыл DESC. Получился список самых НЕпопулярных цветов. Клиент был в замешательстве.
С тех пор, когда вижу ORDER BY, всегда проверяю: А точно ли я хочу от меньшего к большему?
ВОПРОСЫ И ОТВЕТЫ
В: Можно ли сортировать по нескольким полям с разным направлением?
О: Да. Например: сначала по городу по возрастанию, а внутри города — по имени по убыванию.
sql
ORDER BY city_id ASC, name DESC
В: Сортировка влияет на производительность?
О: Да. Если данных много, сортировка требует ресурсов. Иногда имеет смысл сортировать уже на стороне приложения.
В: Что будет, если не указать ORDER BY?
О: Порядок строк будет непредсказуемым. Обычно это порядок добавления, но гарантии нет. Поэтому если порядок важен — всегда указывайте ORDER BY.
ПОПРОБУЙ САМ
— Выведи список клиентов, отсортированный по дате рождения (от молодых к старшим).
— Выведи список городов, отсортированный по названию (от А до Я).
— Выведи список заказов, отсортированный сначала по статусу (новые, в доставке, доставленные, отменённые), а внутри статуса — по дате заказа.
— Выведи трёх клиентов с самой ранней датой рождения.
— Выведи пять самых больших заказов по количеству цветов.
Шаг 15. Больше подзапросов хороших и разных
Как заглянуть внутрь запроса
ЖИВОЙ ПРИМЕР
Представьте, что вы в службе доставки цветов. Вам нужно вывести список клиентов и рядом с каждым — название города, в котором он живёт.
Название города хранится в другой таблице — cities. Как это сделать одним запросом?
С помощью подзапроса в SELECT.
ПСЕВДОНИМЫ ДЛЯ ПОДЗАПРОСОВ
Когда мы используем подзапрос в FROM, ему обязательно нужно дать имя — псевдоним.
Это как временное имя для таблицы, которая существует только внутри запроса.
sql
SELECT * FROM (SELECT name FROM clients) temp
temp — это псевдоним временной таблицы. Без него PostgreSQL выдаст ошибку.
Правила создания псевдонимов:
Верно: Правильно: client_names, client_names_1, client_data
Если очень нужно использовать пробел или спецсимвол, можно взять псевдоним в двойные кавычки:
sql
SELECT *
FROM (SELECT name FROM clients) ’client names'/* */
Но на практике лучше так не делать — код становится менее читаемым.
ПОДЗАПРОСЫ В SELECT
Подзапрос в SELECT возвращает одно значение для каждой строки внешнего запроса.
Задача: вывести имя клиента и название его города.
sql
SELECT name,
(SELECT name FROM cities WHERE id = c.city_id) AS city_name
FROM clients c
Что тут происходит
— Внешний запрос выбирает всех клиентов
— Для каждого клиента выполняется подзапрос, который находит название города по city_id
— Результат подзапроса выводится в отдельной колонке
Результат (фрагмент):
text
name city_name
Николай Москва
Елена Санкт-Петербург
Дмитрий Москва
Анна Казань
…
ПОДЗАПРОСЫ В FROM
Подзапрос в FROM создаёт временную таблицу, с которой можно работать как с обычной.
Задача: вывести клиентов из Москвы, используя подзапрос.
sql
SELECT name, city_name
FROM (SELECT name,
(SELECT name FROM cities WHERE id = c.city_id) AS city_name
FROM clients c) client_city
WHERE city_name = 'Москва'/* */
Что тут происходит
— Внутренний запрос (подзапрос в FROM) создаёт временную таблицу client_city с колонками name и city_name
— Внешний запрос выбирает из этой временной таблицы строки, где city_name = 'Москва' /* */
Результат (фрагмент):
text
name city_name
Николай Москва
Дмитрий Москва
Татьяна Москва
ВЛОЖЕННЫЕ ПОДЗАПРОСЫ
Подзапросы можно вкладывать друг в друга. Главное — не запутаться.
Задача: вывести клиентов, которые живут в том же городе, что и клиент с телефоном 991234567 (Николай).
sql
SELECT name
FROM clients
WHERE city_id = (SELECT city_id
FROM clients
WHERE phone = '991234567')
Что тут происходит
— Самый глубокий подзапрос находит city_id Николая
— Внешний запрос выбирает всех клиентов с таким же city_id
— Сам Николай тоже попадёт в результат — это нормально
Результат (фрагмент):
text
name
Николай
Дмитрий
Татьяна
ТИПИЧНЫЕ ОШИБКИ
Ошибка 1. Подзапрос в SELECT возвращает несколько строк.
Неверно:
sql
SELECT name, (SELECT name FROM cities) FROM clients
Подзапрос вернёт много строк — получим ошибку.
Верно:
sql
SELECT name, (SELECT name FROM cities WHERE id = c.city_id) FROM clients c
Подзапрос должен возвращать одно значение.
Ошибка 2. Забыть псевдоним для подзапроса в FROM.
Неверно:
sql
SELECT * FROM (SELECT name, phone FROM clients)
Верно:
sql
SELECT * FROM (SELECT name, phone FROM clients) client_data
Ошибка 3. Подзапрос в WHERE возвращает несколько строк, а вы используете =.
Неверно:
sql
SELECT name FROM clients WHERE city_id = (SELECT id FROM cities WHERE population> 5000000)
Если городов с населением больше 5 миллионов несколько — ошибка.
Верно:
sql
SELECT name FROM clients WHERE city_id IN (SELECT id FROM cities WHERE population> 5000000)
ТОНКОСТИ И НЮАНСЫ
— Подзапрос в SELECT должен возвращать ровно одно значение.
— Подзапрос в FROM всегда должен иметь псевдоним (имя временной таблицы).
— Подзапрос в WHERE может возвращать несколько значений, если используется с IN, ANY, ALL.
— Подзапросы могут быть вложенными, но это усложняет чтение.
ИЗ ОПЫТА
Однажды мне нужно было вывести список клиентов и название их города, но я не мог использовать JOIN (были ограничения на уровне доступа).
Я использовал подзапрос в SELECT — и всё заработало.
А когда понадобилось отфильтровать клиентов по городу, я обернул этот запрос в подзапрос в FROM и наложил условие. Получилось громоздко, но работало.
С тех пор я знаю: если JOIN не подходит, подзапросы выручат.
ВОПРОСЫ И ОТВЕТЫ
В: Подзапрос в SELECT — это же медленно?
О: Может быть, если данных много. Но на небольших объёмах вполне приемлемо. Для больших объёмов лучше использовать JOIN.
В: Почему подзапрос в FROM требует псевдонима?
О: Потому что база данных должна знать, как обращаться к этой временной таблице. Псевдоним — это её имя.
В: Можно ли использовать подзапрос в WHERE с IN?
О: Да, это мы уже проходили.
ПОПРОБУЙ САМ
— Выведи имя клиента и название его города (используй подзапрос в SELECT).
— Выведи имена клиентов, которые живут в Москве, используя подзапрос в FROM.
— Найди клиентов, которые живут в том же городе, что и клиент с телефоном 993456789 (Дмитрий).
— Найди клиентов, которые живут в городах с населением больше 5 000 000.
Шаг 16. Преобразование типов данных в PostgreSQL Превращаем одно в другое
Из мухи в слона, или буквы в цифры, а цифры в… непечатные буквы
ЖИВОЙ ПРИМЕР
Представьте, вы снова в службе доставки цветов, и вы срочно ищете все заказы от первого января. Новый год — людям очень нужны цветы, чтобы поздравить родных и близких.
Но вот беда: дата заказа хранится как TIMESTAMP, а в отчёте нужно выбрать только те, где дата ровно 1 января.
Вы пишете:
sql
SELECT * FROM orders WHERE
order_ts = '2026-01-01' /* */
И получаете пустой результат. Почему? Потому что order_ts хранит ещё и время: 2026-01-01 10:15:00, 2026-01-01 14:30:00 — они не равны просто дате.
Что делать? Нужно превратить TIMESTAMP в DATE, чтобы сравнить только дату.
В SQL есть преобразование типов. Это как взять данные и временно 'надеть' на них другую обёртку, чтобы сравнить или обработать по-другому.
ЧТО ТАКОЕ ПРЕОБРАЗОВАНИЕ ТИПОВ
В PostgreSQL каждый столбец имеет тип: INTEGER, TEXT, DATE, TIMESTAMP и другие.
Иногда нужно временно изменить тип, чтобы:
— сравнить дату без времени
— применить текстовую функцию к числу
— выполнить арифметику с текстовым числом
— найти все заказы по части даты (год, месяц) с помощью LIKE
Для этого используется приведение типов (casting).
КАК ЭТО ДЕЛАЕТСЯ
Синтаксис 1:
sql
SELECT выражение::новый_тип
Синтаксис 2:
sql
SELECT CAST (выражение AS новый_тип)
Оба способа работают одинаково.
ФУНКЦИИ TO_CHAR И TO_DATE
При преобразовании типов часто используются специализированные функции для работы с датами и числами.
TO_CHAR — преобразует дату, время или число в строку по заданному формату.
sql
SELECT order_ts,
TO_CHAR (order_ts, 'DD.MM.YYYY') AS date_only,
TO_CHAR (order_ts, 'HH24:MI: SS') AS time_only
FROM orders;
Превращает дату и время в строку с нужным форматом.
Результат (фрагмент):
text
order_ts date_only time_only
2026-02-10 10:15:00 10.02.2026 10:15:00
2026-02-12 14:30:00 12.02.2026 14:30:00
TO_DATE — преобразует строку в дату по заданному формату.
sql
SELECT TO_DATE ('15.01.2025', 'DD.MM.YYYY') AS converted_date;
Превращает строку '15.01.2025' в дату.
Популярные форматы для TO_CHAR и TO_DATE:
ПРИМЕРЫ
Пример 1. Превратить TIMESTAMP в DATE, чтобы сравнить с датой.
sql
SELECT id, order_ts
FROM orders
WHERE order_ts::date = '2026-01-01' /* */
order_ts::date отбрасывает время, оставляя только дату. Теперь сравнение с '2026-01-01' работает.
Пример 2. Превратить дату в текст и найти заказы по году с помощью LIKE.
sql
SELECT id, order_ts
FROM orders
WHERE order_ts::text LIKE '2026%' /* */
order_ts::text превращает дату и время в строку вида '2026-02-10 10:15:00». LIKE '2026%' находит все заказы, где год начинается с 2026.
Пример 3. Превратить дату в текст и найти заказы за февраль любого года.
sql
SELECT id, order_ts
FROM orders
WHERE order_ts::text LIKE '%-02-%' /* */
Ищем в строковом представлении даты подстроку -02- (февраль).
Пример 4. Превратить число в текст и применить LIKE.
sql
SELECT phone
FROM clients
WHERE phone::text LIKE '9%' /* */
Если бы phone был числом,::text превратил бы его в строку, и LIKE начал бы работать.
Пример 5. Превратить текст в число и выполнить арифметику.
sql
SELECT price, price::integer +100 AS price_plus_100
FROM flowers
price хранится как NUMERIC.::integer превращает его в целое (отбрасывая копейки), и мы прибавляем 100.
Пример 6. Превратить текст в дату (если формат подходящий).
sql
SELECT '2025-01-15':: date AS new_date
Пример 7. Извлечь год из даты и сравнить с числом.
sql
SELECT id, order_ts
FROM orders
WHERE EXTRACT (YEAR FROM order_ts)::integer = 2026
EXTRACT (YEAR FROM order_ts) возвращает год как NUMERIC.::integer превращает его в целое число для сравнения.
ТИПИЧНЫЕ ОШИБКИ
Ошибка 1. Пытаться превратить текст в число, когда в тексте есть буквы.
sql
SELECT 'abc'::integer
Ошибка: неверный синтаксис для целого числа.
Ошибка 2. Пытаться превратить дату в текст и потом обратно, но с неправильным форматом.
sql
SELECT '2025-13-01':: date
Ошибка: месяца 13 не существует.
Ошибка 3. Забыть, что преобразование не меняет тип в таблице, а работает только для этого запроса.
ТОНКОСТИ И НЮАНСЫ
— Преобразование не меняет данные в таблице — оно работает только для текущего запроса.
— PostgreSQL умный — иногда преобразует типы сам, но лучше делать явно.
— Для дат и времени есть специальные функции (EXTRACT, DATE_PART), которые часто удобнее, чем преобразование в текст.
— Превращение даты в текст + LIKE — мощный приём, когда нужно искать заказы по году, месяцу или любой части даты.
ИЗ ОПЫТА
Однажды я искал заказы, сделанные 1 января, и долго не мог понять, почему запрос ничего не находит. Потом вспомнил, что в TIMESTAMP есть время.
С тех пор, когда нужно сравнить только дату, я всегда пишу order_ts::date = '2026-01-01'.
А однажды мне понадобилось найти все заказы за февраль любого года. Я превратил дату в текст и использовал LIKE '%-02-%' — сработало идеально.
ВОПРОСЫ И ОТВЕТЫ
В: Чем отличаются::text и CAST (… AS text)?
О: Ничем. Это два способа сделать одно и то же.:: короче, CAST более явный.
В: Можно ли превратить текст в дату, если он в формате '15.01.2025'?
О: Да, но нужно указать формат с помощью TO_DATE:
sql
SELECT TO_DATE ('15.01.2025', 'DD.MM.YYYY')
В: Что будет, если преобразовать число с копейками в целое?
О: Дробная часть отбросится (не округлится, а просто отсечётся).
В: Зачем превращать дату в текст и искать с помощью LIKE?
О: Это удобно, когда нужно найти заказы по части даты: по году, месяцу, или когда дата хранится в нестандартном формате.
ПОПРОБУЙ САМ
— Преврати цену цветка в целое число и выведи вместе с исходной ценой.
— Найди заказы, сделанные 1 января 2026 года, используя преобразование TIMESTAMP в DATE.
— Преврати строку '2025-06-15' в дату и выведи.
— Найди заказы, сделанные в 2026 году, используя преобразование даты в текст и LIKE.
— Найди заказы, сделанные в декабре любого года.
Шаг 17. Что такое регулярные выражения и с чем их готовить в PostgreSQL
Регулярный поиск — это регулярные выражения, а регулярные выражения — всегда регулярный поиск
ЖИВОЙ ПРИМЕР
Представьте, что вы ищете в базе данных записи, где есть слово 'срочно'.
Но люди пишут по-разному: 'СРОЧНО!', 'срочно доставить', 'Очень срочно, пожалуйста'.
Как найти всё это одним запросом?
LIKE здесь не справится — он умеет только простые шаблоны с % и _.
Для таких задач в SQL есть регулярные выражения — мощный язык поиска по шаблону.
ЧТО ТАКОЕ РЕГУЛЯРНЫЕ ВЫРАЖЕНИЯ (ПРОСТЫМИ СЛОВАМИ)
LIKE говорит: 'найди текст, где есть эта подстрока, а вокруг может быть что угодно'.
Регулярное выражение говорит: 'найди текст, который соответствует вот такой структуре'.
— LIKE — грубая маска: % = что угодно, _ = один символ.
— Регулярка — точный конструктор: [abc] = один из символов a, b или c; {3} = ровно три раза; [0—9] {3} = три цифры подряд.
ПОЧЕМУ ОДНОГО LIKE НЕДОСТАТОЧНО
LIKE хорош для простых вещей:
— найти все имена, начинающиеся на 'А'.
— найти телефоны, заканчивающиеся на '33'.
Но LIKE не умеет:
— искать независимо от регистра
— искать по сложным правилам (например, 'три цифры подряд')
— проверять, что строка начинается с цифры и заканчивается буквой
— искать даты в тексте
Для этого нужны регулярные выражения.
КАК СТРОИТЬ РЕГУЛЯРНЫЕ ВЫРАЖЕНИЯ (КОРОТКО)
ПРИМЕРЫ
Пример 1. Найти всех клиентов, у которых в имени есть буква 'а' (без учёта регистра).
sql
SELECT name
FROM clients
WHERE name ~* 'a' /* */
Что тут происходит
— ~* — оператор регулярного выражения, который ищет совпадение без учёта регистра
— 'а' — шаблон: буква 'а' в любом регистре (а, А)
— Запрос вернёт всех клиентов, у которых в имени есть буква 'а' (например, Николай, Дмитрий, Мария, Ирина, Виктор)
Результат (фрагмент):
text
Николай
Дмитрий
Мария
Ирина
Виктор
Пример 2. Найти всех клиентов, у которых имя начинается с 'А' или 'а'.
sql
SELECT name
FROM clients
WHERE name ~* '^а'/* */
Что тут происходит
— ~* — без учёта регистра
— ^ — якорь начала строки (имя должно начинаться с указанного символа)
— '^а' — ищем имена, которые начинаются на букву 'а' в любом регистре
— Запрос вернёт: Алексей, Андрей, Анна
Результат:
text
Алексей
Андрей
Анна
Пример 3. Найти всех клиентов, у которых имя заканчивается на 'ий'.
sql
SELECT name
FROM clients
WHERE name ~ 'ий$' /* */
Что тут происходит
— ~ — оператор с учётом регистра (для русских букв это важно)
— $ — якорь конца строки
— 'ий$' — ищем имена, которые заканчиваются на ий
— Запрос вернёт: Дмитрий, Николай
Результат:
text
Дмитрий
Николай
Пример 4. Найти города, в названии которых есть две одинаковые буквы подряд.
sql
SELECT name
FROM cities
WHERE name ~'(.) \1'/* */
Что тут происходит
— (.) — точка в скобках означает 'любой символ'. Скобки запоминают этот символ
— \1 — ссылка на первый запомненный символ (означает 'такой же символ')
— Вместе (.) \1 означает: любой символ, за которым сразу идёт такой же символ
Результат (фрагмент):
text
Санкт-Петербург
Ростов-на-Дону
Пример 5. Найти клиентов, у которых в телефоне есть три одинаковые цифры подряд.
sql
SELECT name, phone
FROM clients
WHERE phone ~' ([0—9]) \1\1'/* */
Что тут происходит
— [0—9] — любая цифра от 0 до 9
— ([0—9]) — скобки запоминают эту цифру
— \1\1 — ссылка на запомненную цифру, повторённая дважды (всего три одинаковых цифры подряд)
— Например: 111, 222, 333 и т. д.
Результат (фрагмент):
text
Ирина 991112233
Виктор 834445566
ФУНКЦИИ ДЛЯ РАБОТЫ С РЕГУЛЯРНЫМИ ВЫРАЖЕНИЯМИ
Примеры с функциями:
1. regexp_match — извлечь первую цифру из строки
sql
SELECT regexp_match ('Заказ №12345','[0—9] +') AS first_number;
Возвращает первое совпадение с шаблоном (одну или несколько цифр).
Результат: {12345}
2. regexp_matches — извлечь все цифры из строки
sql
SELECT regexp_matches ('abc123def456','[0—9] +', 'g') AS all_numbers;
Флаг 'g’ означает 'глобальный поиск' — все совпадения.
Результат: {123}, {456}
3. regexp_replace — заменить все цифры на звёздочки
sql
SELECT regexp_replace ('abc123def456','[0—9]', '*', 'g') AS replaced;
Заменяет каждую цифру на *.
Результат: abc***def***
4. regexp_split_to_table — разбить строку по запятой
sql
SELECT regexp_split_to_table ('Роза, Тюльпан, Лилия',',') AS flower;
Результат:
text
flower
Роза
Тюльпан
Лилия
5. regexp_split_to_array — разбить строку на массив
sql
SELECT regexp_split_to_array ('Роза, Тюльпан, Лилия',',') AS flowers_array;
Результат: {Роза, Тюльпан, Лилия}
6. regexp_match — извлечь код города из телефонного номера (первые 3 цифры)
sql
SELECT regexp_match ('991234567', '^ [0—9] {3}') AS city_code;
^ — начало строки, [0—9] {3} — три цифры.
Результат: {991}
7. regexp_replace — отформатировать номер телефона
sql
SELECT regexp_replace ('991234567','([0—9] {3}) ([0—9] {3}) ([0—9] {3})', '+7-\1-\2-\3') AS formatted_phone;
Разбивает номер на группы и добавляет +7 и дефисы.
Результат: +7-991-234-567
ТИПИЧНЫЕ ОШИБКИ
Ошибка 1. Путать LIKE и регулярные выражения.
LIKE ищет по простым шаблонам с % и _. Регулярные выражения — это отдельный язык. Они не взаимозаменяемы.
Ошибка 2. Забыть про регистр.
~ учитывает регистр, ~* — нет. Если ищете 'москва', а в базе 'Москва' — ~ не найдёт.
Ошибка 3. Слишком сложный шаблон.
Новички часто пишут огромные регулярные выражения, которые потом сложно читать и править. Лучше разбить задачу на несколько шагов.
ТОНКОСТИ И НЮАНСЫ
— Регулярные выражения в PostgreSQL используют синтаксис POSIX. Для поиска цифр используйте [0—9] или [:digit: ].
— Флаг 'g' в regexp_matches включает глобальный поиск (все совпадения). Без него возвращается только первое.
— regexp_match и regexp_matches возвращают массив. Чтобы получить текст, можно обратиться к первому элементу: (regexp_match (…)) [1].
ИЗ ОПЫТА
Однажды я искал заказы, где в комментарии был номер телефона. Я написал регулярное выражение'[0—9] {11}' — и нашёл всё. Но потом оказалось, что номера бывают и с пробелами, и с дефисами.
Пришлось переписать на'[0—9\-] {11,}' — это было сложно, но работало.
С тех пор я тестирую регулярные выражения на маленьком наборе данных, прежде чем запускать на всей базе.
ВОПРОСЫ И ОТВЕТЫ
В: Чем отличаются ~ и ~*?
О: ~ учитывает регистр, ~* — нет.
В: Можно ли использовать регулярные выражения с LIKE?
О: Нет, это разные инструменты. Но есть оператор SIMILAR TO, который что-то среднее. Но его используют редко.
В: Как искать цифры?
О: Используйте [0—9] или [:digit: ].
В: Чем regexp_match отличается от regexp_matches?
О: regexp_match возвращает первое совпадение, regexp_matches — все. regexp_matches обычно используют с флагом 'g'.
ПОПРОБУЙ САМ
— Найди всех клиентов, у которых в имени есть буква 'о'.
— Найди всех клиентов, чьё имя начинается с 'А'.
— Найди всех клиентов, чьё имя заканчивается на 'ий'.
— Найди города, в названии которых есть две одинаковые буквы подряд.
— Найди клиентов, у которых в телефоне есть три одинаковые цифры подряд.
ЗАДАЧИ НА ПОВТОРЕНИЕ
— Выведите имя клиента и название его города, используя подзапрос в SELECT. (шаг 15)
— Найдите клиентов, которые живут в городах с населением больше 5 000 000, используя подзапрос в WHERE с IN. (шаг 15)
— Преобразуйте дату заказа в текст в формате DD.MM.YYYY. Используйте TO_CHAR. (шаг 16)
Шаг 18. Строка — это не просто строка. Функции работы со строками
Инструкция как резать, клеить и заменять
ЖИВОЙ ПРИМЕР
Вы работаете с таблицей клиентов. Имена клиентов в таблице хранятся в разном регистре: 'НИКОЛАЙ', 'елена', 'Анна', 'дмитрий', и некоторые имена содержат лишние пробелы в конце.
Предположим, что вам нужно вывести список имён красиво — с большой буквы, остальные — маленькие, и убрать лишние пробелы в конце.
В языке SQL реализовано множество функций для удобной работы со строковыми данными, которые могут:
— менять регистр
— обрезать лишние пробелы
— соединять строки
— вырезать часть строки
— заменять одно на другое
ОСНОВНЫЕ СТРОКОВЫЕ ФУНКЦИИ
ПРИМЕРЫ
Пример 1. Привести имена клиентов к единому формату (первая буква заглавная), исключая тех, чьи имена начинаются на 'А'.
sql
SELECT name,
INITCAP (name) AS name_normalized
FROM clients
WHERE name NOT LIKE 'А%'/* */
INITCAP делает первую букву заглавной, остальные — строчными.
Результат (фрагмент):
text
name name_normalized
НИКОЛАЙ Николай
елена Елена
дмитрий Дмитрий
Пример 2. Вывести имена клиентов в верхнем и нижнем регистре для тех, у кого длина имени больше 5 символов.
sql
SELECT name,
UPPER (name) AS name_upper,
LOWER (name) AS name_lower
FROM clients
WHERE LENGTH (name)> 5
UPPER и LOWER меняют регистр строки. WHERE LENGTH (name)> 5 оставляет только имена длиннее 5 символов.
Результат (фрагмент):
text
name name_upper name_lower
Николай НИКОЛАЙ николай
Дмитрий ДМИТРИЙ дмитрий
Пример 3. Соединить имя клиента и его номер телефона, но только для клиентов из Москвы или Санкт-Петербурга.
sql
SELECT CONCAT (name, ' (тел:', phone,')') AS contact
FROM clients
WHERE city_id IN (1, 2)
CONCAT соединяет строки.
Результат (фрагмент):
text
contact
Николай (тел: 991234567)
Елена (тел: 112345678)
Дмитрий (тел: 993456789)
Пример 4. Найти длину имени каждого клиента, у которого цена любимого цветка (условно) между 100 и 200 руб.
sql
SELECT DISTINCT c.name,
LENGTH(c.name) AS name_length
FROM clients c
JOIN orders o ON c.phone = o.client_phone
JOIN flowers f ON o. flower_id = f.id
WHERE f.price BETWEEN 100 AND 200
LENGTH возвращает длину имени.
Пример 5. Обрезать лишние пробелы в именах клиентов (если бы они были).
sql
SELECT TRIM (name) AS trimmed_name
FROM clients
WHERE name LIKE '% ' OR name LIKE ' %'/* */
TRIM удаляет пробелы в начале и конце.
Пример 6. Вырезать первые три символа из названия города, но только для городов с населением больше 1 000 000.
sql
SELECT name,
SUBSTRING (name FROM 1 FOR 3) AS short_name
FROM cities
WHERE population> 1000000
Результат (фрагмент):
text
name short_name
Москва Мос
Санкт-Петербург Сан
Казань Каз
Новосибирск Нов
Пример 7. Заменить в названиях городов 'бург' на 'град', но только для городов, где население указано.
sql
SELECT name,
REPLACE (name, 'бург', 'град') AS new_name
FROM cities
WHERE name LIKE '%бург%' AND population IS NOT NULL
Результат (фрагмент):
text
name new_name
Санкт-Петербург Санкт-Петерград
Екатеринбург Екатеринград
Пример 8. Найти позицию буквы 'о' в названии города, но только для городов, название которых начинается с буквы 'Н'.
sql
SELECT name,
POSITION ('о' IN name) AS pos
FROM cities
WHERE name LIKE 'Н%' /* */
Результат (фрагмент):
text
name pos
Новосибирск 2
Нижний Новгород 2
Пример 9. Сформировать email из имени и фамилии (условно), заменив пробелы на точки и приведя к нижнему регистру.
sql
SELECT full_name,
LOWER (REPLACE (full_name, '','.')) || '@example.com' AS email
FROM clients
WHERE full_name IS NOT NULL
REPLACE заменяет пробел на точку. LOWER приводит всё к нижнему регистру. Оператор || добавляет домен.
ТИПИЧНЫЕ ОШИБКИ
Ошибка 1. Путать LENGTH и CHAR_LENGTH.
В PostgreSQL для строк в многобайтовых кодировках (например, UTF-8) LENGTH и CHAR_LENGTH возвращают одно и то же — количество символов.
Ошибка 2. Забывать, что строки в SQL чувствительны к регистру.
sql
SELECT * FROM clients WHERE name = 'николай' /* */
Не найдёт 'Николай'. Нужно либо привести к одному регистру:
sql
SELECT * FROM clients WHERE
LOWER (name) = 'николай' /* */
ТОНКОСТИ И НЮАНСЫ
— Соединение строк — оператор || работает быстрее, чем CONCAT, но CONCAT удобнее, когда нужно соединить много частей.
— Обрезка пробелов — TRIM удаляет только пробелы в начале и конце. Если нужно удалить все пробелы внутри строки, потребуется REPLACE.
— Регистр — INITCAP работает с пробелами, переводит первую букву каждого слова в заглавную.
sql
SELECT INITCAP ('пРИВЕТ МИР') AS capitalized
ИЗ ОПЫТА
В своей работе я часто встречаюсь с различными преобразованиями текста. В SQL есть удобная функция SUBSTRING, которую я использую чаще всего.
Пример 1. Выделить код города из телефонного номера.
sql
SELECT phone,
SUBSTRING (phone FROM 1 FOR 3) AS city_code
FROM clients
Пример 2. Извлечь имя из полного имени (условно).
sql
SELECT full_name,
SUBSTRING (full_name FROM POSITION (' ' IN full_name) +1) AS first_name
FROM clients
WHERE full_name LIKE '% %' /* */
Пример 3. Получить год из даты, если она хранится как строка.
sql
SELECT date_string,
SUBSTRING (date_string FROM 1 FOR 4) AS year
FROM orders
Пример 4. Выбрать последние 4 символа строки.
sql
SELECT phone,
SUBSTRING (phone FROM -4) AS last_four
FROM clients
*FROM -4 означает 'начать с 4-го символа с конца'. *
Пример 5. Выбрать символы с 3-го с конца по 5-й с конца.
sql
SELECT phone,
SUBSTRING (phone FROM -5 FOR 3) AS middle_from_end
FROM clients
ВОПРОСЫ И ОТВЕТЫ
В: Как удалить все пробелы из строки?
О: Используйте REPLACE (строка, '', '').
В: Как проверить, что строка начинается с определённого символа?
О: Используйте LIKE 'A%' или SUBSTRING (строка FROM 1 FOR 1) = 'A'.
В: Что быстрее — CONCAT или ||?
О: На практике разница незаметна. Выбирайте то, что вам понятнее.
ПОПРОБУЙ САМ
— Выведи имена клиентов в нижнем регистре.
— Соедини название цветка и его цену в одну строку (например, 'Роза — 150 руб.').
— Замени в названиях цветов букву 'о' на 'а' (например, Роза → Раза).
— Найди позицию буквы 'а' в названиях цветов.
— Извлеки последние 3 цифры из номера телефона клиента.
— Вырежи из названия города символы с 3-го по 5-й.
— Сформируй email из имени клиента, добавив @example.com и приведя всё к нижнему регистру.
Шаг 19. Разбираем функции работы с календарём, датой и временем
Когда вчера было завтра, или как не запутаться в датах
ЖИВОЙ ПРИМЕР
Вы работаете с таблицей заказов. Вам нужно найти все заказы, сделанные в прошлом месяце.
Дата заказа хранится в формате TIMESTAMP. Как вычислить 'прошлый месяц' без ручного ввода дат?
Такие задачи возникают в работе почти каждый день.
В SQL есть множество функций для работы с датами и временем:
— получать текущую дату и время
— извлекать год, месяц, день, час
— складывать и вычитать интервалы
— сравнивать даты
— форматировать вывод
ОСНОВНЫЕ ТИПЫ ДАТ И ВРЕМЕНИ
ОСНОВНЫЕ ФУНКЦИИ ДЛЯ РАБОТЫ С ДАТАМИ И ВРЕМЕНЕМ
РАЗНИЦА МЕЖДУ ДАТАМИ (В ДНЯХ, ЧАСАХ, МЕСЯЦАХ)
Вычисление разницы между двумя датами — одна из самых частых задач при работе с временными данными.
Разница в днях (простое вычитание):
sql
SELECT ('2026-03-23':: date — '2026-03-01':: date) AS days_diff;
Результат: 22 дня.
Разница в днях между TIMESTAMP (с учётом времени):
sql
SELECT ('2026-03-23 14:30:00':: timestamp — '2026-03-20 10:15:00':: timestamp) AS interval_diff;
Результат: 3 days 04:15:00.
Извлечение количества дней из интервала:
sql
SELECT EXTRACT (DAY FROM ('2026-03-23':: date — '2026-03-01':: date)) AS days;
Разница в часах:
sql
SELECT EXTRACT (EPOCH FROM ('2026-03-23 14:00:00':: timestamp — '2026-03-23 10:00:00':: timestamp)) / 3600 AS hours_diff;
EPOCH переводит интервал в секунды. Делим на 3600 — получаем часы.
Разница в месяцах с помощью AGE:
sql
SELECT AGE ('2026-12-31':: date, '2026-01-01':: date) AS age_diff;
Результат: 11 mons 30 days.
Разница только в месяцах (без дней):
sql
SELECT EXTRACT (YEAR FROM AGE ('2026-12-31', '2026-01-01')) * 12 +
EXTRACT (MONTH FROM AGE ('2026-12-31', '2026-01-01')) AS total_months;
Переводим разницу в полные месяцы.
Количество дней между датами (альтернативный способ):
sql
SELECT date_part ('day', '2026-03-23':: date — '2026-03-01':: date) AS days;
ПРИМЕРЫ
Пример 1. Найти заказы, сделанные в текущем месяце.
sql
SELECT id, order_ts
FROM orders
WHERE DATE_TRUNC ('month', order_ts) = DATE_TRUNC ('month', CURRENT_DATE)
DATE_TRUNC ('month', …) обрезает дату до первого дня месяца. Сравниваем месяц заказа с текущим месяцем.
Пример 2. Найти заказы, сделанные в прошлом месяце.
sql
SELECT id, order_ts
FROM orders
WHERE DATE_TRUNC ('month', order_ts) = DATE_TRUNC ('month', CURRENT_DATE — INTERVAL '1 month')
Вычитаем один месяц из текущей даты, затем обрезаем до первого дня месяца.
Пример 3. Найти заказы, сделанные в прошлом году.
sql
SELECT id, order_ts
FROM orders
WHERE EXTRACT (YEAR FROM order_ts) = EXTRACT (YEAR FROM CURRENT_DATE) — 1
EXTRACT (YEAR FROM …) извлекает год. Сравниваем с предыдущим годом.
Пример 4. Добавить к дате заказа 3 дня.
sql
SELECT id,
order_ts,
order_ts + INTERVAL '3 days' AS plus_3_days
FROM orders
Результат (фрагмент):
text
id order_ts plus_3_days
1 2026-02-10 10:15:00 2026-02-13 10:15:00
2 2026-02-12 14:30:00 2026-02-15 14:30:00
Пример 5. Вывести возраст клиентов на текущую дату.
sql
SELECT name,
birth_date,
AGE (birth_date) AS age
FROM clients
WHERE birth_date IS NOT NULL
AGE (birth_date) возвращает возраст в формате '45 years 10 mons 13 days’.
Результат (фрагмент):
text
name birth_date age
Николай 1980-05-10 45 years 10 mons 13 days
Елена 1992-08-22 33 years 7 mons 1 day
Пример 6. Вывести возраст клиентов в годах (целое число).
sql
SELECT name,
birth_date,
EXTRACT (YEAR FROM AGE (birth_date)) AS age_years
FROM clients
WHERE birth_date IS NOT NULL
Результат (фрагмент):
text
name birth_date age_years
Николай 1980-05-10 45
Елена 1992-08-22 33
Пример 7. Вывести заказы, сделанные в выходные дни.
sql
SELECT id, order_ts,
EXTRACT (DOW FROM order_ts) AS day_of_week
FROM orders
WHERE EXTRACT (DOW FROM order_ts) IN (0, 6)
EXTRACT (DOW FROM …)
возвращает день недели: 0 — воскресенье, 6 — суббота.
ТИПИЧНЫЕ ОШИБКИ
Ошибка 1. Путать DATE и TIMESTAMP.
Сравнение order_ts = '2026-03-23' не найдёт заказы, сделанные в этот день, если в order_ts есть время. Нужно либо использовать::date, либо диапазон:
sql
WHERE order_ts::date = '2026-03-23' /* */
— или
WHERE order_ts> = '2026-03-23' AND order_ts <'2026-03-24' /* */
Ошибка 2. Забывать, что EXTRACT (DOW FROM …) возвращает 0 для воскресенья (в PostgreSQL). В других СУБД может быть 1 для воскресенья.
Ошибка 3. Сравнивать интервалы с числами.
AGE (birth_date)> 18 не работает. Нужно сравнивать годы:
sql
WHERE EXTRACT (YEAR FROM AGE (birth_date))> 18
ТОНКОСТИ И НЮАНСЫ
— DATE_TRUNC удобна для группировок по дням, месяцам, годам.
— EXTRACT возвращает числа, их можно использовать в арифметике.
— INTERVAL можно складывать и вычитать из дат.
— TO_CHAR позволяет выводить даты в любом формате, включая русские названия месяцев (если настроена локаль).
ИЗ ОПЫТА
Однажды мне нужно было построить отчёт по заказам за последний квартал. Я написал:
sql
SELECT *
FROM orders
WHERE order_ts> = DATE_TRUNC ('quarter', CURRENT_DATE) — INTERVAL '3 months’
И забыл, что DATE_TRUNC ('quarter', CURRENT_DATE) возвращает начало текущего квартала, а не прошлого. Пришлось добавить ещё один — INTERVAL '3 months'.
С тех пор я тестирую такие запросы на небольшой выборке, прежде чем запускать на всех данных.
ВОПРОСЫ И ОТВЕТЫ
В: Как узнать возраст клиента в годах, не глядя на месяцы и дни?
О: Используйте EXTRACT (YEAR FROM AGE (birth_date)). Это вернёт целое число — сколько лет человеку.
В: Как посчитать, сколько дней прошло между двумя датами?
О: Просто вычтите одну дату из другой: ('2026-03-23':: date — '2026-03-01':: date). В ответе будут дни.
В: Мне нужно добавить к заказу 3 часа, как это сделать?
О: Прибавьте интервал: order_ts + INTERVAL '3 hours'.
В: Как получить первый день текущего месяца, чтобы, например, начать отчёт с него?
О: Используйте DATE_TRUNC ('month', CURRENT_DATE). Эта команда округлит текущую дату вниз до начала месяца.
ПОПРОБУЙ САМ
— Выведи все заказы, которые были сделаны в этом году.
Подсказка: тебе понадобятся EXTRACT (YEAR FROM …) и CURRENT_DATE.
— Найди клиентов, у которых день рождения наступает в текущем месяце.
Подсказка: сравни месяц рождения с текущим месяцем с помощью EXTRACT (MONTH FROM …).
— Выведи дату заказа в привычном для человека формате: 'Год-Месяц-День Час: Минуты' (например, 2026-03-23 14:30).
Подсказка: используй TO_CHAR с подходящим шаблоном.
— Найди заказы, которые были сделаны в понедельник.
Подсказка: EXTRACT (DOW FROM …) вернёт день недели. Понедельник — это 1, воскресенье — 0.
— Найди заказы, сделанные в первый день любого месяца.
Подсказка: извлеки день из даты (EXTRACT (DAY FROM …)) и сравни с 1.
ЗАДАЧИ НА ПОВТОРЕНИЕ
— Найдите всех клиентов, у которых в имени есть буква 'о'. (шаг 17)
— Найдите всех клиентов, чьё имя заканчивается на 'ий'. (шаг 17)
— Найдите города, в названии которых есть две одинаковые буквы подряд. (шаг 17)
Шаг 20. Интервалы и последовательности
Просто генерируем данные в PostgreSQL
ЖИВОЙ ПРИМЕР
Представьте, что вам нужно построить отчёт по заказам за каждый день месяца, даже если в какие-то дни заказов не было.
В PostgreSQL для этого есть специальная генерация рядов:
— generate_series — создаёт последовательность чисел или дат
— generate_series с датами — генерирует все дни между двумя датами
— generate_series с шагом — можно получить каждый второй день, каждый месяц и т. д.
ГЕНЕРАЦИЯ ЧИСЕЛ
Функция generate_series умеет создавать последовательности чисел. Это удобно, когда нужно:
— получить список номеров строк для отчёта
— создать тестовые данные
— выполнить операцию заданное количество раз
Синтаксис:
sql
generate_series (start, stop, step)
Пример 1. Числа от 1 до 10.
sql
SELECT generate_series (1, 10) AS num;
Результат:
text
num
— — —
1
2
3
…
10
generate_series (1, 10) возвращает набор чисел от 1 до 10. Шаг по умолчанию — 1.
Пример 2. Нечётные числа от 1 до 9.
sql
SELECT generate_series (1, 10, 2) AS odd_num;
Результат:
text
odd_num
— — — —
1
3
5
7
9
Третий параметр — шаг. Получаем нечётные числа от 1 до 9.
Пример 3. Числа от 10 до 1 (обратный порядок).
sql
SELECT generate_series (10, 1, -1) AS reverse_num;
Результат:
text
reverse_num
— — — — — —
10
9
8
…
1
Шаг может быть отрицательным. Получаем числа в обратном порядке.
ГЕНЕРАЦИЯ ДАТ
generate_series умеет работать не только с числами, но и с датами и временем.
Синтаксис для дат:
sql
generate_series (start_date, end_date, step_interval)
Шаг обязательно указывается как INTERVAL: '1 day', '1 month', '1 hour' и т. д.
Пример 4. Даты с 1 по 10 марта 2026.
sql
SELECT generate_series ('2026-03-01':: date, '2026-03-10':: date, '1 day') AS day;
Результат:
text
day
— — — — — —
2026-03-01
2026-03-02
2026-03-03
…
2026-03-10
generate_series с датами генерирует все дни в указанном диапазоне. Шаг '1 day’ — каждый день.
Пример 5. Месяцы с января по декабрь 2026.
sql
SELECT generate_series ('2026-01-01':: date, '2026-12-01':: date, '1 month') AS month;
Результат:
text
month
— — — — — —
2026-01-01
2026-02-01
2026-03-01
…
2026-12-01
Шаг '1 month’ — каждый месяц. Дата всегда будет первым числом месяца.
Пример 6. Часы в течение дня.
sql
SELECT generate_series ('2026-03-23 00:00:00':: timestamp, '2026-03-23 23:00:00':: timestamp, '1 hour') AS hour;
Результат:
text
hour
— — — — — — — — — — —
2026-03-23 00:00:00
2026-03-23 01:00:00
…
2026-03-23 23:00:00
Пример 7. Интервал 15 минут.
sql
SELECT generate_series ('2026-03-23 00:00:00':: timestamp, '2026-03-23 03:00:00':: timestamp, '15 minutes') AS interval_15min;
Результат:
text
interval_15min
— — — — — — — — — — —
2026-03-23 00:00:00
2026-03-23 00:15:00
2026-03-23 00:30:00
…
2026-03-23 03:00:00
Шаг может быть любым интервалом: '15 minutes’, '2 hours’, '3 days’.
Пример 8. Рабочие дни марта 2026 (без суббот и воскресений).
sql
SELECT day
FROM generate_series ('2026-03-01':: date, '2026-03-31':: date, '1 day') AS day
WHERE EXTRACT (DOW FROM day) NOT IN (0, 6);
generate_series создаёт все дни марта. EXTRACT (DOW FROM day) возвращает день недели: 0 — воскресенье, 6 — суббота. NOT IN (0, 6) оставляет только рабочие дни.
Результат (фрагмент):
text
day
— — — — — —
2026-03-02
2026-03-03
2026-03-04
2026-03-05
2026-03-06
2026-03-09
…
Пример 9. 10 случайных чисел.
sql
SELECT random () AS rand_num
FROM generate_series (1, 10);
generate_series создаёт 10 строк, для каждой вычисляется случайное число.
Результат (пример):
text
rand_num
— — — — — — — — — —
0.723456789012345
0.182345678901234
…
0.998765432109876
ГЕНЕРАЦИЯ СЛУЧАЙНЫХ ЧИСЕЛ (RANDOM)
Функция random () возвращает случайное число в диапазоне от 0 до 1 (не включая 1).
sql
SELECT random ();
Возвращает случайное число, например 0.723456789012345.
Генерация целого случайного числа в заданном диапазоне:
sql
— Случайное число от 1 до 100
SELECT floor (random () * 100 +1)::int AS random_number;
random () * 100 даёт число от 0 до 100. floor округляет вниз. +1 сдвигает диапазон до 1–100.
Генерация случайного целого числа от 1 до 10:
sql
SELECT floor (random () * 10 +1)::int;
Генерация нескольких случайных чисел:
sql
SELECT random () AS rand_num
FROM generate_series (1, 5);
Генерирует 5 случайных чисел.
Использование random () с ORDER BY (случайная сортировка):
sql
SELECT * FROM flowers ORDER BY random () LIMIT 3;
Возвращает 3 случайных цветка.
ТИПИЧНЫЕ ОШИБКИ
Ошибка 1. Забывать, что generate_series можно использовать только в FROM.
Неверно:
sql
SELECT generate_series (1, 10) +5;
Верно:
sql
SELECT num +5
FROM generate_series (1, 10) AS num;
Ошибка 2. Неправильно указывать шаг для дат.
Неверно:
sql
SELECT generate_series ('2026-03-01', '2026-03-10', 1);
Верно:
sql
SELECT generate_series ('2026-03-01':: date, '2026-03-10':: date, '1 day');
Ошибка 3. Использовать generate_series без явного приведения типов.
Неверно:
sql
SELECT generate_series ('2026-03-01', '2026-03-10', '1 day');
Верно:
sql
SELECT generate_series ('2026-03-01':: date, '2026-03-10':: date, '1 day');
ТОНКОСТИ И НЮАНСЫ
— generate_series можно использовать не только с числами и датами, но и с TIMESTAMP и TIMESTAMPTZ.
— Для больших диапазонов generate_series может генерировать миллионы строк — будьте осторожны.
— generate_series часто используют для построения отчётов с пропущенными датами.
— Шаг может быть дробным для числовых последовательностей: generate_series (0, 1, 0.1).
ИЗ ОПЫТА
Однажды мне нужно было получить все рабочие дни месяца (без суббот и воскресений). Я использовал generate_series и EXTRACT:
sql
SELECT day
FROM generate_series ('2026-03-01':: date, '2026-03-31':: date, '1 day') AS day
WHERE EXTRACT (DOW FROM day) NOT IN (0, 6);
Без generate_series пришлось бы перебирать даты вручную или писать сложный скрипт.
С тех пор, когда нужно получить диапазон дат или чисел, я сразу думаю про generate_series.
ВОПРОСЫ И ОТВЕТЫ
В: Можно ли сгенерировать последовательность месяцев, начиная не с первого числа?
О: Да. generate_series ('2026-01-15':: date, '2026-12-15':: date, '1 month') — получите 15-е число каждого месяца.
В: Как сгенерировать последовательность с плавающей точкой?
О: Используйте generate_series (0, 1, 0.1).
В: Можно ли использовать generate_series внутри SELECT без FROM?
О: В PostgreSQL — да, начиная с версии 10, но лучше всегда использовать FROM для ясности.
В: Что будет, если указать конечную дату раньше начальной?
О: Если шаг положительный — результат пустой. Если отрицательный — последовательность пойдёт в обратном порядке.
ПОПРОБУЙ САМ
— Сгенерируй все числа от 5 до 15.
— Сгенерируй все нечётные числа от 1 до 20.
— Сгенерируй все дни текущего месяца.
— Сгенерируй все часы текущего дня.
— Сгенерируй последовательность из 10 случайных чисел.
— Выбери 3 случайных цветка из таблицы flowers.
ЗАДАЧИ НА ПОВТОРЕНИЕ
— Найдите всех клиентов, у которых в имени есть буква 'о'. Используйте регулярное выражение ~*. (шаг 17)
— Найдите всех клиентов, чьё имя начинается с 'А'. Используйте регулярное выражение ~* '^а'. (шаг 17)
— Выведите возраст клиентов на текущую дату. Используйте AGE (birth_date). (шаг 19)
Шаг 21. А если из нескольких таблиц? Объединение таблиц JOIN
Что бывает, если несколько таблиц объединяются
ЖИВОЙ ПРИМЕР
Вы работаете с таблицей заказов orders. В ней есть client_phone — номер телефона клиента, но нет его имени.
Но имя клиента хранится в другой таблице — clients.
Как же теперь показать список заказов вместе с именами клиентов из другой таблицы?
В SQL для этого есть оператор объединения таблиц (JOIN).
Он позволяет соединять строки из разных таблиц по заданному условию.
ВИДЫ JOIN
В SQL есть несколько типов объединения таблиц.
В этом шаге мы рассмотрим самый простой и часто используемый — INNER JOIN.
Остальные типы (LEFT JOIN, RIGHT JOIN, FULL OUTER JOIN, CROSS JOIN) мы изучим позже.
СИНТАКСИС JOIN
sql
SELECT список_колонок
FROM таблица1
JOIN таблица2 ON условие_связи
Ключевое слово JOIN — это сокращённая форма INNER JOIN. Условие связи (ON) определяет, по каким колонкам соединяются таблицы
Условие может быть не только равенством колонок, но и более сложным:
sql
ON таблица1.колонка = таблица2.колонка AND таблица1.другая_колонка> 100
Важно: таблицы можно объединять и без явного JOIN, через запятую в FROM с условием в WHERE:
sql
SELECT *
FROM таблица1, таблица2
WHERE таблица1.колонка = таблица2.колонка
Такой способ тоже работает, но он менее нагляден и чаще приводит к ошибкам. Поэтому мы будем использовать явный JOIN.
Пример явного JOIN:
sql
SELECT o.id,
c.name AS client_name
FROM orders o
JOIN clients c ON o.client_phone = c.phone
То же самое без явного JOIN (через запятую и WHERE):
sql
SELECT o.id,
c.name AS client_name
FROM orders o, clients c
WHERE o.client_phone = c.phone
Оба запроса вернут одинаковый результат, но первый вариант читается легче.
ПРИМЕРЫ
Пример 1. Вывести заказы с именами клиентов (INNER JOIN).
sql
SELECT o.id,
o. order_ts,
c.name AS client_name
FROM orders o
INNER JOIN clients c ON o.client_phone = c.phone
INNER JOIN соединяет заказы и клиентов по номеру телефона. Если у заказа нет клиента (такого не бывает, но вдруг), такой заказ не попадёт в результат.
Результат (фрагмент):
text
id order_ts client_name
1 2026-02-10 10:15:00 Николай
2 2026-02-12 14:30:00 Елена
…
Пример 2. Вывести заказы с названиями цветов (INNER JOIN).
sql
SELECT o.id,
f.name AS flower_name,
o. quantity
FROM orders o
INNER JOIN flowers f ON o. flower_id = f.id
Соединяем заказы с цветами по flower_id. Получаем название цветка вместо числового идентификатора.
Результат (фрагмент):
text
id flower_name quantity
1 Тюльпан 5
2 Роза 3
…
Пример 3. Вывести заказы с названиями цветов и их оттенками (соединение трёх таблиц).
sql
SELECT o.id,
f.name AS flower_name,
col.name AS color_name,
o. quantity
FROM orders o
INNER JOIN flowers f ON o. flower_id = f.id
INNER JOIN colors col ON o.color_code = col.code
Соединяем три таблицы: orders → flowers → colors. Каждый заказ получает название цветка и его оттенок.
Результат (фрагмент):
text
id flower_name color_name quantity
1 Тюльпан красный 5
2 Роза розовый 3
…
Пример 4. Вывести заказы с именами клиентов и названиями цветов (соединение трёх таблиц).
sql
SELECT o.id,
c.name AS client_name,
f.name AS flower_name,
o. quantity
FROM orders o
INNER JOIN clients c ON o.client_phone = c.phone
INNER JOIN flowers f ON o. flower_id = f.id
Соединяем заказы с клиентами и цветами. Получаем полную информацию о каждом заказе.
Пример 5. Вывести заказы с цветами, цена которых больше 150 рублей (соединение с условием в WHERE).
sql
SELECT o.id,
f.name AS flower_name,
f.price,
o. quantity
FROM orders o
INNER JOIN flowers f ON o. flower_id = f.id
WHERE f.price> 150
Сначала соединяем заказы с цветами, затем отфильтровываем только те, где цена цветка больше 150.
Результат (фрагмент):
text
id flower_name price quantity
3 Пион 200 7
7 Орхидея 250 1
…
Пример 6. Вывести заказы с именами клиентов и названиями цветов, но только для клиентов из Москвы (city_id = 1).
sql
SELECT o.id,
c.name AS client_name,
f.name AS flower_name,
o. quantity
FROM orders o
INNER JOIN clients c ON o.client_phone = c.phone
INNER JOIN flowers f ON o. flower_id = f.id
WHERE c.city_id = 1
Соединяем три таблицы, затем оставляем только заказы клиентов из Москвы.
ТИПИЧНЫЕ ОШИБКИ
Ошибка 1. Забыть условие соединения (ON).
Неверно:
sql
SELECT * FROM orders o JOIN clients c
Ошибка: декартово произведение (каждый заказ соединится с каждым клиентом).
Верно:
sql
SELECT * FROM orders o JOIN clients c ON o.client_phone = c.phone
Ошибка 2. Соединять таблицы по колонкам с разными типами данных.
Если типы не совпадают, PostgreSQL может попытаться преобразовать их автоматически, но это может привести к ошибкам или медленным запросам. Лучше всегда соединять по колонкам одного типа.
ТОНКОСТИ И НЮАНСЫ
— INNER JOIN можно сократить до JOIN — это одно и то же.
— Условие соединения может быть составным (например, ON a.id = b.id AND a.status = b.status).
— Можно соединять таблицы по разным типам данных, но лучше, чтобы они совпадали.
— Псевдонимы (алиасы) таблиц делают запрос короче и читаемее.
— Можно соединять не две, а несколько таблиц.
ИЗ ОПЫТА
Однажды я писал отчёт и соединял заказы с клиентами через JOIN, думая, что у всех заказов есть клиенты.
Оказалось, что в тестовой базе были некорректные данные по заказам без клиентов. Отчёт показал не все данные.
С тех пор я всегда проверяю данные, прежде чем соединять таблицы, чтобы не потерять важную информацию.
ВОПРОСЫ И ОТВЕТЫ
В: Можно ли соединить больше двух таблиц?
О: Да. В примерах мы соединяли три таблицы: orders, clients, flowers.
В: Что такое декартово произведение?
О: Это когда каждая строка из первой таблицы соединяется с каждой строкой из второй. Если таблицы большие, результат может быть огромным.
В: Можно ли использовать JOIN с самим собой?
О: Да, это называется self-join. Мы рассмотрим это в одном из следующих шагов.
В: Нужно ли всегда указывать INNER перед JOIN?
О: Нет. JOIN сам по себе означает INNER JOIN. Это сокращение.
В: Что делать, если у двух таблиц одинаковые названия колонок?
О: Используйте псевдонимы таблиц, чтобы различать их. Например, orders.id и clients.id.
ПОПРОБУЙ САМ
— Выведи список заказов с именами клиентов (используй INNER JOIN).
— Выведи список заказов с названиями цветов (используй INNER JOIN).
— Выведи список заказов с названиями цветов и их оттенками (соедини три таблицы).
— Выведи список заказов с именами клиентов и названиями цветов (соедини три таблицы).
— Выведи заказы с цветами, цена которых больше 150 рублей (соединение с условием в WHERE).
— Выведи заказы с именами клиентов и названиями цветов, но только для клиентов из Москвы (city_id = 1).
Шаг 22. SQL и пустота. И причём здесь Правые и Левые (RIGHT JOIN и LEFT JOIN)
Когда данные справа и слева
ЖИВОЙ ПРИМЕР
Вы работаете с таблицей заказов orders. Вам нужно вывести все заказы, и если для заказа есть клиент — показать его имя. Если клиента нет — всё равно показать заказ.
В шаге 21 мы использовали INNER JOIN — он показывал только те строки, где есть совпадение в обеих таблицах.
Но что делать, если данные распределены по-разному: слева и справа?
Например, мы хотим видеть все заказы, даже если для некоторых из них не нашлось клиента, или всех клиентов, даже если у них нет заказов.
Для этого в SQL есть LEFT JOIN и RIGHT JOIN. Они помогают работать с 'пустотой' — когда данных нет, на их месте появляется NULL.
ЧТО ТАКОЕ NULL И IS NULL
NULL в SQL означает 'нет данных', 'неизвестно' или 'значение отсутствует'. Это не ноль и не пустая строка — это именно отсутствие информации.
Чтобы проверять, является ли значение NULL, нельзя использовать обычные операторы сравнения (=, <>). Вместо этого используются специальные операторы:
— IS NULL — проверяет, что значение равно NULL
— IS NOT NULL — проверяет, что значение не равно NULL
Примеры:
sql
— найти заказы, у которых нет клиента
SELECT * FROM orders o
LEFT JOIN clients c ON o.client_phone = c.phone
WHERE c.phone IS NULL;
Проверяем, что колонка phone из таблицы clients не содержит значения (клиент не найден).
sql
— найти клиентов с незаполненной датой рождения
SELECT name, phone, birth_date
FROM clients
WHERE birth_date IS NULL;
Выбираем клиентов, у которых дата рождения не указана (NULL).
sql
— найти клиентов, у которых дата рождения заполнена
SELECT name, phone, birth_date
FROM clients
WHERE birth_date IS NOT NULL;
Выбираем клиентов, у которых дата рождения указана.
ФУНКЦИЯ COALESCE
COALESCE — это функция, которая возвращает первое не-NULL значение из списка аргументов. Она очень полезна для замены NULL на значение по умолчанию.
Синтаксис:
sql
COALESCE (значение1, значение2, значение3, …)
Возвращает первый аргумент, который не равен NULL. Если все аргументы равны NULL, возвращает NULL.
Примеры:
sql
— Замена NULL на значение по умолчанию
SELECT name,
COALESCE (birth_date, '1900-01-01') AS birth_date_filled
FROM clients;
*Если дата рождения не указана (NULL), подставляем '1900-01-01'. *
sql
— В запросах с LEFT JOIN
SELECT o.id,
COALESCE(c.name, 'Клиент не найден') AS client_name
FROM orders o
LEFT JOIN clients c ON o.client_phone = c.phone;
Если клиент не найден (c.name = NULL), показываем текст 'Клиент не найден'.
sql
— С несколькими аргументами
SELECT COALESCE (phone, email, 'Нет контактов') AS contact
FROM clients;
Сначала проверяем телефон, если его нет — берём email, если и email нет — выводим 'Нет контактов'.
Почему это важно: COALESCE помогает избежать неожиданных NULL в результатах запросов и делает вывод данных более предсказуемым.
ВИДЫ JOIN (ПОВТОРЕНИЕ И ДОПОЛНЕНИЕ)
Где левая и правая таблица в запросе:
sql
SELECT…
FROM таблица_слева
LEFT JOIN таблица_справа ON условие
— Левая таблица — та, что указана перед словом JOIN
— Правая таблица — та, что указана после слова JOIN
Пример:
sql
SELECT *
FROM orders o — это левая таблица
LEFT JOIN clients c — это правая таблица
ON o.client_phone = c.phone;
LEFT JOIN сохранит все строки из orders (левая таблица), даже если для них нет клиента в clients.
sql
SELECT *
FROM orders o — это левая таблица
RIGHT JOIN clients c — это правая таблица
ON o.client_phone = c.phone;
RIGHT JOIN сохранит все строки из clients (правая таблица), даже если для них нет заказов в orders.
LEFT JOIN
LEFT JOIN возвращает все строки из левой таблицы (той, что указана перед LEFT JOIN), и только совпадающие из правой. Если для строки из левой таблицы нет совпадения в правой, то все колонки из правой таблицы будут заполнены значением NULL.
sql
SELECT o.id,
o. order_ts,
c.name AS client_name
FROM orders o
LEFT JOIN clients c ON o.client_phone = c.phone;
Берём все заказы из таблицы orders. Для каждого заказа ищем клиента в таблице clients. Если клиент найден — показываем его имя. Если нет — в колонке client_name будет NULL.
Результат (фрагмент):
text
id order_ts client_name
1 2026-02-10 10:15:00 Николай
2 2026-02-12 14:30:00 Елена
…
100 2026-03-15 14:15:00 NULL
NULL И IS NULL В JOIN
NULL в результатах LEFT JOIN — это признак того, что для строки из левой таблицы не нашлось соответствующей строки в правой.
Чтобы найти такие строки, используют условие IS NULL:
sql
— найти заказы, для которых нет клиента
SELECT o.id, o. order_ts
FROM orders o
LEFT JOIN clients c ON o.client_phone = c.phone
WHERE c.phone IS NULL;
Сначала соединяем заказы с клиентами через LEFT JOIN, затем оставляем только те строки, где клиент не найден (c.phone IS NULL).
RIGHT JOIN
RIGHT JOIN работает так же, как LEFT JOIN, но все строки берутся из правой таблицы (той, что указана после RIGHT JOIN). Если для строки из правой таблицы нет совпадения в левой, то все колонки из левой таблицы будут заполнены значением NULL.
sql
SELECT c.name,
o.id AS order_id,
o. order_ts
FROM orders o
RIGHT JOIN clients c ON o.client_phone = c.phone;
Берём всех клиентов из таблицы clients. Для каждого клиента ищем заказы в таблице orders. Если заказы найдены — показываем их. Если нет — в колонках order_id и order_ts будет NULL.
Результат (фрагмент):
text
name order_id order_ts
Николай 1 2026-02-10 10:15:00
Николай 4 2026-02-18 16:20:00
Елена 2 2026-02-12 14:30:00
Елена 7 2026-02-23 11:40:00
Анна 5 2026-02-20 12:10:00
…
Светлана NULL NULL
RIGHT JOIN НА ПРАКТИКЕ
На практике RIGHT JOIN используют редко, потому что тот же результат можно получить, поменяв таблицы местами и использовав LEFT JOIN:
sql
— то же самое, что и RIGHT JOIN выше
SELECT c.name,
o.id AS order_id,
o. order_ts
FROM clients c
LEFT JOIN orders o ON c.phone = o.client_phone;
Мы просто поменяли таблицы местами и заменили RIGHT JOIN на LEFT JOIN. Результат будет точно таким же.
ПРИМЕРЫ
Пример 1. Вывести все заказы и имена клиентов (LEFT JOIN).
sql
SELECT o.id,
o. order_ts,
c.name AS client_name
FROM orders o
LEFT JOIN clients c ON o.client_phone = c.phone;
Все заказы остаются в результате. Если клиент найден — показываем его имя, если нет — NULL.
Пример 2. Вывести всех клиентов и их заказы (LEFT JOIN).
sql
SELECT c.name,
o.id AS order_id,
o. order_ts
FROM clients c
LEFT JOIN orders o ON c.phone = o.client_phone;
Все клиенты остаются в результате. Если у клиента есть заказы — показываем их. Если нет — в колонках order_id и order_ts будет NULL.
Результат (фрагмент):
name order_id order_ts
Николай 1 2026-02-10 10:15:00
Николай 4 2026-02-18 16:20:00
Елена 2 2026-02-12 14:30:00
Елена 7 2026-02-23 11:40:00
Анна 5 2026-02-20 12:10:00
…
Светлана NULL NULL
Пример 3. Вывести заказы, для которых нет клиента (LEFT JOIN + проверка на NULL).
sql
SELECT o.id,
o. order_ts
FROM orders o
LEFT JOIN clients c ON o.client_phone = c.phone
WHERE c.phone IS NULL;
Сначала соединяем заказы с клиентами через LEFT JOIN, затем оставляем только те строки, где клиент не найден (c.phone IS NULL).
Пример 4. Вывести клиентов, у которых нет заказов (LEFT JOIN + проверка на NULL).
sql
SELECT c.name,
c.phone
FROM clients c
LEFT JOIN orders o ON c.phone = o.client_phone
WHERE o.id IS NULL;
Соединяем клиентов с заказами через LEFT JOIN, затем оставляем только тех клиентов, у которых нет заказов (o.id IS NULL).
Результат (фрагмент):
name phone
Светлана 433334455
Виктор 834445566
ТИПИЧНЫЕ ОШИБКИ
Ошибка 1. Забыть LEFT JOIN и использовать INNER JOIN когда нужно сохранить все строки из левой таблицы.
Неверно:
sql
SELECT * FROM orders o JOIN clients c ON o.client_phone = c.phone;
Верно:
sql
SELECT * FROM orders o LEFT JOIN clients c ON o.client_phone = c.phone;
Ошибка 2. Использовать RIGHT JOIN когда проще поменять таблицы местами и использовать LEFT JOIN.
Код с LEFT JOIN читается легче, так как логика идёт слева направо.
Ошибка 3. Путать IS NULL и = NULL.
Неверно:
sql
WHERE c.phone = NULL
Верно:
sql
WHERE c.phone IS NULL
ТОНКОСТИ И НЮАНСЫ
— NULL в SQL означает отсутствие значения. Это не ноль и не пустая строка.
— Для проверки на NULL используйте IS NULL или IS NOT NULL.
— LEFT JOIN сохраняет все строки из левой таблицы, RIGHT JOIN — из правой.
— На практике RIGHT JOIN используется редко — его почти всегда можно заменить на LEFT JOIN, поменяв таблицы местами.
— COALESCE помогает заменить NULL на значение по умолчанию.
ИЗ ОПЫТА
Однажды я писал отчёт и соединял заказы с клиентами через JOIN, думая, что у всех заказов есть клиенты.
Оказалось, что в тестовой базе были некорректные данные — заказы без клиентов. Отчёт показал не все данные.
С тех пор я всегда проверяю данные, прежде чем соединять таблицы, чтобы не потерять важную информацию. А если нужно сохранить все строки из основной таблицы — использую LEFT JOIN.
ВОПРОСЫ И ОТВЕТЫ
В: Чем отличается LEFT JOIN от INNER JOIN?
О: INNER JOIN возвращает только строки с совпадениями в обеих таблицах. LEFT JOIN возвращает все строки из левой таблицы, даже если совпадений нет.
В: Что такое NULL в результатах LEFT JOIN?
О: Это означает, что для строки из левой таблицы не нашлось соответствующей строки в правой таблице.
В: Как найти клиентов без заказов?
О: Использовать LEFT JOIN и проверить, что колонка из таблицы заказов равна NULL:
sql
SELECT * FROM clients c
LEFT JOIN orders o ON c.phone = o.client_phone
WHERE o.id IS NULL;
В: Зачем нужен RIGHT JOIN, если есть LEFT JOIN?
О: RIGHT JOIN редко используется на практике, потому что его всегда можно заменить на LEFT JOIN, поменяв таблицы местами. Но знать о его существовании полезно.
В: Для чего нужна функция COALESCE?
О: COALESCE возвращает первое не-NULL значение из списка аргументов. Она полезна для замены NULL на значение по умолчанию.
ПОПРОБУЙ САМ
— Выведите все заказы и имена клиентов, используя LEFT JOIN.
— Выведите всех клиентов и их заказы, используя LEFT JOIN.
— Найдите заказы, для которых нет клиента (используйте LEFT JOIN и IS NULL).
— Найдите клиентов, у которых нет заказов (используйте LEFT JOIN и IS NULL).
— Перепишите запрос с RIGHT JOIN на LEFT JOIN, поменяв таблицы местами.
— Используйте COALESCE, чтобы заменить NULL в именах клиентов на текст 'Клиент не найден'.
ЗАДАЧИ НА ПОВТОРЕНИЕ
— Выведите список заказов с именами клиентов. Используйте INNER JOIN. (шаг 21)
— Выведите список заказов с названиями цветов. Используйте INNER JOIN. (шаг 21)
— Выведите список заказов с названиями цветов и их оттенками. Используйте соединение трёх таблиц: orders → flowers → colors. (шаг 21)
Шаг 23. Голова кусает хвост или объединяем таблицу с самой собой
Когда одной таблицы становится недостаточно
ЖИВОЙ ПРИМЕР
Представьте, что вы снова в службе доставки цветов. Вы работаете с таблицей клиентов, и вам надо найти всех клиентов, которые родились в один и тот же день.
Как это сделать? Нам нужно сравнить каждого клиента с другими клиентами из той же таблицы.
Здесь нам нужна одна и та же таблица, но в двух ролях.
В SQL для этого есть самообъединение (self-join) — соединение таблицы с самой собой.
Таблица объединяется как бы со своей копией, со своим отражением, причем эти копиям даются разные названия — псевдонимы.
ЧТО ТАКОЕ САМООБЪЕДИНЕНИЕ
Самообъединение (self-join) — это когда таблица соединяется сама с собой через JOIN.
Чтобы различать 'копии' одной таблицы, используются псевдонимы (алиасы).
sql
SELECT *
FROM таблица A
JOIN таблица B ON A. колонка = B. колонка
Здесь A и B — это одна и та же таблица, но с разными псевдонимами
Пример: найти клиентов, которые родились в один и тот же день.
sql
SELECT c1.name AS client1,
c2.name AS client2,
c1.birth_date
FROM clients c1
JOIN clients c2 ON c1.birth_date = c2.birth_date
WHERE c1.phone!= c2.phone
Соединяем таблицу clients саму с собой по дате рождения. Условие c1.phone!= c2.phone убирает пары, где клиент сравнивается сам с собой.
ПРИМЕРЫ
Пример 1. Найти клиентов, которые родились в один и тот же день.
sql
SELECT c1.name AS client1,
c2.name AS client2,
c1.birth_date
FROM clients c1
JOIN clients c2 ON c1.birth_date = c2.birth_date
WHERE c1.phone!= c2.phone
Соединяем таблицу clients саму с собой по дате рождения. Условие c1.phone!= c2.phone убирает пары, где клиент сравнивается сам с собой.
Результат (фрагмент):
text
client1 client2 birth_date
Николай Дмитрий 1980-05-10
Дмитрий Николай 1980-05-10
Елена Алексей 1992-08-22
Алексей Елена 1992-08-22
Пример 2. Найти клиентов, которые живут в одном городе.
sql
SELECT c1.name AS client1,
c2.name AS client2,
c1.city_id
FROM clients c1
JOIN clients c2 ON c1.city_id = c2.city_id
WHERE c1.phone!= c2.phone
Соединяем таблицу clients саму с собой по city_id. c1.phone!= c2.phone исключает совпадение клиента с самим собой.
Пример 3. Найти клиентов, которые живут в одном городе, и вывести название города.
sql
SELECT c1.name AS client1,
c2.name AS client2,
ct.name AS city
FROM clients c1
JOIN clients c2 ON c1.city_id = c2.city_id
JOIN cities ct ON c1.city_id = ct.id
WHERE c1.phone!= c2.phone
Соединяем клиентов друг с другом по городу, затем присоединяем таблицу cities, чтобы получить название города.
ТИПИЧНЫЕ ОШИБКИ
Ошибка 1. Забыть псевдонимы.
Без псевдонимов PostgreSQL не сможет различить две копии одной таблицы.
Неверно:
sql
SELECT name FROM clients JOIN clients ON birth_date = birth_date
Верно:
sql
SELECT c1.name FROM clients c1 JOIN clients c2 ON c1.birth_date = c2.birth_date
Ошибка 2. Использовать INNER JOIN, когда нужен LEFT JOIN.
Если у некоторых строк нет связи (например, у клиента нет пары с той же датой рождения), INNER JOIN их исключит.
Чтобы сохранить все строки, используйте LEFT JOIN.
sql
— все клиенты, даже если нет пары
SELECT c1.name, c2.name
FROM clients c1
LEFT JOIN clients c2 ON c1.birth_date = c2.birth_date AND c1.phone!= c2.phone
Ошибка 3. Забыть условие c1.phone!= c2.phone при поиске пар.
Без этого условия клиент будет соединён сам с собой, и в результате появятся 'пары' с одинаковыми именами.
Неверно:
sql
SELECT c1.name, c2.name
FROM clients c1
JOIN clients c2 ON c1.birth_date = c2.birth_date
В результате будет, например, 'Николай — Николай'.
Верно:
sql
SELECT c1.name, c2.name
FROM clients c1
JOIN clients c2 ON c1.birth_date = c2.birth_date
WHERE c1.phone!= c2.phone
Ошибка 4. Путать порядок таблиц в LEFT JOIN при самообъединении.
Порядок важен: левая таблица — это та, все строки которой мы хотим видеть.
sql
— оставляем всех клиентов из левой таблицы (c1)
SELECT c1.name, c2.name
FROM clients c1
LEFT JOIN clients c2 ON c1.birth_date = c2.birth_date AND c1.phone!= c2.phone
ТОНКОСТИ И НЮАНСЫ
— Самообъединение — это обычный JOIN, со всеми его типами (INNER, LEFT, RIGHT). Выбор типа зависит от задачи.
— Псевдонимы обязательны — без них запрос не выполнится.
— Самообъединение часто используют для работы с иерархическими данными (структура сотрудников, категории товаров, комментарии с ответами).
— В условиях соединения можно использовать не только равенство, но и операторы сравнения (>, <,> =, <=).
sql
— найти все пары клиентов, где один родился раньше другого
SELECT c1.name AS older,
c2.name AS younger,
c1.birth_date AS older_birth,
c2.birth_date AS younger_birth
FROM clients c1
JOIN clients c2 ON c1.birth_date <c2.birth_date
— Для поиска всех пар без дублирования (например, 'Николай — Дмитрий' и 'Дмитрий — Николай') можно добавить условие c1.phone> c2.phone вместо!=. Это оставит только одну пару.
sql
— уникальные пары без дублирования
SELECT c1.name, c2.name
FROM clients c1
JOIN clients c2 ON c1.birth_date = c2.birth_date
WHERE c1.phone> c2.phone
ИЗ ОПЫТА
Однажды мне нужно было найти всех клиентов, которые родились в один день, но без дублирования пар (чтобы каждая пара встречалась только один раз).
Сначала я написал:
sql
SELECT c1.name, c2.name
FROM clients c1
JOIN clients c2 ON c1.birth_date = c2.birth_date
WHERE c1.phone!= c2.phone
В результате получил пары 'Николай — Дмитрий' и 'Дмитрий — Николай'. Это не ошибка, но для отчёта хотелось видеть каждую пару только раз.
Тогда я заменил условие на c1.phone> c2.phone:
sql
SELECT c1.name, c2.name
FROM clients c1
JOIN clients c2 ON c1.birth_date = c2.birth_date
WHERE c1.phone> c2.phone
Теперь каждая пара встречалась только один раз. Этот приём часто помогает избежать дублирования при самообъединении.
ВОПРОСЫ И ОТВЕТЫ
В: Что такое самообъединение?
О: Это соединение таблицы с самой собой с помощью JOIN. Чтобы различать 'копии', используют псевдонимы.
В: Зачем нужны псевдонимы в самообъединении?
О: Чтобы база данных понимала, о какой копии таблицы идёт речь.
В: Можно ли использовать LEFT JOIN в самообъединении?
О: Да. Это полезно, чтобы не потерять строки, у которых нет пары (например, клиенты, у которых нет других клиентов с той же датой рождения).
В: Как избежать дублирования пар (когда пара встречается дважды)?
О: Добавьте условие с оператором сравнения, например c1.phone> c2.phone.
sql
SELECT c1.name, c2.name
FROM clients c1
JOIN clients c2 ON c1.birth_date = c2.birth_date
WHERE c1.phone> c2.phone
В: Можно ли использовать самообъединение с подзапросами?
О: Да, но это уже более сложный случай — рекурсивные запросы. Они рассмотрены в отдельном шаге.
ПОПРОБУЙ САМ
У нас есть таблица employees мы создали ее в начале книги:
— Выведи список сотрудников с именами их руководителей.
— Найди сотрудников, у которых один и тот же руководитель.
— Найди сотрудников, которые зарабатывают больше своего руководителя.
— Найди сотрудников, у которых нет руководителя.
— Выведи список сотрудников с именами их руководителей и руководителей руководителей (для этого понадобится два самообъединения).
ЗАДАЧИ НА ПОВТОРЕНИЕ
— Сгенерируйте все дни текущего месяца. Используйте generate_series. (шаг 20)
— Сгенерируйте последовательность из 10 случайных чисел. Используйте generate_series и random (). (шаг 20)
Шаг 24. Объединяем, пересекаем, вычитаем. Работа с множествами (UNION, INTERSECT, EXCEPT)
Как собрать данные из разных запросов в один результат
ЖИВОЙ ПРИМЕР
Предположим, что вам нужно получить общий список всех клиентов, которые делали заказы в феврале, а также всех клиентов, которые делали заказы в марте.
Вам нужен общий список — без дубликатов.
Для решения подобных задач в SQL есть специальные операторы для работы с множествами:
— UNION (объединение) — объединяет результаты двух запросов, убирая дубликаты строк
— UNION ALL (объединение) — объединяет результаты двух запросов, но дубликаты остаются
— INTERSECT (пересечение) — возвращает только те строки, которые есть в обоих запросах
— EXCEPT (исключение) — возвращает те данные, которые есть в первом запросе, но нет во втором
ПРАВИЛА РАБОТЫ С ОПЕРАТОРАМИ МНОЖЕСТВ
Чтобы два запроса можно было объединить, они должны соответствовать простым правилам:
— Одинаковое количество колонок в обоих запросах.
— Совместимые типы данных — колонки должны быть одного типа или приводиться к одному типу.
— Порядок колонок важен — первая колонка первого запроса соединяется с первой колонкой второго, и так далее.
При нарушении любого из этих условий запрос вернёт ошибку.
UNION И UNION ALL
UNION объединяет результаты двух запросов, убирая дубликаты.
UNION ALL объединяет, оставляя все строки, включая дубликаты (работает быстрее).
Задача: получить список телефонов всех клиентов и всех клиентов, которые делали заказы (объединённый список).
sql
SELECT phone FROM clients
UNION
SELECT client_phone FROM orders
Что тут происходит
— Первый запрос возвращает телефоны всех клиентов.
— Второй — телефоны всех, кто делал заказы.
— UNION убирает дубликаты (телефоны, которые есть в обеих таблицах).
С UNION ALL дубликаты останутся:
sql
SELECT phone FROM clients
UNION ALL
SELECT client_phone FROM orders
INTERSECT
INTERSECT возвращает только те строки, которые есть в обоих запросах.
Задача: найти клиентов, у которых есть отменённые заказы.
sql
SELECT phone FROM clients
INTERSECT
SELECT client_phone
FROM orders WHERE status = 'отменён' /* */
Что тут происходит
— Первый запрос возвращает телефоны всех клиентов.
— Второй — телефоны клиентов с отменёнными заказами.
— INTERSECT оставляет только телефоны, которые есть в обоих результатах.
EXCEPT
EXCEPT возвращает строки из первого запроса, которых нет во втором.
Задача: найти клиентов, которые не делали ни одного заказа.
sql
SELECT phone FROM clients
EXCEPT
SELECT client_phone FROM orders
Что тут происходит
— Первый запрос возвращает телефоны всех клиентов.
— Второй — телефоны всех, кто делал заказы.
— EXCEPT оставляет только тех, кто есть в первом списке, но отсутствует во втором.
ПРИМЕРЫ
Пример 1. Объединить имена клиентов из Москвы и Санкт-Петербурга (UNION).
sql
SELECT name FROM clients WHERE city_id = 1
UNION
SELECT name FROM clients WHERE city_id = 2
Выбираем имена клиентов из Москвы и Санкт-Петербурга, объединяем в один список, убираем дубликаты.
Результат (фрагмент):
text
name
Николай
Дмитрий
Татьяна
Елена
Алексей
Пример 2. Получить все названия цветов и все оттенки в одном списке (UNION).
sql
SELECT name FROM flowers
UNION
SELECT name FROM colors
В одном списке будут и названия цветов, и названия оттенков.
Результат (фрагмент):
text
name
Роза
Тюльпан
Хризантема
Лилия
Пион
Гербера
Орхидея
Ромашка
красный
белый
розовый
Пример 3. Найти клиентов, которые делали заказы в феврале и в марте (INTERSECT).
sql
SELECT client_phone FROM orders WHERE order_ts> = '2026-02-01' AND order_ts <'2026-03-01' /* */
INTERSECT
SELECT client_phone FROM orders WHERE order_ts> = '2026-03-01' AND order_ts <'2026-04-01' /* */
Находим телефоны клиентов, которые делали заказы в феврале, и пересекаем с теми, кто делал заказы в марте.
Результат (фрагмент):
text
client_phone
112345678
…
Пример 4. Найти клиентов, которые делали заказы в феврале, но не в марте (EXCEPT).
sql
SELECT client_phone FROM orders WHERE order_ts> = '2026-02-01' AND order_ts <'2026-03-01' /* */
EXCEPT
Бесплатный фрагмент закончился.
Купите книгу, чтобы продолжить чтение.