Для подключения к базе данных PostgreSQL необходимо в окружение установить соответствующий драйвер psycopg2:
# (base) Anaconda Prompt
conda install -n ds-book --override-channels -c conda-forge -c defaults psycopg2import pandas as pd
import sqlalchemy as sa
import sql
pd.set_option('display.max_rows', 10)
connection_url = sa.engine.URL.create(
drivername="postgresql+psycopg2",
host="95.163.241.236",
port=5432,
database="simulative",
username="student",
password="qweasd963",
)
engine = sa.create_engine(connection_url)
%load_ext sql
%config SqlMagic.displaycon = False
%config SqlMagic.autopandas = True
%sql engine
print(f"Pandas ver. {pd.__version__}: порог усечения строк уменьшен до 10")
print(f"SQLAlchemy ver. {sa.__version__}: подключение создано")
print(f"JupySQL ver. {sql.__version__}: подключен через SQLAlchemy Engine")Pandas ver. 3.0.5: порог усечения строк уменьшен до 10
SQLAlchemy ver. 2.0.52: подключение создано
JupySQL ver. 0.11.1: подключен через SQLAlchemy Engine
Интро¶
Ссылка на Бесплатный интенсив Основы SQL
Расписание
День 1
Введение и подключение к БД
Базовый синтаксис (SELECT, сортировка)
День 2
Горизонтальная фильтрация WHERE
Скалярные функции (текстовые
День 3
Полезные операторы
Скалярные функции (работа с датой и временем)
День 4
Дополнительные приемы SQL
Соединение таблиц
День 5
Объединение таблиц
Группировки
День 6
Подзапросы и CTE
Оконные функции
День 7
Допроходим уроки
Финальный проект
День 8
Доделываем проект и заканчиваем курс
Вводная инфа
Разбор задач с собеседований SQL: агрегация и группировка данных
Описание таблиц учебной базы данных курса
Дорожная карта аналитика данных
Блог с полезностями от Симулейтив
Статьи [1] от Евгения Буторина
Полезности из чата
SQL Fiddle - онлайн-редактор (компилятор) SQL-запросов для разных СУБД (MySQL, PostgreSQL, SQLLite ... )
Примечание
Первые три дня делал в DBeaver, потом ушел в JupyterLab.
В интерфейсе JupyterLab столбцы помещаются как в DBeaver. Но не на сайте Jupyter Book. Поэтому:
чтобы столбцы на сайте умещались по ширине без горизонтальной прокрутки, старался ужать вывод за счет выбора полей в SELECT;
чтобы перечисляемое количество полей не отвлекало от сути, визуально отделял суть через дополнительный комментарий
--.
Pandas используется исключительно для усечения строк вывода.
3. Базовый синтаксис¶
В этом уроке вы сделаете первые уверенные шаги в SQL. Мы разберём, как извлекать данные из таблиц, выбирать конкретные столбцы или все сразу. А чтобы данные были не только полезными, но и удобными для восприятия - научимся сортировать результаты.
Вы изучите:
Команду
SELECTи выбор нужных колонокИспользование псевдонимов (
AS)Сортировку результатов с помощью
ORDER BY,ASC,DESC
SELECT¶
%%sql
SELECT * FROM users;AS¶
%%sql
SELECT
u.username login,
u.date_joined AS created_at
FROM users u;ORDER BY¶
ASC¶
%%sql
-- ASC сортировка по возрастанию (по умолчанию - можно не указывать)
SELECT
id,
username,
first_name,
last_name,
date_joined
FROM users u
ORDER BY date_joined ASC;DESC¶
%%sql
-- DESC сортировка по убыванию
SELECT
id,
username,
first_name,
last_name,
date_joined
FROM users u
ORDER BY date_joined DESC;%%sql
SELECT
id,
username,
first_name,
last_name,
is_active
FROM users u
ORDER BY is_active;%%sql
SELECT
id,
first_name,
last_name,
is_active,
date_joined
FROM users u
ORDER BY is_active, date_joined DESC;%%sql
SELECT
id,
username,
email,
date_joined
FROM users
ORDER BY date_joined DESC, id;Практика #1¶
Задание 1
Из таблицы users требуется вывести следующую информацию о пользователях:
Идентификатор
Логин
Почта
Дата и время регистрации
Столбцы в результате:
idusernameemaildate_joined
Сортировка:
Результат отсортируйте сначала по убыванию поля date_joined, а затем по возрастанию поля id.
%%sql
select
id,
username,
email,
date_joined
from users
order by date_joined desc, id;Куратор¶
Curse of knowledge (проклятие знания):
Это когда опытный специалист настолько привык к вещам, которые делает каждый день годами, что для него это становится воздухом. Ему кажется: Ну это же очевидно! – и он просто забывает, как на это смотрит человек, делающий первые шаги.
4. Горизонтальная фильтрация¶
В этом уроке мы научимся вырезать из таблицы только нужные нам строки по определенным условиям, ведь без фильтрации не обходится ни один реальный SQL-запрос.
Вы изучите:
Условия сравнения:
=,!=,<,>,BETWEENЛогические операторы:
AND,OR,NOTРабота с NULL:
IS NULL,IS NOT NULLФильтрацию по строкам, числам и датам
%%sql
SELECT
id,
username,
first_name,
last_name,
email
FROM users
WHERE id = 198;%%sql
SELECT
id,
username,
first_name,
last_name,
email
FROM users
WHERE 1 = 1;%%sql
SELECT
id,
username,
first_name,
last_name,
email
FROM users
WHERE True;AND¶
%%sql
SELECT
id,
username,
first_name,
last_name,
is_active,
referal_user,
company_id,
score
FROM users
--
WHERE id = 198 AND True;IS NOT NULL¶
%%sql
SELECT
id,
first_name,
last_name,
is_active,
referal_user,
company_id,
score
FROM users
--
WHERE is_active = 1 AND company_id IS NOT null;OR¶
%%sql
SELECT
id,
first_name,
last_name,
is_active,
referal_user,
company_id,
score
FROM users
--
WHERE referal_user IS NOT NULL OR company_id IS NOT NULL;%%sql
SELECT
id,
first_name,
last_name,
is_active,
referal_user,
company_id,
score
FROM users
--
WHERE referal_user IS NOT NULL OR company_id IS NOT NULL OR score > 1000;%%sql
SELECT
id,
first_name,
last_name,
is_active,
referal_user,
company_id,
score
FROM users
--
WHERE referal_user IS NOT NULL OR company_id IS NOT NULL OR score > 500
ORDER BY score DESC;%%sql
SELECT
id,
first_name,
last_name,
is_active,
referal_user,
company_id,
score
FROM users
--
WHERE is_active + 1 = 2;%%sql
SELECT id,
first_name,
last_name,
is_active,
referal_user,
company_id,
score
FROM users
--
WHERE
referal_user IS NOT NULL
AND
company_id IS NOT NULL
OR
score > 500
ORDER BY score DESC;%%sql
SELECT
id,
username,
first_name,
last_name,
is_active,
referal_user,
company_id,
score
FROM users
--
WHERE
referal_user IS NOT NULL
AND
(company_id IS NOT NULL
OR
score > 500)
ORDER BY score DESC;%%sql
SELECT
id,
username,
first_name,
last_name,
is_active,
referal_user,
company_id,
score
FROM users
--
WHERE
referal_user IS NOT NULL
OR
company_id IS NOT NULL
AND
score > 500
ORDER BY score DESC;%%sql
SELECT
id,
username,
first_name,
last_name,
is_active,
referal_user,
company_id,
score
FROM users
--
WHERE
(referal_user IS NOT NULL
OR
company_id IS NOT NULL)
AND
score > 500
ORDER BY score DESC;%%sql
-- AND имеет приоритет над OR
-- Всегда расставляйте скобки
SELECT
id,
username,
first_name,
last_name,
is_active,
referal_user,
company_id,
score
FROM users
--
WHERE
(
referal_user IS NOT NULL
)
OR
(
company_id IS NOT NULL
AND
score > 500
)
OR
(
is_active = 0
)
ORDER BY score DESC;%%sql
SELECT
id,
username,
first_name,
last_name,
is_active,
referal_user,
company_id,
score
FROM users
--
WHERE company_id != 7; -- Пропали значения NULLIS NULL¶
%%sql
SELECT
id,
first_name,
last_name,
is_active,
referal_user,
company_id,
score
FROM users
--
WHERE company_id != 7 OR company_id IS NULL;%%sql
SELECT
is_active,
is_active + 1 AS new_col
FROM users;%%sql
SELECT
is_active,
is_active + 1 AS new_col
FROM users
WHERE new_col = 2;
-- ERROR мы не можем использовать алиасы из ТЕКУЩЕГО запроса в фильтрацииRuntimeError: (psycopg2.errors.UndefinedColumn) column "new_col" does not exist
LINE 5: WHERE new_col = 2;
^
[SQL: SELECT
is_active,
is_active + 1 AS new_col
FROM users
WHERE new_col = 2;]
(Background on this error at: https://sqlalche.me/e/20/f405)
%%sql
SELECT
is_active,
is_active + 1 AS new_col
FROM users
WHERE is_active + 1 = 2;Практика #2¶
Задание 1
Из таблицы users требуется вывести информацию о пользователях, у которых либо не указана фамилия либо количество очков опыта строго больше 100:
Идентификатор
Логин
Количество очков опыта на платформе
Столбцы в результате:
idusernamescore
Сортировка:
Результат отсортируйте по возрастанию поля id.
%%sql
-- Практика 2. Задача 1
SELECT
id,
username,
score
FROM users
WHERE
score > 100 OR last_name IS NULL
ORDER BY id;Задание 2
Из таблицы users требуется вывести информацию об активных пользователях (is_active = 1), которые при этом либо имеют количество очков опыта строго больше 500, либо относятся к компании с id = 7:
Идентификатор
Логин
Идентификатор компании
Количество очков опыта на платформе
Столбцы в результате: \
idusernamecompany_idscore
Сортировка:
Результат отсортируйте по возрастанию поля id.
%%sql
-- Практика 2. Задача 2
SELECT
id,
username,
company_id,
score
FROM users
WHERE
is_active = 1
AND
(score > 500 OR company_id = 7)
ORDER BY id;Дополнительное задание
Что выведет запрос?
SELECT * FROM table
ORDER BY name ASC, name DESC%%sql
-- Задача из чата
SELECT * FROM users
ORDER BY first_name ASC, first_name DESC;Выведет все строки таблицы table по возрастанию поля name
table по возрастанию поля nameЯ так понял: DESC - это дополнительное условие к первому (ASC). \
ASCотсортировал по возрастанию - разбил на группы одинаковых имен.
Анна
Анна
Анна \DESC- если бы были фамилии, то он отсортировал бы получившиеся группы дополнительно по убыванию фамилий. Но поскольку снова сортировка по name, то SQL сортирует (пытается сортировать) получившиеся группы внутри себя. Анна
Анна
Анна
Сортирует - сравнивает одинаковые слова - не находит между ними разницы и оставляет в том порядке, который получил после ASC
5. Скалярные функции (текстовые)¶
В этом уроке мы научимся работать со строками: изменять регистр, обрезать лишнее, извлекать подстроки, склеивать значения и искать/заменять фрагменты. Эти функции пригодятся вам каждый раз, когда нужно почистить или привести к нужному виду текстовые данные.
Вы изучите:
Изменение регистра:
LOWER,UPPER,INITCAPРабота с длиной и пробелами:
LENGTHИзвлечение подстрок:
LEFT,RIGHTПоиск позиции фрагмента:
POSITIONФильтрация по шаблону:
LIKE,ILIKE
LOWER( )¶
%%sql
SELECT LOWER('ПриВЕт');%%sql
-- в двойных кавычках в PostgreSQL указываются объекты БД:
-- названия столбцов, названия таблиц - чтобы явно отличить от зарезервированных слов
SELECT
LOWER('ПриВЕт') AS "lower_case", -- в двойных кавычках
'ПриВЕт' AS init_phrase;UPPER( )¶
%%sql
SELECT UPPER('ПриВЕт');INITCAP( )¶
%%sql
SELECT
name,
LOWER(name),
UPPER(name),
INITCAP(name)
FROM problem;%%sql
SELECT id, username, first_name, last_name,
is_active, referal_user, company_id, score
FROM users
--
WHERE LOWER(username) = 'девелопер';LIKE( )¶
%%sql
SELECT
id,
name
FROM problem
--
WHERE lower(name) LIKE '%new%'; -- Регистрозависимый поискiLIKE( )¶
%%sql
SELECT
id,
name
FROM problem
--
WHERE name iLIKE '%new%'; -- регистроНЕзависимый поискLENGTH( )¶
%%sql
SELECT name, length(name)
FROM problem;RIGHT( )¶
%%sql
SELECT email, right(email, 9)
FROM users;POSITION( )¶
%%sql
SELECT position('@' IN email)
FROM users;LEFT( )¶
%%sql
SELECT email, left(email, position('@' IN email))
FROM users;%%sql
SELECT email, left(email, position('@' IN email) - 1)
FROM users;%%sql
SELECT
email,
left(email, position('@' IN email) - 1) AS left_part,
right(email, length(email) - position('@' IN email)) AS right_part
FROM users;Практика #3¶
Задание 1
Из таблицы users требуется вывести следующую информацию о пользователях:
Идентификатор
Логин
Имя
Фамилия
Имя в нижнем регистре
Фамилия в верхнем регистре
Длина логина
Столбцы в результате:
idusernamefirst_namelast_namelower_first_nameupper_last_namelength_username
Сортировка:
Результат отсортируйте сначала по убыванию length_username, затем по возрастанию id.
%%sql
SELECT
id,
username,
first_name,
last_name,
LOWER(first_name) lower_first_name,
UPPER(last_name) upper_last_name,
length(username) length_username
FROM users
ORDER BY length_username DESC, id;Задание 2
Из таблицы users требуется вывести следующую информацию о пользователях:
Идентификатор
Логин
Почта
Домен электронной почты (вычисляется как часть электронной почты после символа @)
Столбцы в результате:
idusernameemaildomain
Сортировка:
Результат отсортируйте по возрастанию поля id.
%%sql
SELECT
id,
username,
email,
right(email, length(email) - position('@' IN email)) AS domain
FROM users
ORDER BY id;Задание 3
Из таблицы users требуется вывести информацию о пользователях, которые имеют домен электронной почты bk.ru.
Столбцы в результате:
idusernameemail
Сортировка:
Результат отсортируйте по возрастанию поля id.
%%sql
SELECT
id,
username,
email
FROM users
WHERE lower(email) LIKE '%@bk.ru'
ORDER BY id;Дополнительная задача
Как можно отфильтровать из таблицы все платежи по жку (может писаться в description как ЖКУ, жку, ЖКХ, жкх), с тем ограничением, что диалект нашего SQL поддерживает только LIKE? При этом в описании могут быть слова «платежку», «задержку» и тд. Также могут быть знаки препинания, как до, так и после. Слово может быть и первым, и последний, и по середине.
Жду ваши ответы)) Пример начала запроса:
SELECT * FROM transaction
WHERE description like …Понимаю, что оптимально здесь использовать регулярные выражения, но прошу решить задачу без них)
Решение 1 (в лоб)
%%sql
SELECT *
FROM transaction
WHERE LOWER(description) LIKE '% жку %'
OR LOWER(description) LIKE 'жку %'
OR LOWER(description) LIKE '% жку'
OR LOWER(description) = 'жку'
OR LOWER(description) LIKE '% жкх %'
OR LOWER(description) LIKE 'жкх %'
OR LOWER(description) LIKE '% жкх'
OR LOWER(description) = 'жкх';Решение 2 (регулярные выражения)
Как эту проблему решают регулярные выражения RegEx:
Регулярные выражения – это специальный язык описания текстовых шаблонов. В PostgreSQL для этого есть оператор ~* (поиск по регулярному выражению без учета регистра). Идеальный и пуленепробиваемый запрос выглядел бы так:
SELECT * FROM transaction
WHERE description ~* '\b(жку|жкх)\b';Почему это магия:
\b– это специальный символ регулярных выражений, который означает граница слова (word boundary).Границей слова движок RegEx считает всё что угодно, кроме букв: пробел, точку, запятую, скобку, начало строки или конец строки.
Этот запрос мгновенно найдет “ЖКУ,”, “(ЖКУ)”, “ЖКУ.”, “оплата ЖКУ” и при этом железобетонно проигнорирует “платежку”, потому что между буквой е и ж в слове платежку нет границы слова!
Поэтому регулярные выражения оптимальны: они пишутся в одну строчку и учитывают абсолютно все знаки препинания в мире за один раз.
Круглые скобки
()создают группу условий. Они ограничивают зону действия вертикальной черты (как скобки в математике).Символ
|разделяет варианты внутри этой группы. Конструкция (жку|жкх) говорит движку регулярных выражений: Найди мне либо точную последовательность букв “жку”, ЛИБО последовательность “жкх”. Если бы символа|не существовало, пришлось бы писать два отдельных регулярных выражения и связывать их через привычный SQL-оператор OR:
WHERE description ~* '\bжку\b'
OR description ~* '\bжкх\b'Решение 3 (элегантное):
SELECT *
FROM transaction
WHERE INITCAP(description) LIKE '%Жку%'
OR INITCAP(description) LIKE '%Жкх%';%%sql
WITH transaction as (
select 'Оплата за задержку' as description
union all
select 'Платежка за жку, за октябрь 2025' as description
union all
select 'жку за октябрь 2025' as description
union all
select 'жкх' as description
union all
select 'оплата, жкх.' as description
union all
select 'ЖКУ' as description
union all
select 'жкх: оплата задежки'
union all
select 'Надо проверить платежку'
union all
select 'Оплата за (жку)'
)
--
SELECT *
FROM transaction
WHERE INITCAP(description) LIKE '%Жку%'
OR INITCAP(description) LIKE '%Жкх%';6. Полезные операторы¶
В этом уроке продолжаем делать SQL-запросы сложнее. Вы узнаете, как подставить значение по умолчанию, избежать деления на ноль, или применить условную логику прямо в запросе. Эти конструкции особенно полезны, когда работаете с реальными, неидеальными данными.
Вы изучите:
Подстановку значений по умолчанию:
COALESCE,NULLIFСклеивание строк:
CONCAT,||Ветвление логики через
CASE WHENРабота с
NULLи комбинирование выражений
%%sql
-- Посмотрим на табл. users
SELECT
id,
username,
first_name,
last_name
FROM users;CONCAT( )¶
-- Объединяет переменное количество аргументов в одну строку
-- Null значения игнорируются.
CONCAT(expression [, ...])
-- expression - любое выражение для объединения.Функция concat() и оператор конкатенации || объединяет любое число строк в одну. Но есть различия (см. комментарий в запросе).
Задача: Чтобы обратиться к юзеру, обратимся по имени и фамилии. Если данных нет, будем писать Дорогой клиент.
%%sql
SELECT
id,
username,
first_name,
last_name,
first_name || ' ' || last_name AS concat1, -- если есть NULL, то результат NULL
CONCAT(first_name, ' ', last_name) concat2 -- если есть NULL, то результат строка
FROM users;CONCAT_WS( )¶
Когда разделитель один и тот же, удобнее CONCAT_WS (With Separator):
первый аргумент – разделитель, дальше – склеиваемые части.
%%sql
SELECT
id,
first_name,
last_name,
first_name || ' ' || last_name AS concat1, -- если есть NULL, то результат NULL
CONCAT(first_name, ' ', last_name) concat2, -- если есть NULL, то результат строка
CONCAT_WS(' ', first_name, last_name) concat3 -- первым аргументом разделитель
FROM users;COALESCE( )¶
Загружаем какое-то количество аргументов и COALESCE смотрит значение столбца в каждой строчке на значение аргумента NULL или не NULL.
-- Возвращает первое не-null значение из списка
-- vali - Значения для проверки
COALESCE(val1, val2, ..., valn)Если val1 не NULL - выводим, если val1 NULL - идем дальше, проверяем val2. COALESCE выводит первое значение, которое не NULL.
%%sql
SELECT '' != NULL;%%sql
SELECT '' IS NOT NULL;%%sql
SELECT
id,
first_name,
last_name,
first_name || ' ' || last_name AS concat1,
-- если есть NULL, то результат NULL
CONCAT(first_name, ' ', last_name) concat2,
-- если есть NULL, то результат строка
CONCAT_WS(' ', first_name, last_name) concat3,
-- первым аргументом разделитель
COALESCE(first_name || ' ' || last_name, 'Дорогой клиент') concat4
-- работает, т.к. `||` выдает явный NULL
FROM users;%%sql
SELECT
first_name,
last_name,
first_name || ' ' || last_name AS concat1,
CONCAT(first_name, ' ', last_name) concat2,
CONCAT_WS(' ', first_name, last_name) concat3,
COALESCE(first_name || ' ' || last_name, 'Дорогой клиент') concat4,
-- работает, т.к. `||` выдает явный NULL
COALESCE(CONCAT(first_name, ' ', last_name), 'Дорогой клиент') concat5
-- не корректно, т.к. `CONCAT` не выдает NULL (пустая строка IS NOT NULL)
FROM users;%%sql
-- Посмотрим на вывод: как и ожидалось вместо NULL строка
SELECT CONCAT(first_name, ' ', last_name)
FROM users;Используем строку CONCAT(first_name, ' ', last_name) чтобы реализовать конструкцию таким образом: если у нас получилась пустая строка, преобразуем ее в NULL, а потом этот NULL обработает COALESCE и выведет ‘Дорогой клиент’.
Используем вместо CONCAT -> CONCAT_WS, т.к. если у нас много аргументов (много строк нужно объединить), то удобнее написать разделитель только один раз.
NULLIF( )¶
NULLIF(value_1, value_2)Сравнивает результат работы первого аргумента и второго аргумента.
Если они совпадают – выводит NULL. Если не совпадают – выводит первый аргумент.
%%sql
-- Все значения где была пустая строка "обнулились"
SELECT NULLIF(CONCAT_WS(' ', first_name, last_name), '')
FROM users;%%sql
-- Сравним вывод
SELECT
CONCAT_WS(' ', first_name, last_name),
NULLIF(CONCAT_WS(' ', first_name, last_name), '')
FROM users;Научились превращать пустую строку в NULL, а значит теперь можем пойти обратно в наш запрос и добавить в COALESCE строку NULLIF
%%sql
SELECT
last_name,
first_name || ' ' || last_name AS concat1,
CONCAT(first_name, ' ', last_name) concat2,
CONCAT_WS(' ', first_name, last_name) concat3,
COALESCE(first_name || ' ' || last_name, 'Дорогой клиент') concat4,
COALESCE(CONCAT(first_name, ' ', last_name), 'Дорогой клиент') concat5,
COALESCE(NULLIF(CONCAT_WS(' ', first_name, last_name), ''), 'Дорогой клиент') concat6
-- работает, т.к. NULLIF превращает пустую строку в NULL
FROM users;CASE( )¶
%%sql
SELECT id, username, first_name, last_name, score,
CASE
WHEN score < 100 THEN 'low'
WHEN score < 500 THEN 'medium'
ELSE 'high'
END AS score2
FROM users
ORDER BY score2; -- чтобы увидеть разницу в усеченном выводе PandasКак только выполняется одно из условий, оператор останавливается.
Внутри можно наслаивать как угодно, например
WHEN score < 100 AND is_active = 1 THEN 'low'Применим CASE() к нашей задаче
%%sql
-- Вначале проверим что если имя NULL, то и фамилия NULL
-- Есть строки где задано только имя или только фамилия?
SELECT first_name, last_name
FROM users
WHERE first_name IS NULL AND last_name IS NOT NULL;
-- WHERE first_name IS NOT NULL AND last_name IS NULLПолучили, что если имя NULL, то и фамилия NULL -- т.е. не требуется использовать оба параметра. Возмьмем только имя.
%%sql
SELECT
first_name || ' ' || last_name AS concat1,
-- если есть NULL, то результат NULL
CONCAT(first_name, ' ', last_name) concat2,
-- если есть NULL, то результат строка
CONCAT_WS(' ', first_name, last_name) concat3,
-- указываем первым аргументом разделитель
COALESCE(first_name || ' ' || last_name, 'Дорогой клиент') concat4,
-- работает, т.к. `||` выдает явный NULL
COALESCE(CONCAT(first_name, ' ', last_name), 'Дорогой клиент') concat5,
-- не корректно, т.к. `CONCAT` не выдает NULL (пустая строка IS NOT NULL)
COALESCE(NULLIF(CONCAT_WS(' ', first_name, last_name), ''), 'Дорогой клиент') concat6,
-- работает, т.к. NULLIF превращает пустую строку в NULL
CASE
WHEN first_name IS NULL THEN 'Дорогой клиент'
ELSE CONCAT_WS(' ', first_name, last_name)
END AS concat7
FROM users;Практика #4¶
Задание 1
Из таблицы users требуется вывести следующую информацию о пользователях:
Идентификатор
Логин
Текстовое описание идентификатора формата “Идентификатор пользователя равен x”, где x - значение
id
Столбцы в результате
idusernametext_user_id
Важно: Обратите внимание, что название столбцов в вашем ответе должно в точности совпадать с условием.
Сортировка:
Результат отсортируйте по возрастанию поля id.
%%sql
SELECT
id,
username,
concat('Идентификатор пользователя равен ', id)
FROM users
ORDER BY id;Задание 2
Из таблицы users требуется вывести следующую информацию о пользователях:
Идентификатор
Логин
Имя
Фамилия
Приветственное имя пользователя:
если есть имя, то вывести имя пользователя,
если имени нет, но есть фамилия, то вывести фамилию,
если отсутствуют как имя, так и фамилия, то вывести Дорогой друг
Столбцы в результате
idusernamefirst_namelast_namedisplay_name
Важно: Обратите внимание, что название столбцов в вашем ответе должно в точности совпадать с условием.
Сортировка:
Результат отсортируйте по возрастанию поля id.
%%sql
SELECT
id,
username,
first_name,
last_name,
COALESCE(first_name, last_name, 'Дорогой друг') AS display_name
FROM users
ORDER BY id;Задание 3
Из таблицы users требуется вывести следующую информацию о пользователях:
Идентификатор
Логин
Количество очков опыта на платформе
Группа пользователя по количеству очков опыта на платформе
Правила присвоения группы пользователю по количеству очков опыта на платформе:
Если значение строго больше 300, то Мастер
Если значение строго больше 150, то Эксперт
Если значение строго больше 75, то Продвинутый
В остальных случаях Новичок
Столбцы в результате:
idusernamescoregroup_user
Важно: Обратите внимание, что название столбцов в вашем ответе должно в точности совпадать с условием.
Сортировка:
Результат отсортируйте по возрастанию поля id.
%%sql
SELECT
id,
username,
score,
CASE
WHEN score > 300 THEN 'Мастер'
WHEN score > 150 THEN 'Эксперт'
WHEN score > 75 THEN 'Продвинутый'
ELSE 'Новичок'
END group_user
FROM users
ORDER BY id;7. Скалярные функции (дата и время)¶
Работа с датами - одна из самых частых задач в SQL. В этом уроке вы научитесь извлекать нужные части даты, округлять и форматировать её, вычислять разницу между датами и даже прибавлять интервалы.
Вы изучите:
Извлечение и округление дат:
EXTRACT,DATE_TRUNC,TO_CHARАрифметика с датами:
+,-,INTERVALТекущая дата и время:
NOW()Создание даты вручную:
MAKE_DATE
%%sql
SELECT *
FROM users;Возникают разные задачи:
Хотим посмотреть регистрации пользователей помесячно, т.е. нам нужно оставить только год и месяц, отрезать время и дату.
Или хотим посмотреть регистрацию пользователей с разбивкой по дням недели.
Или посмотреть сколько дней прошло с момента регистрации до совершения первой активности пользователем, т.е. соединяем две таблички регистрация, активность и вычитаем одно из другого, т.е. вычитание дат.
Хотим посмотреть сколько времени прошло до покупки.
Хотим посмотреть сколько времени прошло между текущим моментом и регистрацией.
Хотим посмотреть сколько действий совершил пользователь в течение 3-7-30 дней после регистрации и все это в одном запросе.
Для того, чтобы их решать нужно знать всего лишь парочку функций и просто в нужный момент их применять.
В Postgres достаточно удобно работать с дата-временем
Потому что, например, в MySQL чтобы вычесть из даты дату нужно использовать специальную функцию, а в Postges достаточно просто поставить знак минус.
%%sql
SELECT
username,
date_joined
FROM users;Первое что нужно научиться делать - это вытаскивать какие-то конкретные части даты или времени. Чаще всего пригождается вытащить год, месяц, день - когда мы просто хотим обрезать время.
TO_CHAR( )¶
Переводит дату/время или число в строку по нужному формату.
-- PostgreSQL
TO_CHAR(timestamp, format)
--
SELECT TO_CHAR(NOW(), 'YYYY-MM-DD')timestamp - Значение для преобразования format - Строка формата
%%sql
SELECT
username,
date_joined,
TO_CHAR(date_joined, 'YYYY-MM') AS ym,
TO_CHAR(date_joined, 'YYYY-MM-DD') AS ymd
FROM users;Не самый эффективный способ работы с датавременем, потому что преобразование типа из одного в другой сжирает какое-то количество ресурсов, особенно если мы фильтрацию по этому какую-то делаем. Но способ настолько простой, что позволяет делать такие конструкции:
%%sql
SELECT
username,
date_joined,
TO_CHAR(date_joined, 'YYYY-MM') AS ym,
TO_CHAR(date_joined, 'YYYY-MM-DD') AS ymd
FROM users
--
WHERE TO_CHAR(date_joined, 'YYYY-MM') = '2021-11';Если у нас огромное количество данных или другие ограничения из-за которых идет борьба за ресурсы и нам нужно ускорять выполнение нашего кода, то приводить датавремя в строку и сравнивать со строкой может быть не самым оптимальным решением.
Но честно скажу, за все время работы прям с реальным ограничением такой конструкции столкнулся 1 раз за 10 лет. Поэтому рекомендую такую конструкцию, т.к. она реально удобна.
Альтернативный вариант:
DATE_TRUNC( )¶
Обрезает дату/время до указанной точности.
-- PostgreSQL
DATE_TRUNC(field, source)
--
SELECT DATE_TRUNC('month', TIMESTAMP '2023-02-15')field - определяет до какой точности обрезать входное значение (year, month, day и т.д.) source - значение даты/времени, выражение типа timestamp, timestamp with time zone или interval. (Значения типов date и time автоматически приводятся к типам timestamp и interval, соответственно.)
%reload_ext sql%%sql
SELECT
username,
date_joined,
TO_CHAR(date_joined, 'YYYY-MM') AS ym,
TO_CHAR(date_joined, 'YYYY-MM-DD') AS ymd,
DATE_TRUNC('month', date_joined) AS ym2,
-- отрежет все время и дату приведет на 1-й день месяца
DATE_TRUNC('day', date_joined) AS ymd2,
DATE_TRUNC('year', date_joined) AS "year"
FROM users;Для вычитания дат мы можем собрать дату:
MAKE_DATE( )¶
Создает дату из года, месяца и дня.
MAKE_DATE(year, month, day)
--
SELECT MAKE_DATE(2025, 6, 5)%%sql
SELECT
username,
date_joined,
MAKE_DATE(2037, 5, 5)
FROM users;%%sql
SELECT
username,
date_joined,
MAKE_DATE(2037, 5, 5) - date_joined AS diff_exact
-- Разница времени между собранной датой и ..
FROM users;%%sql
SELECT
username,
date_joined,
MAKE_DATE(2037, 5, 5) - date_joined AS diff_exact,
NOW() - date_joined AS diff_now
-- Разница между текущей датой и определенной
FROM users;Если нас не интересует сколько прошло часов, минут. Если нас интересует сколько прошло, например, дней:
EXTRACT( )¶
Извлекает подполя из значения даты/времени
EXTRACT(field FROM source)
--
SELECT EXTRACT(
YEAR
FROM TIMESTAMP '2023-01-01'
)field - Поле для извлечения (year, month, day и т.д.) из значения источника FROM source - Значение даты/времени; источником должно быть заданное выражением значение типа timestamp, date, time или interval
ВОЗВРАЩАЕТ ЧИСЛО!
EXTRACT(year FROM ...)вернёт просто2021(число, а не строку).EXTRACT(month FROM ...)вернёт номер месяца:1,2...12.EXTRACT(day FROM ...)вернёт день месяца:1,15,31и т.д.
%%sql
SELECT
date_joined,
MAKE_DATE(2037, 5, 5) - date_joined AS diff_exact,
NOW() - date_joined AS diff_now,
EXTRACT(days FROM MAKE_DATE(2037, 5, 5) - date_joined) AS diff_days
-- Вытащи кол-во дней из разницы
FROM users;Узнать сколько прошло всего часов:
%%sql
SELECT
date_joined,
MAKE_DATE(2037, 5, 5) - date_joined AS diff_exact,
NOW() - date_joined AS diff_now,
EXTRACT(days FROM MAKE_DATE(2037, 5, 5) - date_joined) AS diff_days,
--
EXTRACT(days FROM MAKE_DATE(2037, 5, 5) - date_joined) * 24
+ EXTRACT(hours FROM MAKE_DATE(2037, 5, 5) - date_joined)
AS diff_hours
FROM users;INTERVAL( )¶
%%sql
SELECT
date_joined,
date_joined + INTERVAL '3 days' AS future_day,
date_joined + INTERVAL '3 weeks' AS future_week,
date_joined + INTERVAL '3 years' AS future_year
FROM users;Пример конструкции: отфильтровать всех пользователей которые купили подписку в течение 1 суток после регистрации:
WHERE purchase_date BETWEEN date_joined
AND date_joined + INTERVAL '24 hours'%reload_ext sqlПрактка #5¶
Задание 1
Из таблицы users требуется вывести следующую информацию о пользователях:
Идентификатор
Логин
Почта
Дата и время регистрации
Дата и время регистрации, округленная до первого числа месяца (например,
2022-04-21 12:20:00.000202—>2022-04-01 00:00:00)
Столбцы в результате
idusernameemaildate_joinedregistration_month_start
Сортировка
Результат отсортируйте сначала по убыванию поля date_joined, а затем по возрастанию поля id.
%%sql
-- Практика 5. Задача 1
SELECT
id,
username,
email,
date_joined,
date_trunc('month', date_joined) AS registration_month_start
FROM users
ORDER BY date_joined DESC, id;Задание 2
Из таблицы users требуется вывести следующую информацию о пользователях:
Идентификатор
Логин
Почта
Дата и время регистрации
Отформатированная в виде текста дата регистрации в формате
DD Mon YYYY
Столбцы в результате
idusernameemaildate_joinedformatted_date
Сортировка
Результат отсортируйте сначала по убыванию поля date_joined, а затем по возрастанию поля id.
%%sql
-- Практика 5. Задача 2
SELECT
id,
username,
email,
date_joined,
TO_CHAR(date_joined, 'DD Mon YYYY') AS formatted_date
FROM users
ORDER BY date_joined DESC, id;Задание 3
Из таблицы users требуется вывести следующую информацию о пользователях:
Идентификатор
Логин
Почта
Дата и время регистрации
Отформатированная в виде текста дата регистрации в формате год-месяц (
YYYY-MM)
толбцы в результате
idusernameemaildate_joinedformatted_date
Сортировка
Результат отсортируйте сначала по убыванию поля date_joined, а затем по возрастанию поля id.
%%sql
-- Практика 5. Задача 3
SELECT
id,
username,
email,
date_joined,
TO_CHAR(date_joined, 'YYYY-MM') AS formatted_date
FROM users
ORDER BY date_joined DESC, id;Задание 4
Из таблицы users требуется вывести информацию о пользователях, которые зарегистрировались на платформе в течение 45 дней после 2022-01-01.
Столбцы в результате
idusernamedate_joined
Сортировка
Результат отсортируйте сначала по убыванию поля date_joined, а затем по возрастанию поля id.
%%sql
-- Практика 5. Задача 4
-- ВАРИАНТ 1: `MAKE_DATE`
SELECT
id,
username,
date_joined
FROM users
WHERE date_joined BETWEEN MAKE_DATE(2022, 1, 1)
AND MAKE_DATE(2022, 1, 1) + INTERVAL '45 days'
ORDER BY date_joined DESC, id;DATE¶
Типизированный литерал стандарта ANSI SQL (Date Literal):
DATE 'YYYY-MM-DD'
-- date + integer → date
-- date + interval → timestamp
Функция извлечения части даты из даты/времени:
DATE(datetime)
--
SELECT DATE('2022-12-05 10:37:22')Явное лучше неявного
Алан Болье формулирует фундаментальный принцип: Явное лучше неявного (Explicit is better than implicit).
Когда пишем просто '2022-01-01', для SQL это обычная текстовая строка (литерал неизвестного типа unknown или varchar). Если будем полагаться на то, что СУБД сама догадается, что это дата, ты мы отдаём управление механизму неявного приведения типов (implicit casting). В простых случаях СУБД угадывает, а в сложных (как со знаком + INTERVAL) спотыкается.
Что означает слово DATE перед строкой?
Конструкция DATE '2022-01-01' – это типизированный литерал стандарта ANSI SQL (Date Literal).
Запись вида:
DATE 'YYYY-MM-DD'говорит парсеру базы данных: Не думай, не угадывай и не воспринимай это как простой текст. Это константа строго типа данных DATE!
В стандарте SQL точно так же можно явно задавать типы для других констант:
TIME '12:30:00'– литерал времениTIMESTAMP '2022-01-01 12:30:00'– дата со временемINTERVAL '45 days'– интервал. Механика здесь ровно та же самая, что и уDATE '2022-01-01'.
В PostgreSQL есть три способа сделать явное приведение:
Стандарт ANSI SQL (работает во всех БД: Oracle, MySQL, Postgres):
DATE '2022-01-01'Специальный синтаксис PostgreSQL (два двоеточия):
'2022-01-01'::dateЧерез функцию конструирования:
MAKE_DATE(2022, 1, 1)
Все три варианта делают одно и то же: исключают неявное угадывание и дают 100% гарантию, что база работает именно с датой.
DATE '2022-01-01' или DATE('2022-01-01')
DATE '2022-01-01' или DATE('2022-01-01')Правильны оба варианта, но это принципиально разные механизмы.
Вариант без скобок: DATE '2022-01-01'
Что это такое: Это литерал (константа) стандарта ANSI SQL.
Как работает: На этапе разбора текста запроса парсер базы данных видит слово
DATEперед строковой константой и сразу компилирует её в значение типаdate.Ограничение: Сюда нельзя передать переменную или имя столбца. Нельзя написать
DATE date_joined– будет синтаксическая ошибка. Этот синтаксис предназначен только для фиксированных значений (констант).Где применяется: Когда пишем фиксированную дату руками в коде запроса.
Вариант со скобками: DATE(выражение)
Что это такое: Это вызов функции (или функциональное приведение типа). Передаём внутрь скобок любое выражение (строку, колонку, дату со временем) – и на выходе получаем
date.Пример: Если есть колонка
date_joinedсо временем2022-01-15 14:30:00, тоDATE(date_joined)отсечёт время и вернёт только дату2022-01-15.DATE('2022-01-01')тоже сработает: функция примет строку и превратит её в дату.
Итог:
Если задаём фиксированную дату руками: лучше писать
DATE '2022-01-01'(мировой стандарт) илиMAKE_DATE(2022, 1, 1).Если нужно отрезать время от колонки/переменной: используют
DATE(date_joined)(илиdate_joined::date).
%%sql
-- Практика 5. Задача 4
-- ВАРИАНТ 2: `DATE`
SELECT
id,
username,
date_joined
FROM users
WHERE date_joined BETWEEN DATE '2022-01-01'
AND DATE '2022-01-01' + INTERVAL '45 days'
ORDER BY date_joined DESC, id;В реальной работе
Для простой фильтрации строк CASE в блоке WHERE стараются не использовать без необходимости, потому что:
Обычный
date_joined BETWEEN ...пишется короче и читается легче.Простой
BETWEENпозволяет СУБД использовать индексы по полюdate_joined(запрос работает в тысячи раз быстрее на больших таблицах).
Но как тренировка понимания того, как работает CASE и как строятся предикаты – хорошее упражнение:
%%sql
-- Практика 5. Задача 4
-- ВАРИАНТ 3: `CASE WHEN`
SELECT
id,
username,
date_joined
FROM users
WHERE CASE
WHEN date_joined < MAKE_DATE(2022, 1, 1) THEN 'no'
WHEN date_joined > MAKE_DATE(2022, 1, 1) + INTERVAL '45 days' THEN 'no'
ELSE 'yes'
END = 'yes'
ORDER BY date_joined DESC, id;Задание 5
Из таблицы users требуется вывести информацию о пользователях, которые зарегистрировались на платформе в 2021 году.
Столбцы в результате
idusernamedate_joined
Сортировка
Результат отсортируйте по возрастанию поля id.
%%sql
-- Практика 5. Задача 5
SELECT
id,
username,
date_joined
FROM users
WHERE TO_CHAR(date_joined, 'YYYY') = '2021'
ORDER BY id;ВАРИАНТЫ: Практика 5. Задача 5
WHERE EXTRACT(year FROM date_joined) = 2021
-- возвращает число 2021, сравниваем с числом 2021WHERE DATE_TRUNC('year', date_joined) = TIMESTAMP '2021-01-01'
-- явное лучше неявного
-- или = '2021-01-01'
-- или = DATE '2021-01-01'
-- или = MAKE_DATE(2021, 1, 1)Что возвращает каждое выражение:
| Выражение | Что возвращает | Тип данных | Пример значения |
| DATE_TRUNC(‘year’, date_joined) | Дата со временем, усечённая до начала года | timestamp (дата и время) | 2021-01-01 00:00:00 |
| TIMESTAMP ‘2021-01-01’ | Константа даты и времени (со временем по умолчанию 00:00:00) | timestamp (дата и время) | 2021-01-01 00:00:00 |
| ‘2021-01-01’ | Обычная строка текста | unknown / text (строка) | ‘2021-01-01’ |
| DATE ‘2021-01-01’ | Календарная дата без времени | date (чистая дата) | 2021-01-01 |
| MAKE_DATE(2021, 1, 1) | Календарная дата без времени | date (чистая дата) | 2021-01-01 |
Эталон следования правилу явное лучше неявного DATE_TRUNC('year', date_joined) = TIMESTAMP '2021-01-01'
Слева:
timestamp.Справа:
timestamp.Базе данных вообще не нужно тратить ресурсы на неявное приведение типов (type casting). Она видит два абсолютно идентичных типа данных и сравнивает их напрямую бит в бит.
Почему они все могут сравниваться с DATE_TRUNC? DATE_TRUNC('year', date_joined) = DATE '2021-01-01'
PostgreSQL берёт date справа и приводит его к timestamp (2021-01-01 00:00:00). Значения слева и справа совпадают до микросекунды, поэтому равенство срабатывает идеально.
WHERE date_joined >= DATE '2021-01-01'
AND date_joined < DATE '2022-01-01'
/* явное указание типа DATE – признак хорошего тона, хотя
PostgreSQL легко догадается сам, т.к. слева `timestamp` */WHERE date_joined BETWEEN '2021-01-01 00:00:00'
AND '2021-12-31 23:59:59.999999'
/* чтобы не ловить микросекунды и не писать кучу девяток,
разработчики баз данных почти никогда не используют
`BETWEEN` для дат со временем */Задание 6
Из таблицы users требуется вывести информацию о пользователях, которые имеют домен электронной почты bk.ru и зарегистрировались на платформе в 2022 году.
Столбцы в результате
idusernameemaildate_joined
Сортировка
Результат отсортируйте по возрастанию поля id.
%%sql
-- Практика 5. Задача 6
SELECT
id,
username,
email,
date_joined
FROM users
-- 1-е место: идиоматичный ILIKE + быстрый числовой EXTRACT
WHERE email ILIKE '%@bk.ru'
AND EXTRACT(year FROM date_joined) = 2022
/* Альтернативные варианты условий:
-- 2-е место: через DATE_TRUNC и строгий литерал TIMESTAMP
email ILIKE '%@bk.ru'
AND DATE_TRUNC('year', date_joined) = TIMESTAMP '2022-01-01'
-- 3-е место: через LOWER и строковое форматирование TO_CHAR
LOWER(email) LIKE '%@bk.ru'
AND TO_CHAR(date_joined, 'YYYY') = '2022'
*/
ORDER BY id;Задание 7
Из таблицы users требуется вывести информацию о пользователях, которые зарегистрировались на платформе в 2021 году либо имеют количество очков опыта на платформе строго больше 100.
Столбцы в результате
idusernameemaildate_joinedscore
Сортировка
Результат отсортируйте по возрастанию поля id.
%%sql
-- Практика 5. Задача 7
SELECT
id,
username,
email,
date_joined,
score
FROM users
WHERE EXTRACT(year FROM date_joined) = 2021
OR score > 100
ORDER BY id;Задание 8
Из таблицы users требуется вывести информацию о пользователях, которые имеют домен bk.ru либо yandex.ru и при этом зарегистрировались на платформе в 2021 году.
Столбцы в результате
idusernameemaildate_joined
Сортировка
Результат отсортируйте по возрастанию поля id.
%%sql
-- Практика 5. Задача 8
SELECT
id,
username,
email,
date_joined
FROM users
WHERE (email iLIKE '%@bk.ru'
OR email iLIKE '%@yandex.ru')
AND EXTRACT(year FROM date_joined) = 2021
/* AND DATE_TRUNC('year', date_joined) = TIMESTAMP '2021-01-01' */
ORDER BY id;8. Дополнительные приемы¶
Этот короткий, но полезный урок посвящён типичной ошибке - целочисленному делению. Вы поймёте, как она влияет на результаты и как её избежать. Это не только спасёт ваши запросы от неверных результатов, но и прокачает внимание к деталям.
Допустим мы хотим разделить score / 10.
%%sql
SELECT
score,
score / 10
FROM users
WHERE score::text NOT LIKE '%0';
-- score::text превращает число в строку для LIKE (т.к. функции TEXT() нет)%%sql
SELECT
score,
score / 10 AS integer_division,
-- целочисленное деление (45 / 10 = 4)
score / 10.0 AS divide_by_float,
-- эталон: деление на литерал с точкой (4.5)
score * 1.0 / 10 AS multiply_first
-- повышение через умножение на 1.0
FROM users
WHERE score::text NOT LIKE '%0';Cast оператор ::¶
Оператор :: (двойное двоеточие)
:: (двойное двоеточие)Фирменный оператор PostgreSQL для явного приведения одного типа данных к другому.
Является более короткой альтернативой конструкции CAST(выражение AS тип).
Примеры использования:
score::numeric– превращает целое число в число с плавающей точкой (для точного деления без потери остатка:45::numeric / 10 = 4.5).'2026-08-25'::date– превращает текстовую строку в дату (типdate), позволяя безопасно складывать её сINTERVALили числами дней.date_joined::date– отсекает время от временной метки (timestamp), оставляя только календарный день.score::text– превращает число в текстовую строку (чтобы использовать строковые операторы вродеLIKEили конкатенацию).'120'::integer(или::int) – превращает строку с цифрами в целое число для математических вычислений.'true'::boolean(или'1'::bool) – превращает текст или цифру в логический тип (TRUE/FALSE).
%%sql
SELECT
score,
score / 10 AS integer_division,
score / 10.0 AS divide_by_float,
score * 1.0 / 10 AS multiply_first,
score::numeric / 10 AS explicit_cast,
-- явное приведение типа (numeric)
CAST(score AS numeric) / 10 AS ansi_cast_divide
-- универсальный стандарт ANSI SQL для приведения типов
FROM users
WHERE score::text NOT LIKE '%0';CAST ( )¶
Функция / конструкция CAST()
CAST()Используется для явного приведения значения из одного типа данных в другой.
CAST(выражение AS целевой_тип_данных)Основные типы для приведения:
AS
DATE– превращает строку или временную метку в чистую дату без времени:CAST('2026-08-25' AS DATE)➔2026-08-25AS
TIMESTAMP– превращает строку в дату со временем:CAST('2026-08-25 14:30:00' AS TIMESTAMP)➔2026-08-25 14:30:00AS
TIME– превращает строку или временную метку во время суток (отсекает дату):CAST('2026-08-25 14:30:00' AS TIME)➔14:30:00AS
NUMERIC(или ASDECIMAL) – превращает целое число или строку в точное вещественное число:CAST(score AS NUMERIC) / 10➔4.5AS
INTEGER(или ASINT) – превращает вещественное число (округляя его) или текст в целое число:CAST('42' AS INTEGER)➔42AS
TEXT(или ASVARCHAR) – превращает любое значение (дату, число) в строку текста.
Помнить разницу:
TIMESTAMP= дата + время (2026-08-25 14:30:00)DATE= только календарь (2026-08-25)TIME= только часы на стене (14:30:00)INTERVAL= отрезок длительности (3 days 4 hours)
%%sql
SELECT
score,
score / 10 AS integer_division,
CAST(score / 10.0 AS numeric(10, 2)) AS direct_cast_round
-- СНАЧАЛА деление, а приведение к numeric(10, 2) ПОТОМ
FROM users
WHERE score::text NOT LIKE '%0';Но самый понятный, универсальный и читаемый – это вариант с ROUND:
ROUND(CAST(score AS numeric) / 10, 2)ROUND( )¶
Функция ROUND()
ROUND()Используется для математического округления числовых значений (по правилам арифметики: от 5 – в большую сторону).
Синтаксис:
ROUND(число) -- округление до ближайшего целого
ROUND(число::numeric, s) -- округление до `s` знаков после запятойПримеры:
ROUND(4.2)➔4ROUND(4.8)➔5ROUND(12.3456::numeric, 2)➔ 12.35ROUND(score / 10.0, 2)➔ округляет результат деления до сотых.
⚠️ Важно для PostgreSQL: Если передаётся второй аргумент (количество знаков после запятой), первый аргумент обязан иметь тип numeric (или быть явно приведён к нему через ::numeric или литерал с точкой, например 10.0).
%%sql
SELECT
score,
score / 10 AS integer_division,
ROUND(score / 10.0, 2) AS divide_by_float,
ROUND(score * 1.0 / 10, 2) AS multiply_first,
ROUND(score::numeric / 10, 2) AS explicit_cast,
ROUND(CAST(score AS numeric) / 10, 2) AS ansi_cast_divide
FROM users
WHERE score::text NOT LIKE '%0';DISTINCT¶
Ключевое слово DISTINCT
DISTINCTОператор для устранения дубликатов. Оставляет только уникальные строки в результирующей выборке.
Указывается сразу после ключевого слова SELECT.
Синтаксис:
SELECT DISTINCT колонка1, колонка2 FROM таблица;Как это работает:
По одной колонке: возвращает список уникальных значений этого поля.
-- Вернёт список уникальных доменов или стран без повторов:
SELECT DISTINCT tier FROM users;По нескольким колонкам: уникальность определяется по комбинации всех указанных колонок (дубликатом считается строка, где совпадают все поля сразу).
-- Вернёт уникальные пары `имя + фамилия`:
SELECT DISTINCT first_name, last_name FROM users;💡 Заметка: DISTINCT требует от базы данных сортировки или хеширования всех строк для поиска повторов, поэтому на огромных таблицах он может работать медленнее обычного SELECT.
%%sql
SELECT
DISTINCT RIGHT(email, LENGTH(email) - POSITION('@' IN email)) AS unic
FROM users
ORDER BY unic;9. Соединение таблиц - JOIN¶
В этом уроке вы научитесь одному из ключевых навыков в SQL - объединению данные из нескольких таблиц. Вы разберётесь в различных типах соединений, научитесь задавать условия и учитывать такие нюансы, как пропущенные строки или дубликаты.
Вы изучите:
Виды соединений:
INNER JOIN,LEFT JOIN,RIGHT JOIN,FULL JOINУсловия соединения
ONс несколькими полямиОбработка пропусков и дубликатов
%%sql
SELECT *
FROM coderun cINNER JOIN (JOIN) - берем все строки из первой таблицы, все строки из второй. Оставляем только те строки, по которым нашли соответствие.
LEFT JOIN - из 1-й (левой) таблицы выводим все строки вне зависимости встречаются они в правой таблице или нет. Если встречаются, выводим все значения которые нашли в правой таблице, если не встречаются выводим NULL
FULL JOIN - комбинация левого и правого соединения.
INNER JOIN¶
%%sql
SELECT *
FROM coderun c
JOIN users u
ON c.user_id = u.id;%%sql
SELECT c.*, u.username
FROM coderun c
JOIN users u
ON c.user_id = u.id;RIGHT JOIN¶
%%sql
SELECT *
FROM coderun c
RIGHT JOIN users u
ON c.user_id = u.id;%%sql
SELECT *
FROM coderun c
RIGHT JOIN users u
ON c.user_id = u.id
WHERE c.id IS NULL;LEFT JOIN¶
%%sql
SELECT *
FROM users u
LEFT JOIN coderun c
ON u.id = c.user_id
WHERE c.id IS NULL;%%sql
SELECT *
FROM coderun c
JOIN users u
ON c.user_id = u.id
JOIN problem p
ON p.id = c.problem_id;%%sql
-- Очень аккуратно с JOIN'ами
SELECT *
FROM coderun c
RIGHT JOIN users u
ON c.user_id = u.id
JOIN problem p
ON p.id = c.problem_id
WHERE c.problem_id IS NULL;Неправильная последовательность JOIN’ов и Неправильный выбор типа соединения приводит к огромному количеству ошибок в сложных запросах.
%%sql
-- Правильно
SELECT *
FROM coderun c
RIGHT JOIN users u
ON c.user_id = u.id
LEFT JOIN problem p
ON p.id = c.problem_id
WHERE c.problem_id IS NULL;%%sql
SELECT u.company_id
FROM users u;%%sql
SELECT *
FROM company c;%%sql
SELECT *
FROM coderun c
RIGHT JOIN users u
ON c.user_id = u.id
JOIN company c2
ON COALESCE(u.company_id, 1) = c2.id;%%sql
SELECT COUNT(*)
FROM coderun c
RIGHT JOIN users u
ON c.user_id = u.id
JOIN problem p
ON p.id = c.problem_id;%%sql
SELECT COUNT(*)
FROM coderun c
RIGHT JOIN users u
ON c.user_id = u.id
LEFT JOIN problem p
ON p.id = c.problem_id;%%sql
SELECT count(*)
FROM coderun c
RIGHT JOIN users u
ON c.user_id = u.id
JOIN company c2
ON COALESCE(u.company_id, 1) = c2.id;%%sql
SELECT count(*)
FROM coderun c
RIGHT JOIN users u
ON c.user_id = u.id
LEFT JOIN company c2
ON COALESCE(u.company_id, 1) = c2.id;%%sql
SELECT *
FROM coderun c
JOIN users u
ON c.user_id = u.id AND created_at <= date_joined;%%sql
-- Эквивалентно предыдущему
SELECT *
FROM coderun c
JOIN users u
ON c.user_id = u.id
WHERE created_at <= date_joined;%%sql
-- Заменим на RIGTH JOIN
SELECT *
FROM coderun c
RIGHT JOIN users u
ON c.user_id = u.id AND created_at <= date_joined;%%sql
-- Пример использования OR
SELECT *
FROM coderun c
JOIN users u
ON (c.user_id = u.id AND created_at < date_joined)
OR (c.user_id = u.id AND created_at = date_joined);%%sql
SELECT *
FROM problem_to_company ptc;%%sql
SELECT *
FROM users u
JOIN company c
ON u.company_id = c.id
WHERE username = 'nsosabisfsobm';%%sql
SELECT *
FROM problem p
JOIN problem_to_company ptc
ON p.id = ptc.problem_id AND ptc.company_id = 1
WHERE p.id = 144;