Привет, Хабр)
Публикую шпаргалку по SQL, которая долгое время помогала мне, да и сейчас я периодически в неё заглядываю.
Все примеры изначально писались для СУБД SQLite, но почти всё из этого применимо также и к другим СУБД.
Вначале идут очень простые запросы, с них можно начать новичкам. Если хочется чего-то более интересного — листайте вниз. Здесь есть и примеры довольно сложных запросов с агрегирующими функциями, триггерами, длинными подзапросами, с оконными функциями. Помимо этого, часть примеров посвящена работе с SQL в Python при помощи библиотечек engine2
, engine1
, engine0
. Этот список запросов с комментариями можно использовать как наглядное пособие для изучения SQL.
Большинство советов я публиковал в своем канале по анализу данных, где вы найдете большое количество советов, инструментов и примеров с кодом. А здесь большая полезная папка, которую я собрал в которой куча полезного для работы с данными.
Кстати, все эти примеры SQL заботливо собраны в одной папке, можете скачать её и экспериментировать локально. После скачивания и разархивирования, у вас будет 3 группы файлов:
-
assays.db9
— базы данных SQLite, которые используются в примерах ниже -
assays.db8
— SQL-запросы, сценарии Python -
assays.db7
— ожидаемый результат для примеров
Поехали!
Выбираем все значения из таблички
Дополнительные команды SQL
Выбираем нужные столбцы
Сортировка
Ограничение выводимых записей
Ещё некоторые параметры вывода
Удаляем дубликаты
Фильтруем результаты
Более сложные условия фильтрации
Некоторые математические действия
Переименовываем столбцы
Подсчёт с пропущенными значениями
Вывод с условием при помощи WHERE
Условие с отрицанием
Выбираем NULL значения
Агрегирование в SQL
Распространённые агрегирующие функции в SQL
Подсчёт значений при помощи COUNT
Группировка
Как себя ведут неагрегированные столбцы
Выбор нужных столбцов для агрегирования
Фильтрация агрегированных значений
Читабельный вывод
Фильтрация входных данных
Создание табличек
Вставляем данные
Обновляем строки
Удаляем строки
Резервное копирование
Объединение табличек при помощи JOIN
INNER JOIN
Агрегирование объединённых через JOIN записей
LEFT JOIN
Агрегирование данных, собранных через LEFT JOIN
Объединение значений
SELECT DISTINCT и условие WHERE
Использование набора в условии WHERE при помощи IN
Подзапросы
Автоикремент и PRIMARY KEY
Изменение таблички при помощи ALTER
Создание новой таблички на базе старой
Удаление таблички
Сравнение отдельных значений с агрегированными
Сравнение отдельных значений с агрегированными внутри групп
CTE — табличные выражения
Смотрим план запроса с помощью EXPLAIN
Нумеруем строки
Условия if-else
Выбираем с помощью SELECT и CASE
Работаем с диапазоном значений
Ищем по фрагменту с помощью LIKE
Выбираем первую и последнюю строки
Пересечение отдельных табличек
Исключение
Случайные значения в SQL
Создание индексов
Генерация последовательности значений
Генерируем последовательность на основе данных
Генерация последовательностей дат
Подсчитываем количество значений за день, без пропусков
JOIN таблички с собой же
Генерируем уникальные пары значений
Фильтрация пар
EXISTS
NOT EXISTS в SQL
Опережение и отставание
Оконные функции
Используем PARTITION BY в SQL
Данные типа blob
Сохранение JSON
Выбираем отдельные поля в JSON
Доступ к JSON-объекту
Распаковка JSON
Последний элемент в массиве
Модифицируем JSON
Immediate If в SQL
Представление VIEW в SQL
Добавляем проверку CHECK
TRANSACTION в SQL
ROLLBACK в SQL
Откат с помощью ROLLBACK
Вставка значений
Создание триггера
Рекурсивный запрос
Продолжаем работать с bi_contact
Обновляем идентификаторы групп
Рекурсивно устанавливаем метки
Работа с SQL в Python при помощи sqlite3
Инкрементная выборка
Простые операции CREATE, INSERT, DELETE и другие с помощью sqlite3
Интерполируем значения
Выполнение полноценных SQL-запросов в Python
Исключения SQLite в Python
Python и SQLite, ещё некоторые возможности
Работа с датой и временем
SQL в Jupyter Notebooks
Pandas и SQL
Polars и SQL
ORM
Продолжаем работать с ORM
The end
Выбираем все значения из таблички
assays.db6
assays.db5
-
ничего особенного, выбираем все записи из таблички
assays.db4
Дополнительные команды SQL
assays.db3
assays.db2
-
включаем заголовки и режим markdown; в
assays.db1
подобные команды начинаются сassays.db0
, а в PostgreSQL сSession9
-
кстати, для просмотра дополнительной инфы или чтобы узнать, какие команды есть, используйте
Session8
Выбираем нужные столбцы
Session7
Session6
-
выбираем колонки
Session5
,Session4
,Session3
из табличкиSession2
Сортировка
Session1
Session0
-
выбираем столбцы
engine9
,engine8
,engine7
из табличкиengine6
-
сортируем все значения из
engine5
в возрастающем порядке (от A к Z) -
строки с одинаковыми значениями
engine4
дополнительно сортируем по их значениямengine3
в обратном порядке, от большего к меньшему (от Z к A)
Ограничение выводимых записей
engine2
engine1
-
выбираем столбцы
engine0
,select(Department)9
,select(Department)8
из табличкиselect(Department)7
-
сортируем по
select(Department)6
в порядке возрастания, строки с одинаковым значениемselect(Department)5
сортируются поselect(Department)4
, с одинаковымselect(Department)3
дополнительно сортируются поselect(Department)2
-
ну и выводим только первые 10 строк
Ещё некоторые параметры вывода
select(Department)1
select(Department)0
-
Department9
указывается послеDepartment8
и позволяет пропустить сколько-то первых строк, в данном случае пропущены 3 первых строки
Удаляем дубликаты
Department7
Department6
-
Department5
— выбираем уникальные комбинации из столбцовDepartment4
,Department3
,Department2
Фильтруем результаты
Department1
Department0
-
выбираем уникальные комбинации значений
session.exec(statement).all()9
,session.exec(statement).all()8
,session.exec(statement).all()7
изsession.exec(statement).all()6
, где значения поляsession.exec(statement).all()5
равноsession.exec(statement).all()4
Более сложные условия фильтрации
session.exec(statement).all()3
session.exec(statement).all()2
-
выбираем уникальные комбинации значений
session.exec(statement).all()1
,session.exec(statement).all()0
,class Staff(SQLModel, table=True):
ident: str = Field(default=None, primary_key=True)
personal: str
family: str
dept: Optional[str] = Field(default=None, foreign_key="department.ident")
age: intengine = create_engine("sqlite:///db/assays.db")
SQLModel.metadata.create_all(engine)
with Session(engine) as session:
statement = select(Department, Staff).where(Staff.dept == Department.ident)
for dept, staff in session.exec(statement):
print(f"{dept.name}: {staff.personal} {staff.family}")
9 изclass Staff(SQLModel, table=True):
ident: str = Field(default=None, primary_key=True)
personal: str
family: str
dept: Optional[str] = Field(default=None, foreign_key="department.ident")
age: intengine = create_engine("sqlite:///db/assays.db")
SQLModel.metadata.create_all(engine)
with Session(engine) as session:
statement = select(Department, Staff).where(Staff.dept == Department.ident)
for dept, staff in session.exec(statement):
print(f"{dept.name}: {staff.personal} {staff.family}")
8, где значения поляclass Staff(SQLModel, table=True):
ident: str = Field(default=None, primary_key=True)
personal: str
family: str
dept: Optional[str] = Field(default=None, foreign_key="department.ident")
age: intengine = create_engine("sqlite:///db/assays.db")
SQLModel.metadata.create_all(engine)
with Session(engine) as session:
statement = select(Department, Staff).where(Staff.dept == Department.ident)
for dept, staff in session.exec(statement):
print(f"{dept.name}: {staff.personal} {staff.family}")
7 равноclass Staff(SQLModel, table=True):
ident: str = Field(default=None, primary_key=True)
personal: str
family: str
dept: Optional[str] = Field(default=None, foreign_key="department.ident")
age: intengine = create_engine("sqlite:///db/assays.db")
SQLModel.metadata.create_all(engine)
with Session(engine) as session:
statement = select(Department, Staff).where(Staff.dept == Department.ident)
for dept, staff in session.exec(statement):
print(f"{dept.name}: {staff.personal} {staff.family}")
6, а значения поляclass Staff(SQLModel, table=True):
ident: str = Field(default=None, primary_key=True)
personal: str
family: str
dept: Optional[str] = Field(default=None, foreign_key="department.ident")
age: intengine = create_engine("sqlite:///db/assays.db")
SQLModel.metadata.create_all(engine)
with Session(engine) as session:
statement = select(Department, Staff).where(Staff.dept == Department.ident)
for dept, staff in session.exec(statement):
print(f"{dept.name}: {staff.personal} {staff.family}")
5 не равноclass Staff(SQLModel, table=True):
ident: str = Field(default=None, primary_key=True)
personal: str
family: str
dept: Optional[str] = Field(default=None, foreign_key="department.ident")
age: intengine = create_engine("sqlite:///db/assays.db")
SQLModel.metadata.create_all(engine)
with Session(engine) as session:
statement = select(Department, Staff).where(Staff.dept == Department.ident)
for dept, staff in session.exec(statement):
print(f"{dept.name}: {staff.personal} {staff.family}")
4
Некоторые математические действия
class Staff(SQLModel, table=True):
ident: str = Field(default=None, primary_key=True)
personal: str
family: str
dept: Optional[str] = Field(default=None, foreign_key="department.ident")
age: int
engine = create_engine("sqlite:///db/assays.db")
SQLModel.metadata.create_all(engine)
with Session(engine) as session:
statement = select(Department, Staff).where(Staff.dept == Department.ident)
for dept, staff in session.exec(statement):
print(f"{dept.name}: {staff.personal} {staff.family}")
3
class Staff(SQLModel, table=True):
ident: str = Field(default=None, primary_key=True)
personal: str
family: str
dept: Optional[str] = Field(default=None, foreign_key="department.ident")
age: int
engine = create_engine("sqlite:///db/assays.db")
SQLModel.metadata.create_all(engine)
with Session(engine) as session:
statement = select(Department, Staff).where(Staff.dept == Department.ident)
for dept, staff in session.exec(statement):
print(f"{dept.name}: {staff.personal} {staff.family}")
2
-
выводим 3 первых строки значений
class Staff(SQLModel, table=True):
ident: str = Field(default=None, primary_key=True)
personal: str
family: str
dept: Optional[str] = Field(default=None, foreign_key="department.ident")
age: intengine = create_engine("sqlite:///db/assays.db")
SQLModel.metadata.create_all(engine)
with Session(engine) as session:
statement = select(Department, Staff).where(Staff.dept == Department.ident)
for dept, staff in session.exec(statement):
print(f"{dept.name}: {staff.personal} {staff.family}")
1, делённых на 10.0, и значенийclass Staff(SQLModel, table=True):
ident: str = Field(default=None, primary_key=True)
personal: str
family: str
dept: Optional[str] = Field(default=None, foreign_key="department.ident")
age: intengine = create_engine("sqlite:///db/assays.db")
SQLModel.metadata.create_all(engine)
with Session(engine) as session:
statement = select(Department, Staff).where(Staff.dept == Department.ident)
for dept, staff in session.exec(statement):
print(f"{dept.name}: {staff.personal} {staff.family}")
0, делённых на 1000.0
Переименовываем столбцы
Histology: Divit Dhaliwal
Molecular Biology: Indrans Sridhar
Molecular Biology: Pranay Khanna
Histology: Vedika Rout
Genetics: Abram Chokshi
Histology: Romil Kapoor
Molecular Biology: Ishaan Ramaswamy
Genetics: Nitya Lal
9
Histology: Divit Dhaliwal
Molecular Biology: Indrans Sridhar
Molecular Biology: Pranay Khanna
Histology: Vedika Rout
Genetics: Abram Chokshi
Histology: Romil Kapoor
Molecular Biology: Ishaan Ramaswamy
Genetics: Nitya Lal
8
-
делим значения
Histology: Divit Dhaliwal
на 10.0, делим значения
Molecular Biology: Indrans Sridhar
Molecular Biology: Pranay Khanna
Histology: Vedika Rout
Genetics: Abram Chokshi
Histology: Romil Kapoor
Molecular Biology: Ishaan Ramaswamy
Genetics: Nitya Lal
7Histology: Divit Dhaliwal
на 1000.0
Molecular Biology: Indrans Sridhar
Molecular Biology: Pranay Khanna
Histology: Vedika Rout
Genetics: Abram Chokshi
Histology: Romil Kapoor
Molecular Biology: Ishaan Ramaswamy
Genetics: Nitya Lal
6 -
переименовываем столбцы
Histology: Divit Dhaliwal
— в
Molecular Biology: Indrans Sridhar
Molecular Biology: Pranay Khanna
Histology: Vedika Rout
Genetics: Abram Chokshi
Histology: Romil Kapoor
Molecular Biology: Ishaan Ramaswamy
Genetics: Nitya Lal
5Histology: Divit Dhaliwal
,
Molecular Biology: Indrans Sridhar
Molecular Biology: Pranay Khanna
Histology: Vedika Rout
Genetics: Abram Chokshi
Histology: Romil Kapoor
Molecular Biology: Ishaan Ramaswamy
Genetics: Nitya Lal
4Histology: Divit Dhaliwal
— в
Molecular Biology: Indrans Sridhar
Molecular Biology: Pranay Khanna
Histology: Vedika Rout
Genetics: Abram Chokshi
Histology: Romil Kapoor
Molecular Biology: Ishaan Ramaswamy
Genetics: Nitya Lal
3Histology: Divit Dhaliwal
,
Molecular Biology: Indrans Sridhar
Molecular Biology: Pranay Khanna
Histology: Vedika Rout
Genetics: Abram Chokshi
Histology: Romil Kapoor
Molecular Biology: Ishaan Ramaswamy
Genetics: Nitya Lal
2Histology: Divit Dhaliwal
— в
Molecular Biology: Indrans Sridhar
Molecular Biology: Pranay Khanna
Histology: Vedika Rout
Genetics: Abram Chokshi
Histology: Romil Kapoor
Molecular Biology: Ishaan Ramaswamy
Genetics: Nitya Lal
1Histology: Divit Dhaliwal
Molecular Biology: Indrans Sridhar
Molecular Biology: Pranay Khanna
Histology: Vedika Rout
Genetics: Abram Chokshi
Histology: Romil Kapoor
Molecular Biology: Ishaan Ramaswamy
Genetics: Nitya Lal
0 -
выводим первые 3 строки
Взаимосвязь рассмотренных понятий SQL можно показать так:
Подсчёт с пропущенными значениями
Staff9
Staff8
-
делим значения из
Staff7
на 10, затем присваиваем результаты новому столбцуStaff6
-
делим значения из столбца
Staff5
на 1000 и затем присваивание результатов новому столбцуStaff4
-
переименовываем
Staff3
вStaff2
Вывод с условием при помощи WHERE
Staff1
Staff0
-
выбираем столбцы
table=True9
,table=True8
,table=True7
-
выводим все записи из
table=True6
, где значениеtable=True5
равноtable=True4
table=True3
table=True2
-
выводим все записи из
table=True1
, где значениеtable=True0
равноStaff9
и значениеStaff8
равноStaff7
Условие с отрицанием
-
условие с оператором отрицания
Staff6
тоже без проблем работает
Staff5
Staff4
Выбираем NULL значения
Staff3
Staff2
-
выбираем строки со значениями
Staff1
,Staff0
,ident9
из табличкиident8
, где значенияident7
нет (ident6
)
Вот так можно показать связь понятий SQL, которые мы рассмотрели выше:
Агрегирование в SQL
ident5
ident4
-
суммируем все значения колонки
ident3
, сохраняем в новый столбецident2
Распространённые агрегирующие функции в SQL
ident1
ident0
-
находим максимальное значение из столбца
None9
, записываем это значение какNone8
-
аналогично находим минимальное из
None7
, находим среднее изNone6
, среднее изNone5
Подсчёт значений при помощи COUNT
None4
None3
-
None2
— считаем все значения изNone1
-
None0
— считаем все значения из столбцаprimary_key=True9
-
primary_key=True8
— считаем уникальные значения изprimary_key=True7
(очевидно их 2:primary_key=True6
,primary_key=True5
) -
записываем эти 3 числа как
primary_key=True4
,primary_key=True3
,primary_key=True2
соответственно
Группировка
primary_key=True1
primary_key=True0
-
из таблички
personal9
находим среднее всех значенийpersonal8
, сохраняем какpersonal7
-
группируем по значениям
personal6
(группыpersonal5
,personal4
,personal3
)
Как себя ведут неагрегированные столбцы
personal2
personal1
-
для того, чтобы было видно названия отдельных групп, выбираем не только среднее
personal0
, но иfamily9
-
видим 3 группы:
family8
,family7
,family6
Выбор нужных столбцов для агрегирования
src/arbitrary_in_aggregation.sql
family5
out/arbitrary_in_aggregation.out
family4
-
здесь у нас популярная ошибка, мы просто выбираем
family3
, а не находим среднее, поэтому SQL выбирает любые значения изfamily2
. Аккуратнее)
Фильтрация агрегированных значений
family1
family0
-
здесь мы используем
dept9
вместоdept8
(эффект тот же самый), оставляем только те значения изdept7
, которые больше 4000
Читабельный вывод
dept6
dept5
-
округляем среднее
dept4
до 1 знака после запятой, используяdept3
Фильтрация входных данных
src/filter_aggregate_inputs.sql
dept2
out/filter_aggregate_inputs.out
dept1
-
при помощи
dept0
мы находим среднее только тех значенийNone9
, которые меньше 4000 -
округляем до 1 знака после запятой, сохраняем в столбец
None8
-
группируем по
None7
Вот так выглядит связь основных понятий, которые мы только что обсуждали:
Кстати, вот так выглядит создание БД в оперативной памяти:
None6
-
запускаем интерактивную оболочку SQLite, создаём новую базу данных в оперативной памяти для более быстрой работы
Создание табличек
None5
-
создаём таблицу
None4
со столбцами:None3
— столбец текстовых значений, не может быть пустым (None2
),None1
— содержит вещественные числа, не может быть пустым -
создаём табличку
None0
со столбцами:foreign_key="department.ident"9
— текстовый, не может быть пустым,foreign_key="department.ident"8
— текстовый, не может быть пустым
Вставляем данные
foreign_key="department.ident"7
foreign_key="department.ident"6
-
ничего особенного, заполняем табличку
foreign_key="department.ident"5
парамиforeign_key="department.ident"4
-foreign_key="department.ident"3
, и так же заполняем табличкуforeign_key="department.ident"2
парамиforeign_key="department.ident"1
-foreign_key="department.ident"0
Обновляем строки
age9
age8
-
меняем все записи
age7
наage6
Удаляем строки
age5
age4
-
удаляем все строки, где значение
age3
равноage2
Резервное копирование
age1
age0
-
создаём табличку
Staff9
c текстовыми столбцамиStaff8
иStaff7
-
помещаем внутрь
Staff6
значения столбцовStaff5
иStaff4
из таблицыStaff3
, где значения столбцаStaff2
равноStaff1
-
удаляем из
Staff0
все записи со значениемcreate_engine9
равнымcreate_engine8
-
отображаем записи таблички
create_engine7
Вот так выглядит связь основных понятий, которые мы только что обсуждали:
Объединение табличек при помощи JOIN
create_engine6
create_engine5
-
делаем
create_engine4
для 2 таблицcreate_engine3
иcreate_engine2
— все возможные комбинации строк из этих таблиц (если вcreate_engine1
3 строки, а вcreate_engine0
4 строки, то результат будет иметь 4 ⋅ 3 = 12 строк)
INNER JOIN
create_all9
create_all8
-
объединяем 2 таблицы
create_all7
иcreate_all6
— берём те записи, где значениеcreate_all5
изcreate_all4
совпадает со значениемcreate_all3
изcreate_all2
Агрегирование объединённых через JOIN записей
create_all1
create_all0
-
объединяем те строки таблиц
Department9
иDepartment8
, где значениеDepartment7
в таблицеDepartment6
соответствует значениюDepartment5
вDepartment4
-
суммируем значения
Department3
из таблицыDepartment2
для каждого значенияDepartment1
из таблицыDepartment0
-
группируем результаты по значениям
Staff9
изStaff8
LEFT JOIN
Staff7
Staff6
-
склеиваем таблицы
Staff5
иStaff4
по соответствующим значениям столбцаStaff3
-
если в таблице
Staff2
есть строки, для которых нет совпадений в таблицеStaff1
, то они все равно будут включены в результат с пустыми (Staff0
) значениями -
использование
Staff.dept9
гарантирует, что все строки из левой таблицыStaff.dept8
будут включены в результат, независимо от наличия совпадающих строк в правой таблицеStaff.dept7
Агрегирование данных, собранных через LEFT JOIN
Staff.dept6
Staff.dept5
-
вычисляем сумму значений столбца
Staff.dept4
изStaff.dept3
, сохраняем какStaff.dept2
-
используем
Staff.dept1
, чтобы гарантированно включить все строки изStaff.dept0
вDepartment.ident9
-
группируем по столбцу
Department.ident8
изDepartment.ident7
Вот так выглядит связь основных понятий, которые мы только что обсуждали:
Объединение значений
Department.ident6
Department.ident5
-
Department.ident4
используется для заменыDepartment.ident3
на 0.0, если суммаDepartment.ident2
для данногоDepartment.ident1
равнаDepartment.ident0
-
more9
включает все записи изmore8
и только соответствующие записи изmore7
-
группируем по значениям столбца
more6
изmore5
SELECT DISTINCT и условие WHERE
more4
more3
-
выбираем уникальные значения из столбца
more2
, где полеmore1
не равноmore0
Использование набора в условии WHERE при помощи IN
6
5
-
выбираем все строки из
4
, где3
не равно2
и не равно1
Подзапросы
0
9
-
внутренний подзапрос выбирает уникальные значения столбца
8
из7
, где в поле6
стоит5
-
внешний, главный запрос выбирает те уникальные значения
4
, где3
не равно значениям из внутренного подзапроса
Автоикремент и PRIMARY KEY
2
1
-
создаём табличку
0
с 2 столбцами:9
с целочисленными значениями,8
с текстовыми значениями; столбец7
устанавливаем как6
, включаем автоматическое инкрементирование значений -
помещаем в таблицу
5
3 пары4
-3
-
при попытке добавить ещё одну пару
2
возникает ошибка, поскольку уже существует строка с1
равным 1
Внутренняя табличка:
0
9
-
выводим все текущие значения автоинкрементных счетчиков для таблиц в БД SQLite
Изменение таблички при помощи ALTER
8
7
-
добавляем новый столбец
6
в табличку5
; столбец заполняется целыми числами, не может быть пустым; ставим значение по умолчанию4
для этого столбца -
делаем значение столбца
3
равным 1 там, где2
равен1
-
устанавливаем значение
0
равным 2 для строки, где9
равен8
Создание новой таблички на базе старой
7
6
-
создаём таблицу
5
с 2 целочисленными столбцами:4
и3
; оба столбца не могут быть пустыми -
2
2
ограничения добавляются, чтобы связать столбцы1
и0
новой таблицы9
с соответствующими столбцами8
в таблицах7
и6
-
добавляем данные в таблицу
5
, используя результат запроса4
-
3
— данные будут выбраны из результатов соединения таблиц2
и1
по условию равенства значений столбца0
в таблице9
и столбца8
в таблице7
-
6
— результаты предыдущего соединения будут дополнительно соединены с таблицей5
по условию равенства значений столбца4
в таблице3
и столбца2
в1
Удаление таблички
0
-
удаляем
9
из БД -
изменяем имя таблички
8
на7
6
-
создаём таблицу
5
с 3 колонками:4
хранит целые числа, используется в качестве первичного ключа (3
) и автоматически увеличивается (autoincrement);2
текстовый столбец, не может быть пустым (1
);0
— столбец вещественных чисел, не может быть пустым -
создаём
9
с 2 колонками:8
и7
-
создаём таблицу
6
с 2 колонками:5
— хранит целые числа, используется в качестве первичного ключа и автоматически увеличивается (autoincrement),4
— хранит текст, не может быть пустым -
создаём
3
с 4 колонками:2
- хранит целые числа, не может быть пустым; аналогичный столбец1
-
устанавливаем внешние ключи, связывающие
0
с9
в таблице8
и7
с6
в таблице5
Сравнение отдельных значений с агрегированными
src/compare_individual_aggregate.sql
4
out/compare_individual_aggregate.out
3
-
выбираем только те строки, где значение в столбце
2
больше, чем среднее значение1
по всем строкам в таблице0
-
ну и выводим только первые 5 строк
Сравнение отдельных значений с агрегированными внутри групп
9
8
-
выбираем столбцы
7
и6
из таблицы5
-
вычисляем среднюю массу для каждого вида пингвина, округляем до 1 знака после запятой, используя подзапрос, который связывается с исходной таблицей
4
по полю3
-
используя результаты подзапроса, фильтруем только те записи, где масса пингвина больше средней массы для его вида
-
выводим только первые 5 записей
CTE — табличные выражения
src/common_table_expressions.sql
2
out/common_table_expressions.out
1
-
создаём табличку
0
(с помощью9
), которая содержит среднюю массу тела пингвинов (8
) для каждого вида из7
(6
) -
из
5
выбираем такие столбцы:4
,3
; и из из общей таблицы2
выбираем1
, округлённое до 1 знака -
объединяем
0
с общей таблицей9
(через8
); для каждого пингвина будет найдена соответствующая средняя масса тела для его вида -
7
— фильтруем; оставляем только тех, у которых масса тела больше средней массы их вида -
выводим только первые 5 строк
Смотрим план запроса с помощью EXPLAIN
6
5
-
4
— получаем план выполнения запроса, как будет выполнен запрос в базе данных -
выбираем столбец
3
, вычисляем среднее значение столбца body_mass_g для каждого вида из2
-
1
— группируем результаты по столбцу0
Нумеруем строки
-
каждая таблица имеет специальный столбец
9
с уникальными числовыми идентификаторами
8
7
Условия if-else
6
5
-
создаём временную таблицу
4
, которая содержит два столбца:3
и2
-
1
определяется на основе условия: если0
меньше 3500, то он считается9
, в противном случае -8
-
выбираем столбцы
7
и6
из временной таблицы5
, а подсчитываем количество записей для каждой комбинации4
и3
, используя функцию2
-
группируем данные (
1
) по0
и9
Выбираем с помощью SELECT и CASE
8
7
-
в блоке
6
создаём набор данных с именем5
, где находится4
и3
, определенные на2
-
1
разделяет пингвинов на 3 категории:0
,9
и8
в зависимости от их массы -
в основном блоке
7
выбираются вид пингвина, его размер и количество пингвинов каждого размера (6
) из набора5
-
результаты группируются по виду пингвина и их размеру с помощью
4
-
в конце запроса результаты сортируются сначала по
3
в алфавитном порядке, а затем по2
Работаем с диапазоном значений
1
0
-
создаём общую таблицу выражений (CTE)
9
, она выбирает вид пингвина и определяет его размер в зависимости от массы тела; если масса в диапазоне от 3500 до 5000 г, это размер8
, в противном случае -7
-
затем из этой CTE извлекаем данные с указанием видов пингвинов, их размеров и количества пингвинов каждого вида и размера, используя
6
с агрегирующей функцией5
-
группируем по виду и размеру пингвина с помощью
4
-
сортируем результат по виду и количеству пингвинов в порядке возрастания с помощью
3
Ещё одна БД:
ER-диаграмма показывает отношения между отдельными табличками и выглядит так:
2
1
Ищем по фрагменту с помощью LIKE
0
9
-
8
— хотим выбрать столбцы7
и6
из таблицы5
-
4
— ну понятно, запрос будет выполнен в таблице3
-
2
— хотим выбрать строки, в которых значение столбца1
содержит подстроку0
(с помощью9
) или значение столбца8
содержит7
(с помощью6
)
Выбираем первую и последнюю строки
5
4
-
выбираем 5 самых старых записей из таблицы
3
, отсортированных по возрастанию даты начала (2
) с помощью подзапроса (внутренний1
) -
выбираем 5 самых новых записей из
0
, отсортированных по убыванию даты начала (9
) с помощью другого подзапроса -
объединяем эти 2 подзапроса с помощью
8
, так мы получаем временную таблицу, содержащую 10 записей (5 самых старых и 5 самых новых) -
из временной таблицы выбираем все столбцы для каждой записи (
7
) и окончательно сортируем записи по возрастанию даты начала (6
) с помощью внешнего5
Пересечение отдельных табличек
4
3
-
здесь мы используем
2
для объединения результатов двух отдельных запросов -
вначале выбираем данные из таблицы
1
, в которых значение поля0
равно9
-
потом выбираем данные из таблицы
8
, в которых значение поля7
меньше 50 -
с помощью
6
мы находим пересечение, то есть строки, которые являются общими для результатов обоих запросов -
в результате будут выбраны строки, которые присутствуют в обоих результатах, то есть записи из
5
, где значение4
равно3
и значение2
меньше 50
Исключение
1
0
-
при помощи
9
извлекаем 4 поля из8
:7
,6
,5
и4
-
затем используем
3
, чтобы отфильтровать только те строки, в которых значение2
равно1
-
после этого при помощи
0
удаляем из исходного результата любые строки, которые также присутствуют в результате второго запроса -
второй запрос
9
также извлекает четыре поля из8
:7
,6
,5
и4
-
используем
3
, чтобы отфильтровать только те строки, где значение2
меньше 50
Случайные значения в SQL
1
0
-
создаём временную таблицу
9
-
в этой таблице извлекается случайное число с помощью
8
-
конкатенируем значения
7
и6
под именем5
с помощью4
для разделения -
таким образом создаём временную таблицу, содержащую столбцы
3
с случайными числами и2
со значениями из столбцов1
и0
таблицы9
-
делаем выборку из временной таблицы
8
; в выборку включаем столбцы7
,6
-
5
— это мы вычисляем остаток от деления абсолютного значения4
на 10 -
ну и в конце оставляем только строки, где
3
меньше 5
Создание индексов
2
1
-
выбираем все значения столбца
0
из таблицы9
, где значение столбца8
содержит подстроку7
-
создаём индекс с именем
6
для столбца5
в таблице4
-
запрашиваем план выполнения запроса (
3
)
Генерация последовательности значений
2
1
-
0
— генерируем ряд чисел от 1 до 5 -
SELECT value
— выбираем этот столбецvalue
со сгенерированными числами от 1 до 5
Генерируем последовательность на основе данных
CREATE TABLE temp (num integer NOT NULL);
INSERT INTO temp
VALUES (1),
(5);
SELECT value
FROM generate_series ((SELECT min(num)
FROM TEMP),
(SELECT max(num)
FROM TEMP));
| value |
|-------|
| 1 |
| 2 |
| 3 |
| 4 |
| 5 |
-
создаём временную таблицу
temp
, которая содержит 1 столбец с именемnum
типа integer; этот столбец не может быть пустым -
помещаем в
temp
значения 1 и 5 в столбецnum
-
используем
generate_series
для создания последовательности чисел между минимальным и максимальным значениями из столбцаnum
в таблицеtemp
Генерация последовательностей дат
SELECT date(
(SELECT julianday(min(started))
FROM experiment) + value) AS some_day
FROM
(SELECT value
FROM generate_series(
(SELECT 0),
(SELECT count(*) - 1
FROM experiment)))
LIMIT 5;
| some_day |
|------------|
| 2023-01-29 |
| 2023-01-30 |
| 2023-01-31 |
| 2023-02-01 |
| 2023-02-02 |
-
SELECT julianday(min(started)) FROM experiment
— находим минимальную дату в столбцеstarted
изexperiment
, преобразуем её вJulian
день (числовое представление даты) и возвращаем этотJulian
день -
внешним подзапросом вычисляем разницу между этим минимальным
Julian
днем и каждымvalue
изgenerate_series
-
затем складываем эти разницы с минимальным
Julian
днем, и конвертируем обратно в дату с помощьюdate()
-
ну и выбираем только первые 5 результатов этого вычисления с помощью
LIMIT 5
Подсчитываем количество значений за день, без пропусков
WITH -- complete sequence of days with 0 as placeholder for number of experiments
all_days AS (
SELECT DATE (
(
SELECT julianday (MIN(started))
FROM experiment
) + VALUE
) AS some_day,
0 AS zeroes
FROM (
SELECT VALUE
FROM generate_series (
(
SELECT 0
),
(
SELECT COUNT(*) - 1
FROM experiment
)
)
)
), -- sequence of actual days with actual number of experiments started
actual_days AS (
SELECT started,
COUNT(started) AS num_exp
FROM experiment
GROUP BY started
) -- combined by joining on day and taking actual number (if available) or zero
SELECT all_days.some_day AS DAY,
COALESCE(actual_days.num_exp, all_days.zeroes) AS num_exp
FROM all_days
LEFT JOIN actual_days ON all_days.some_day = actual_days.started
LIMIT 5;
| day | num_exp |
|------------|---------|
| 2023-01-29 | 1 |
| 2023-01-30 | 1 |
| 2023-01-31 | 0 |
| 2023-02-01 | 0 |
| 2023-02-02 | 1 |
-
создаём последовательность всех дней с нулевым значением в качестве заполнителя для количества экспериментов
-
создаём последовательность фактических дней с реальным числом экспериментов
-
объединяем эти последовательности, соединяя их по дням и беря реальное количество (если доступно) или ноль
-
выводит результат, показывая дни (
all_days.some_day
) и соответствующее количество экспериментов (COALESCE(actual_days.num_exp, all_days.zeroes) AS num_exp
), при этом используется функцияCOALESCE
, чтобы использовать фактическое количество экспериментов, если оно доступно, или ноль, если нет
JOIN таблички с собой же
WITH person AS (
SELECT ident,
personal || ' ' || family AS name
FROM staff
)
SELECT LEFT.name,
RIGHT.name
FROM person AS
LEFT JOIN person AS RIGHT
LIMIT 10;
| name | name |
|--------------|------------------|
| Kartik Gupta | Kartik Gupta |
| Kartik Gupta | Divit Dhaliwal |
| Kartik Gupta | Indrans Sridhar |
| Kartik Gupta | Pranay Khanna |
| Kartik Gupta | Riaan Dua |
| Kartik Gupta | Vedika Rout |
| Kartik Gupta | Abram Chokshi |
| Kartik Gupta | Romil Kapoor |
| Kartik Gupta | Ishaan Ramaswamy |
| Kartik Gupta | Nitya Lal |
-
создаём временную общую таблицу
person
с помощью WITH -
объединяем столбцы
personal
иfamily
в один столбецname
-
при помощи
SELECT
выбираем изperson
значения столбцаname
через алиасыleft
иright
-
после этого происходит объединение
person
с собой с помощью оператораLEFT JOIN
, при этом таблица алиасируется какRIGHT
Этот SQL-код, однако, содержит ошибку, правильный синтаксис должен быть следующим:
WITH person AS (
SELECT ident,
personal || ' ' || family AS name
FROM staff
)
SELECT LEFT.name,
RIGHT.name
FROM person AS LEFT
LEFT JOIN person AS RIGHT ON < условие соединения >
LIMIT 10;
В исходном примере условие соединения (ON
) не было указано
Генерируем уникальные пары значений
WITH person AS (
SELECT ident,
personal || ' ' || family AS name
FROM staff
)
SELECT LEFT.name,
RIGHT.name
FROM person AS
LEFT JOIN person AS RIGHT ON LEFT.ident < RIGHT.ident
WHERE LEFT.ident <= 4
AND RIGHT.ident <= 4;
| name | name |
|-----------------|-----------------|
| Kartik Gupta | Divit Dhaliwal |
| Kartik Gupta | Indrans Sridhar |
| Kartik Gupta | Pranay Khanna |
| Divit Dhaliwal | Indrans Sridhar |
| Divit Dhaliwal | Pranay Khanna |
| Indrans Sridhar | Pranay Khanna |
-
создаём временную таблицу
person
, которая содержит результат выбора из таблицыstaff
-
из
person
выбираем значенияleft.name
иright.name
с использованием операции слияния (JOIN
). В этом случае происходит слияниеperson
с собой, причем каждая копияperson
используется в качестве левой и правой таблиц соответственно. Слияние выполняется по условию, что идентификатор слева меньше идентификатора справа. -
затем применяем дополнительное условие с помощью
WHERE
, которое фильтрует результатыJOIN
-операции. Это условие проверяет, что идентификаторы слева и справа меньше или равны 4
Фильтрация пар
WITH person AS (
SELECT ident,
personal || ' ' || family AS name
FROM staff
),
together AS (
SELECT LEFT.staff AS left_staff,
RIGHT.staff AS right_staff
FROM performed AS
LEFT JOIN performed AS RIGHT ON LEFT.experiment = RIGHT.experiment
WHERE left_staff < right_staff
)
SELECT LEFT.name AS person_1,
RIGHT.name AS person_2
FROM person AS
LEFT JOIN person AS
RIGHT JOIN together ON LEFT.ident = left_staff
AND RIGHT.ident = right_staff;
| person_1 | person_2 |
|-----------------|------------------|
| Kartik Gupta | Vedika Rout |
| Pranay Khanna | Vedika Rout |
| Indrans Sridhar | Romil Kapoor |
| Abram Chokshi | Ishaan Ramaswamy |
| Pranay Khanna | Vedika Rout |
| Kartik Gupta | Abram Chokshi |
| Abram Chokshi | Romil Kapoor |
| Kartik Gupta | Divit Dhaliwal |
| Divit Dhaliwal | Abram Chokshi |
| Pranay Khanna | Ishaan Ramaswamy |
| Indrans Sridhar | Romil Kapoor |
| Kartik Gupta | Ishaan Ramaswamy |
| Kartik Gupta | Nitya Lal |
| Kartik Gupta | Abram Chokshi |
| Pranay Khanna | Romil Kapoor |
-
во временной табличке
person
выбираем имена сотрудников из таблицыstaff
; используемpersonal ' ' family AS name
, чтобы объединить значения из столбцовpersonal
иfamily
-
временная табличка
together
использует операторLEFT JOIN
для объединения таблицыperformed
с собой на основе столбцаexperiment
. Затем выбираются пары сотрудников, участвовавших в одном и том же эксперименте, исключая случаи, когда идентификатор левого сотрудника (left_staff
) больше идентификатора правого сотрудника (right_staff
) -
затем выполняется основной
SELECT
, который используетperson
иtogether
для объединения имен сотрудников на основе их идентификаторов. Он выполняет дваLEFT JOIN
, чтобы объединитьperson
с самим собой и затем объединить результат сtogether
на основе идентификаторов сотрудников. -
затем выбираются имена сотрудников для отображения в итоговом результате.
EXISTS
SELECT name,
building
FROM department
WHERE EXISTS
(SELECT 1
FROM staff
WHERE dept = department.ident )
ORDER BY name;
| name | building |
|-------------------|------------------|
| Genetics | Chesson |
| Histology | Fashet Extension |
| Molecular Biology | Chesson |
-
выбираем столбцы
name
иbuilding
из таблицыdepartment
-
WHERE EXISTS (SELECT 1 FROM staff WHERE dept = department.ident )
— используем подзапрос, который проверяет существование хотя бы одной записи в таблицеstaff
, для которой значение столбцаdept
совпадает с значением столбцаident
из таблицыdepartment
-
ORDER BY name
— устанавливаем порядок сортировки результатов по столбцуname
в алфавитном порядке
NOT EXISTS в SQL
SELECT name,
building
FROM department
WHERE NOT EXISTS
(SELECT 1
FROM staff
WHERE dept = department.ident )
ORDER BY name;
| name | building |
|---------------|----------|
| Endocrinology | TGVH |
-
выбираем столбцы
name
иbuilding
из таблицыdepartment
-
WHERE NOT EXISTS
— выбираем только те записи изdepartment
, для которых не существует записей в таблицеstaff
-
SELECT 1 FROM staff WHERE dept = department.ident
— проверяем, существуют ли записи в таблицеstaff
, связанные с отделом из таблицыdepartment
-
ORDER BY name
— сортируем результат по столбцу name
Избегание коррелированных подзапросов
src/avoid_correlated_subqueries.sql
SELECT DISTINCT department.name AS name,
department.building AS building
FROM department
JOIN staff ON department.ident = staff.dept
ORDER BY name;
out/avoid_correlated_subqueries.out
| name | building |
|-------------------|------------------|
| Genetics | Chesson |
| Histology | Fashet Extension |
| Molecular Biology | Chesson |
-
SELECT DISTINCT
— выбираем уникальные значенияname
иbuilding
из таблицыdepartment
-
JOIN staff ON department.ident = staff.dept
— объединяем таблицыdepartment
иstaff
на основе условия, что значение столбцаident
изdepartment
равно значениюdept
изstaff
-
ORDER BY name
— результаты выборки сортируем в алфавитном порядке по столбцуname
Опережение и отставание
WITH ym_num AS (
SELECT strftime ('%Y-%m', started) AS ym,
COUNT(*) AS num
FROM experiment
GROUP BY ym
)
SELECT ym,
lag (num) OVER (
ORDER BY ym
) AS prev_num,
num,
lead (num) OVER (
ORDER BY ym
) AS next_num
FROM ym_num
ORDER BY ym;
| ym | prev_num | num | next_num |
|---------|----------|-----|----------|
| 2023-01 | | 2 | 5 |
| 2023-02 | 2 | 5 | 5 |
| 2023-03 | 5 | 5 | 1 |
| 2023-04 | 5 | 1 | 6 |
| 2023-05 | 1 | 6 | 5 |
| 2023-06 | 6 | 5 | 3 |
| 2023-07 | 5 | 3 | 2 |
| 2023-08 | 3 | 2 | 4 |
| 2023-09 | 2 | 4 | 6 |
| 2023-10 | 4 | 6 | 4 |
| 2023-12 | 6 | 4 | 5 |
| 2024-01 | 4 | 5 | 2 |
| 2024-02 | 5 | 2 | |
-
создаём временную таблицу
ym_num
из 2 столбцов:ym
(год-месяц 'YYYY-MM') иnum
(количество записей в каждом месяце) -
используем SQLite
strftime
для извлечения года и месяца изstarted
, агрегируем результаты с помощьюGROUP BY
-
в основном запросе выбираем данные из
ym_num
, выполняем следующие операции и получаем год-месяцym
, количество записей в предыдущем месяце(lag)num
, текущее количество записейnum
и количество записей в следующем месяце(lead)num
-
результаты упорядочиваем по столбцу
ym
(год-месяц)
Оконные функции
WITH ym_num AS (
SELECT strftime ('%Y-%m', started) AS ym,
COUNT(*) AS num
FROM experiment
GROUP BY ym
)
SELECT ym,
num,
SUM(num) OVER (
ORDER BY ym
) AS num_done,
CUME_DIST() OVER (
ORDER BY ym
) AS progress
FROM ym_num
ORDER BY ym;
| ym | num | num_done | progress |
|---------|-----|----------|--------------------|
| 2023-01 | 2 | 2 | 0.0769230769230769 |
| 2023-02 | 5 | 7 | 0.153846153846154 |
| 2023-03 | 5 | 12 | 0.230769230769231 |
| 2023-04 | 1 | 13 | 0.307692307692308 |
| 2023-05 | 6 | 19 | 0.384615384615385 |
| 2023-06 | 5 | 24 | 0.461538461538462 |
| 2023-07 | 3 | 27 | 0.538461538461538 |
| 2023-08 | 2 | 29 | 0.615384615384615 |
| 2023-09 | 4 | 33 | 0.692307692307692 |
| 2023-10 | 6 | 39 | 0.769230769230769 |
| 2023-12 | 4 | 43 | 0.846153846153846 |
| 2024-01 | 5 | 48 | 0.923076923076923 |
| 2024-02 | 2 | 50 | 1.0 |
-
создаём временную таблицу
ym_num
, которая содержит:ym
— год и месяц, извлеченные изstarted
вexperiment
с помощьюstrftime('%Y-%m')
;num
— количество записей вexperiment
для каждого сочетания года и месяца -
выбираем
ym
иnum
из таблицыym_num
, добавляем 2 дополнительных столбца:num_done
— сумма количества экспериментов по всем предыдущим годам и месяцам (sum(num) OVER (ORDER BY ym)
);progress
— кумулятивное распределение количества экспериментов по всем предыдущим годам и месяцам (cume_dist() OVER (ORDER BY ym)
) -
упорядочиваем результаты по столбцу
ym
(год и месяц)
Внезапно небольшое задание: объясните, что делает запрос ниже
src/explain_window_function.sql
EXPLAIN query PLAN
WITH ym_num AS (
SELECT strftime ('%Y-%m', started) AS ym,
COUNT(*) AS num
FROM experiment
GROUP BY ym
)
SELECT ym,
num,
SUM(num) OVER (
ORDER BY ym
) AS num_done,
CUME_DIST() OVER (
ORDER BY ym
) AS progress
FROM ym_num
ORDER BY ym;
out/explain_window_function.out
QUERY PLAN
|--CO-ROUTINE (subquery-3)
| |--CO-ROUTINE (subquery-4)
| | |--CO-ROUTINE ym_num
| | | |--SCAN experiment
| | | `--USE TEMP B-TREE FOR GROUP BY
| | |--SCAN ym_num
| | `--USE TEMP B-TREE FOR ORDER BY
| `--SCAN (subquery-4)
`--SCAN (subquery-3)
-
создаём временную табличку
ym_num
с результатами агрегирования по месяцам, где данные изstarted
преобразуются в формат год-месяц (strftime('%Y-%m', started) AS ym
) и подсчитываем количество событий (count(*) AS num
) -
группируем результаты по полю
ym
-
выбираем поля
ym
иnum
изym_num
и добавляем 2 дополнительных поля:num_done
иprogress
;num_done
— общее количество событий/мес, сгруппированных в порядке увеличения месяца; полеprogress
— прогресс в процентном соотношении относительно общего числа записей (cume_dist()
) -
в итоге выводим данные в порядке увеличения значения
ym
(год-месяц)
Используем PARTITION BY в SQL
WITH y_m_num AS
(SELECT strftime('%Y', started) AS YEAR,
strftime('%m', started) AS MONTH,
count(*) AS num
FROM experiment
GROUP BY YEAR,
MONTH)
SELECT YEAR,
MONTH,
num,
sum(num) OVER (PARTITION BY YEAR
ORDER BY MONTH) AS num_done
FROM y_m_num
ORDER BY YEAR,
MONTH;
| year | month | num | num_done |
|------|-------|-----|----------|
| 2023 | 01 | 2 | 2 |
| 2023 | 02 | 5 | 7 |
| 2023 | 03 | 5 | 12 |
| 2023 | 04 | 1 | 13 |
| 2023 | 05 | 6 | 19 |
| 2023 | 06 | 5 | 24 |
| 2023 | 07 | 3 | 27 |
| 2023 | 08 | 2 | 29 |
| 2023 | 09 | 4 | 33 |
| 2023 | 10 | 6 | 39 |
| 2023 | 12 | 4 | 43 |
| 2024 | 01 | 5 | 5 |
| 2024 | 02 | 2 | 7 |
-
создаём временную таблицу
y_m_num
с тремя столбцами:YEAR,
MONTH
иnum
. -
временную табличку заполняем записями из
experiment
. Для каждой записи определяем год и месяц даты в столбцеstarted
(черезstrftime
), считаем количество записей (count(*)
) для каждого года и месяца, группируем результаты по году и месяцу -
выбираем данные из
y_m_num
, добавляем столбецnum_done
— накопительное значение дляnum
в пределах каждого годаsum(num) OVER (PARTITION BY YEAR ORDER BY MONTH)
— суммируем значениеnum
для каждого месяца при сортировке по месяцам внутри каждого года -
в итоге сортируем результаты по году и месяцу с помощью
ORDER BY YEAR, MONTH
Данные типа blob
CREATE TABLE images (name text NOT NULL,
content blob);
INSERT INTO images(name, content)
VALUES ("biohazard", readfile("img/biohazard.png")),
("crush", readfile("img/crush.png")),
("fire", readfile("img/fire.png")),
("radioactive", readfile("img/radioactive.png")),
("tripping", readfile("img/tripping.png"));
SELECT name,
length(content)
FROM images;
| name | length(content) |
|-------------|-----------------|
| biohazard | 19629 |
| crush | 15967 |
| fire | 18699 |
| radioactive | 16661 |
| tripping | 17208 |
-
создаём таблицу
images
со столбцами:name
— текстовый столбец, не может быть пустым;content
— столбец двоичных данных (blob
) -
вставляем 5 пар
name
-blob
вimages
с помощьюINSERT INTO
-
readfile
читает содержимое файлаname
и возвращает его как двоичные данныеblob
-
выполняем выборку данных из
images
с помощьюSELECT
, получая значенияname
и вычисляя количество байт двоичных данных вcontent
Ещё одна БД
sqlite3 data/lab_log.db
.schema
CREATE TABLE sqlite_sequence(name,
seq);
CREATE TABLE person(ident integer PRIMARY KEY autoincrement,
details text NOT NULL);
CREATE TABLE machine(ident integer PRIMARY KEY autoincrement,
name text NOT NULL,
details text NOT NULL);
CREATE TABLE usage(ident integer PRIMARY KEY autoincrement,
log text NOT NULL);
-
создаём
sqlite_sequence
со столбцамиname
иseq
(для значения счетчика, он используется в качествеAUTOINCREMENT
) -
создаём
person
со столбцамиident
(целочисленный, является первичным ключом (PRIMARY KEY
), автоматически инкрементируется) иdetails
(текстовый столбец, не может иметь значениеNULL
) -
создаём
machine
со столбцамиident
(целочисленный, является первичным ключом (PRIMARY KEY
), автоматически инкрементируется),name
(текстовый, не может иметь значениеNULL
),details
(текстовый, не может иметь значениеNULL
) -
создаём
usage
со столбцамиident
(целочисленный, является первичным ключом (PRIMARY KEY
), автоматически инкрементируется) иlog
(текстовый столбец, не может иметь значениеNULL
)
Сохранение JSON
SELECT *
FROM machine;
| ident | name | details |
|-------|----------------|---------------------------------------------------------|
| 1 | WY401 | {"acquired": "2023-05-01"} |
| 2 | Inphormex | {"acquired": "2021-07-15", "refurbished": "2023-10-22"} |
| 3 | AutoPlate 9000 | {"note": "needs software update"} |
Выбираем отдельные поля в JSON
SELECT details->'$.acquired' AS single_arrow,
details->>'$.acquired' AS double_arrow
FROM machine;
| single_arrow | double_arrow |
|--------------|--------------|
| "2023-05-01" | 2023-05-01 |
| "2021-07-15" | 2021-07-15 |
| | |
-
details->'$.acquired' AS single_arrow
— с помощью->
извлекаем значение JSON поляacquired
из столбцаdetails
для каждой строки изmachine
, обозначаем его какsingle_arrow
-
details->>'$.acquired' AS double_arrow
— оператор->>
также используется для извлечения JSON по указанному пути, но возвращает текст, в отличие от->
, который возвращает JSON значение; здесь мы извлекаем значение JSON поляacquired
из столбцаdetails
для каждой строки изmachine
, обозначаем его какdouble_arrow
Доступ к JSON-объекту
SELECT ident,
json_array_length(log->'$') AS LENGTH,
log->'$[0]' AS FIRST
FROM USAGE;
| ident | length | first |
|-------|--------|--------------------------------------------------------------|
| 1 | 4 | {"machine":"Inphormex","person":["Gabrielle","Dubu00e9"]} |
| 2 | 5 | {"machine":"Inphormex","person":["Marianne","Richer"]} |
| 3 | 2 | {"machine":"sterilizer","person":["Josette","Villeneuve"]} |
| 4 | 1 | {"machine":"sterilizer","person":["Maude","Goulet"]} |
| 5 | 2 | {"machine":"AutoPlate 9000","person":["Brigitte","Michaud"]} |
| 6 | 1 | {"machine":"sterilizer","person":["Marianne","Richer"]} |
| 7 | 3 | {"machine":"WY401","person":["Maude","Goulet"]} |
| 8 | 1 | {"machine":"AutoPlate 9000"} |
-
json_array_length(log->'$') AS LENGTH
— вычисляем длину массива, находящегося внутри JSON-объекта в столбцеlog
; используем оператор->
, чтобы получить массив из корневого уровня JSON-объекта, иjson_array_length
для подсчета количества элементов в этом массиве; результат помещаем в столбец с именемLENGTH
-
log->'$[0]' AS FIRST
— извлекаем первый элемент из массива, указанного в корневом уровне JSON-объекта в столбцеlog
; используем оператор->
, чтобы получить доступ к массиву, и указываем индекс элемента в квадратных скобках; результат сохраняем в столбецFIRST
Распаковка JSON
SELECT ident,
json_each.key AS KEY,
json_each.value AS value
FROM USAGE,
json_each(usage.log)
LIMIT 10;
| ident | key | value |
|-------|-----|--------------------------------------------------------------|
| 1 | 0 | {"machine":"Inphormex","person":["Gabrielle","Dubu00e9"]} |
| 1 | 1 | {"machine":"Inphormex","person":["Gabrielle","Dubu00e9"]} |
| 1 | 2 | {"machine":"WY401","person":["Gabrielle","Dubu00e9"]} |
| 1 | 3 | {"machine":"Inphormex","person":["Gabrielle","Dubu00e9"]} |
| 2 | 0 | {"machine":"Inphormex","person":["Marianne","Richer"]} |
| 2 | 1 | {"machine":"AutoPlate 9000","person":["Marianne","Richer"]} |
| 2 | 2 | {"machine":"sterilizer","person":["Marianne","Richer"]} |
| 2 | 3 | {"machine":"AutoPlate 9000","person":["Monique","Marcotte"]} |
| 2 | 4 | {"machine":"sterilizer","person":["Marianne","Richer"]} |
| 3 | 0 | {"machine":"sterilizer","person":["Josette","Villeneuve"]} |
-
SELECT ident, json_each.key AS KEY, json_each.value AS value
— определяем, что нужно выбрать из таблицыusage
и JSON-объектов, распарсенных с помощью функцииjson_each
; из каждой строки выбираем идентификатор, а также ключ и его значение из каждого JSON-объекта в столбцеlog
-
FROM usage, json_each(usage.log)
— указываем источник данных для выборки;usage
указывается после ключевого слова FROM, аjson_each
вызывается передlog
, чтобы разобрать JSON-объекты из этого столбца -
LIMIT 10
— выбираем только первые 10 строк
Последний элемент в массиве
SELECT ident,
log->'$[#-1].machine' AS FINAL
FROM USAGE
LIMIT 5;
| ident | final |
|-------|--------------|
| 1 | "Inphormex" |
| 2 | "sterilizer" |
| 3 | "Inphormex" |
| 4 | "sterilizer" |
| 5 | "sterilizer" |
-
SELECT ident, log->'$[#-1].machine' AS FINAL
— выбираем 2 столбца изmachine
;ident
возвращается как есть, а столбецlog
обрабатывается так:-
log->'$[#-1].machine
— извлекаем данные из столбцаlog
(->
используется для доступа к JSON-полю в столбцеlog
) -
$[#-1]
— обращаемся к последнему элементу массива, который хранится вlog
-
.machine
— хотим извлечь значение поляmachine
из объекта, находящегося в последнем элементе массива
-
Модифицируем JSON
SELECT ident,
name,
json_set(details, '$.sold', json_quote('2024-01-25')) AS updated
FROM machine;
| ident | name | updated |
|-------|----------------|--------------------------------------------------------------|
| 1 | WY401 | {"acquired":"2023-05-01","sold":"2024-01-25"} |
| 2 | Inphormex | {"acquired":"2021-07-15","refurbished":"2023-10-22","sold":" |
| | | 2024-01-25"} |
| 3 | AutoPlate 9000 | {"note":"needs software update","sold":"2024-01-25"} |
-
SELECT ident, name, ... FROM machine;
— выбираем значения столбцовident
иname
из таблицыmachine
-
json_set(details, '$.sold', json_quote('2024-01-25')) AS updated
— при помощиjson_set
обновляем JSON-объект в столбцеdetails
; функция добавляет/изменяет свойствоsold
в JSON-объекте в столбцеdetails
, присваивая ему новое значение, полученное с помощью функцииjson_quote
; результат сохраняем какupdated
Обновляем табличку penguins:
SELECT species,
count(*) AS num
FROM penguins
GROUP BY species;
| species | num |
|-----------|-----|
| Adelie | 152 |
| Chinstrap | 68 |
| Gentoo | 124 |
Immediate If в SQL
ALTER TABLE penguins ADD active integer NOT NULL DEFAULT 1;
UPDATE penguins
SET active = IIF(species = 'Adelie', 0, 1);
-
изменяем таблицу
penguins
, добавляя новый столбецactive
типаinteger
, который не может содержать значениеNULL
, и устанавливаем значение по умолчанию 1 для всех строк -
обновляем значения в столбце
active
вpenguins
; значение столбцаactive
устанавливается на 0, если значение вspecies
равно'Adelie'
, иначе устанавливается на 1 -
функция
IIF
(Immediate If) используется здесь для реализации условного выражения (1 аргумент - условие, 2 - результат, если условие истинно, и 3 - результат, если условие ложно)
SELECT species,
count(*) AS num
FROM penguins
WHERE active
GROUP BY species;
| species | num |
|-----------|-----|
| Chinstrap | 68 |
| Gentoo | 124 |
Представление VIEW в SQL
CREATE VIEW IF NOT EXISTS active_penguins (species, island, bill_length_mm, bill_depth_mm, flipper_length_mm, body_mass_g, sex) AS
SELECT species,
island,
bill_length_mm,
bill_depth_mm,
flipper_length_mm,
body_mass_g,
sex
FROM penguins
WHERE active;
SELECT species,
count(*) AS num
FROM active_penguins
GROUP BY species;
| species | num |
|-----------|-----|
| Chinstrap | 68 |
| Gentoo | 124 |
-
создаём представление (
VIEW
) с именемactive_penguins
, если его еще не существует -
представление содержит столбцы
species
,island
,bill_length_mm
,bill_depth_mm
,flipper_length_mm
,body_mass_g
, иsex
; данные для представления берутся изpenguins
, при условии, что пингвины являются активными (WHERE active
) -
выполняем выборку из представления
active_penguins
: выбираем вид пингвина (species
) и количество таких пингвинов (num
), удовлетворяющих условиям, заданным в представленииactive_penguins
Напоминание о часах работы:
CREATE TABLE job (name text NOT NULL,
billable real NOT NULL);
INSERT INTO job
VALUES ('calibrate', 1.5),
('clean', 0.5);
SELECT *
FROM job;
| name | billable |
|-----------|----------|
| calibrate | 1.5 |
| clean | 0.5 |
Добавляем проверку CHECK
CREATE TABLE job (name text NOT NULL,
billable real NOT NULL, CHECK (billable > 0.0));
INSERT INTO job
VALUES ('calibrate', 1.5);
INSERT INTO job
VALUES ('reset', -0.5);
SELECT *
FROM job;
Runtime error near line 9: CHECK constraint failed: billable > 0.0 (19)
| name | billable |
|-----------|----------|
| calibrate | 1.5 |
-
создаём таблицу
job
с 2 столбцами, которые не могут быть пустыми:name
(текстовый тип данных) иbillable
(вещественные тип данных) -
ограничение (
CHECK
) гарантирует, что значение столбцаbillable
должно быть больше чем 0.0 -
добавляем новую запись в
job
с указанными значениями'calibrate'
для столбцаname
и 1.5 для столбцаbillable
— сейчас под условиеCHECK
это попадает -
пытаемся добавить еще одну запись в таблицу
job
с указанными значениями'reset'
для столбцаname
и -0.5 для столбцаbillable
. Однако, так как -0.5 меньше либо равно 0.0, то это нарушает условиеCHECK
ACID
ACID — это акроним, который описывает набор свойств транзакций баз данных, предназначенных для обеспечения целостности данных в случае ошибок, сбоев питания и других непредвиденных ситуаций:
-
Атомарность (Atomicity): Транзакция должна быть атомарной, что означает, что она должна быть выполнена целиком или не выполнена вообще. Если одна часть транзакции не может быть выполнена, то все изменения, сделанные в рамках этой транзакции, должны быть отменены.
-
Согласованность (Consistency): Транзакция должна приводить базу данных из одного согласованного состояния в другое согласованное состояние. Это означает, что все правила и ограничения, установленные на данные, должны быть соблюдены во время выполнения транзакции.
-
Изолированность (Isolation): Транзакции должны быть изолированы друг от друга, чтобы предотвратить взаимное влияние. Каждая транзакция должна быть выполнена так, как если бы она была единственной выполняемой транзакцией в базе данных. Это гарантирует, что результаты одной транзакции не будут видны другим транзакциям до их завершения.
-
Долговечность (Durability): Результаты выполненной транзакции должны быть постоянными и доступными даже в случае сбоя системы или перезагрузки. Это достигается путем записи изменений в постоянное хранилище, например SSD.
TRANSACTION в SQL
CREATE TABLE job (name text NOT NULL,
billable real NOT NULL, CHECK (billable > 0.0));
INSERT INTO job
VALUES ('calibrate', 1.5);
BEGIN TRANSACTION;
INSERT INTO job
VALUES ('clean', 0.5);
ROLLBACK;
SELECT *
FROM job;
| name | billable |
|-----------|----------|
| calibrate | 1.5 |
-
создаём таблицу
job
с 2 колонками, которые не могут быть пустыми:-
name
текстового типа -
billable
с типом данныхreal
(вещественное число) и условиемCHECK (billable > 0.0)
, что гарантирует, что значениеbillable
больше 0.0
-
-
добавляем в
job
запись:('calibrate', 1.5)
-
начинаем новую транзакцию.
-
добавляем другую запись в таблицу
job
:('clean', 0.5)
-
откатываем последнюю транзакцию, добавляя
'clean', 0.5
, поэтому данная строка не сохраняется
ROLLBACK в SQL
CREATE TABLE job (
name text NOT NULL,
billable real NOT NULL,
CHECK (billable > 0.0) ON CONFLICT ROLLBACK
);
INSERT INTO job
VALUES ('calibrate', 1.5);
INSERT INTO job
VALUES ('clean', 0.5),
('reset', -0.5);
SELECT *
FROM job;
Runtime error near line 11: CHECK constraint failed: billable > 0.0 (19)
| name | billable |
|-----------|----------|
| calibrate | 1.5 |
-
создаём новую таблицу с именем
job
и 2 непустыми столбцами: текстовымname
и вещественнымbillable
-
значение в
billable
должно быть больше 0 (CHECK (billable > 0.0)
) -
добавляем в
job
запись с именемcalibrate
со значениемbillable
1.5 -
вторая запись с именем
clean
имеет значениеbillable
равное 0.5 -
третья запись с именем
reset
имеет значениеbillable
равное -0.5 — тут возникает проблема с записью третьей строки, так как это нарушает ограничениеCHECK (billable > 0.0)
Откат с помощью ROLLBACK
CREATE TABLE job (name text NOT NULL,
billable real NOT NULL,
CHECK (billable > 0.0));
INSERT OR ROLLBACK INTO job
VALUES ('calibrate', 1.5);
INSERT OR ROLLBACK INTO job
VALUES ('clean', 0.5),
('reset', -0.5);
SELECT *
FROM job;
Runtime error near line 11: CHECK constraint failed: billable > 0.0 (19)
| name | billable |
|-----------|----------|
| calibrate | 1.5 |
-
создаём таблицу
job
с 2 непустыми столбцами:-
текстовым столбцом
name
-
вещественнозначным
billable
с ограничениемCHECK (billable > 0.0)
— значение в этом столбце всегда будет больше нуля
-
-
вставляем данные в
job
с помощью оператораINSERT OR ROLLBACK
, а именно одну запись с названиемcalibrate
и значениемbillable
равным 1.5 -
вставляем ещё 2 записи в таблицу
job
с помощью оператораINSERT OR ROLLBACK
:clean
со значением 0.5 дляbillable
,reset
со значением -0.5billable
(что не подходит по условиюCHECK
)
Вставка значений
CREATE TABLE jobs_done (person text UNIQUE,
num integer DEFAULT 0);
INSERT INTO jobs_done
VALUES("zia", 1);
.print "after first"
SELECT *
FROM jobs_done;
.print
INSERT INTO jobs_done
VALUES("zia", 1);
.print "after failed"
SELECT *
FROM jobs_done;
INSERT INTO jobs_done
VALUES("zia", 1) ON conflict(person) DO
UPDATE
SET num = num + 1;
.print "nafter upsert"
SELECT *
FROM jobs_done;
after first
| person | num |
|--------|-----|
| zia | 1 |
Runtime error near line 14: UNIQUE constraint failed: jobs_done.person (19)
after failed
| person | num |
|--------|-----|
| zia | 1 |
after upsert
| person | num |
|--------|-----|
| zia | 2 |
-
создаём
jobs_done
со столбцамиperson
(текстовый тип данных с уникальными значениями) иnum
(целочисленный тип, по умолчанию равен 0) -
вставляем в
jobs_done
запись с именем"zia"
и числом 1 -
пытаемся снова вставить строку с тем же именем
"zia"
и числом 1 и снова выводим результаты запросаSELECT
-
вставляем строку с тем же именем
"zia"
и числом 1 но уже указываем, чтобы в случае конфликта по столбцуperson
, обновить значение столбцаnum
, увеличив его на 1
Создание триггера
-- Track hours of lab work.
CREATE TABLE job (person text NOT NULL,
reported real NOT NULL CHECK (reported >= 0.0));
-- Explicitly store per-person total rather than using sum().
CREATE TABLE total (person text UNIQUE NOT NULL,
hours real);
-- Initialize totals.
INSERT INTO total
VALUES ("gene", 0.0),
("august", 0.0);
-- Define a trigger.
CREATE TRIGGER total_trigger
BEFORE
INSERT ON job BEGIN -- Check that the person exists.
SELECT CASE
WHEN NOT EXISTS
(SELECT 1
FROM total
WHERE person = new.person) THEN raise
(ROLLBACK, 'Unknown person ')
END; -- Update their total hours (or fail if non-negative constraint violated).
UPDATE total
SET hours = hours + new.reported
WHERE total.person = new.person; END;
-
создаём таблицу
job
со столбцамиperson
иreported
-
создаём
total
со столбцамиperson
иhours
-
устанавливаем значения
gene
иaugust
в 0.0 -
создаём триггер
total_trigger
, который срабатывает перед вставкой новых записей в таблицуjob
. Этот триггер:-
проверяет, существует ли человек в таблице
total
, прежде чем разрешить вставку новых записей в таблицуjob
-
обновляет общее количество отработанных часов для соответствующего человека в таблице
total
путем добавления нового количества отработанных часов из таблицыjob
-
INSERT INTO job
VALUES ('gene', 1.5),
('august', 0.5),
('gene', 1.0);
| person | reported |
|--------|----------|
| gene | 1.5 |
| august | 0.5 |
| gene | 1.0 |
| person | hours |
|--------|-------|
| gene | 2.5 |
| august | 0.5 |
Срабатывание триггера
INSERT INTO job
VALUES ('gene', 1.0),
('august', -1.0) ;
Runtime error near line 6: CHECK constraint failed: reported >= 0.0 (19)
| person | hours |
|--------|-------|
| gene | 0.0 |
| august | 0.0 |
Графическое представление
CREATE TABLE lineage (parent text NOT NULL,
child text NOT NULL);
INSERT INTO lineage
VALUES ('Arturo', 'Clemente'),
('Darío', 'Clemente'),
('Clemente', 'Homero'),
('Clemente', 'Ivonne'),
('Ivonne', 'Lourdes'),
('Soledad', 'Lourdes'),
('Lourdes', 'Santiago');
SELECT *
FROM lineage;
| parent | child |
|----------|----------|
| Arturo | Clemente |
| Darío | Clemente |
| Clemente | Homero |
| Clemente | Ivonne |
| Ivonne | Lourdes |
| Soledad | Lourdes |
| Lourdes | Santiago |
Рекурсивный запрос
WITH RECURSIVE descendent AS (
SELECT 'Clemente' AS person,
0 AS generations
UNION ALL
SELECT lineage.child AS person,
descendent.generations + 1 AS generations
FROM descendent
JOIN lineage ON descendent.person = lineage.parent
)
SELECT person,
generations
FROM descendent;
| person | generations |
|----------|-------------|
| Clemente | 0 |
| Homero | 1 |
| Ivonne | 1 |
| Lourdes | 2 |
| Santiago | 3 |
-
определяем общий термин
descendent
(потомок) как рекурсивное общее выражение. Начинаем с одной записи, где'Clemente'
- это начальное имя, а 0 - это количество поколений. -
далее мы выполняем рекурсивное объединение с самим собой (с
descendent
) и таблицейlineage
, чтобы найти всех потомков для каждого найденного человека. Выбираем потомка из таблицыlineage
, увеличиваем количество поколений на 1 и продолжаем делать это для всех найденных потомков, пока они находятся -
если новых потомков больше не найдено, используем
SELECT
для выбора столбцовperson
иgenerations
изdescendent
База данных отслеживания контактов
SELECT *
FROM person;
| ident | name |
|-------|-----------------------|
| 1 | Juana Baeza |
| 2 | Agustín Rodríquez |
| 3 | Ariadna Caraballo |
| 4 | Micaela Laboy |
| 5 | Verónica Altamirano |
| 6 | Reina Rivero |
| 7 | Elias Merino |
| 8 | Minerva Guerrero |
| 9 | Mauro Balderas |
| 10 | Pilar Alarcón |
| 11 | Daniela Menéndez |
| 12 | Marco Antonio Barrera |
| 13 | Cristal Soliz |
| 14 | Bernardo Narváez |
| 15 | Óscar Barrios |
SELECT *
FROM contact;
| left | right |
|-------------------|-----------------------|
| Agustín Rodríquez | Ariadna Caraballo |
| Agustín Rodríquez | Verónica Altamirano |
| Juana Baeza | Verónica Altamirano |
| Juana Baeza | Micaela Laboy |
| Pilar Alarcón | Reina Rivero |
| Cristal Soliz | Marco Antonio Barrera |
| Cristal Soliz | Daniela Menéndez |
| Daniela Menéndez | Marco Antonio Barrera |
Продолжаем работать с bi_contact
CREATE TEMPORARY TABLE bi_contact (LEFT text, RIGHT text);
INSERT INTO bi_contact
SELECT LEFT,
RIGHT
FROM contact
UNION ALL
SELECT RIGHT,
LEFT
FROM contact;
| original_count |
|----------------|
| 8 |
| num_contact |
|-------------|
| 16 |
-
создаём временную табличку
bi_contact
с 2 столбцами:LEFT
иRIGHT
, оба текстовые -
вставляем в
bi_contact
данные из другой таблицы при помощиSELECT
-
используем
UNION ALL
для объединения результатов 2 операцийSELECT
в один набор данных; данные из столбцаLEFT
иRIGHT
таблицыcontact
вставляем в таблицуbi_contact
. Первый набор данных берёт значения из столбцовLEFT
иRIGHT
таблицыcontact
, а второй набор данных берёт значения из столбцовRIGHT
иLEFT
таблицыcontact
-
в общем, вставляем в
bi_contact
комбинацию значений из столбцовLEFT
иRIGHT
таблицыcontact
и их перевёрнутые комбинации
Обновляем идентификаторы групп
SELECT left.name AS left_name,
left.ident AS left_ident,
right.name AS right_name,
right.ident AS right_ident,
min(left.ident, right.ident) AS new_ident
FROM (
person AS
LEFT JOIN bi_contact ON left.name = bi_contact.left
)
JOIN person AS RIGHT ON bi_contact.right = right.name;
| left_name | left_ident | right_name | right_ident | new_ident |
|-----------------------|------------|-----------------------|-------------|-----------|
| Juana Baeza | 1 | Micaela Laboy | 4 | 1 |
| Juana Baeza | 1 | Verónica Altamirano | 5 | 1 |
| Agustín Rodríquez | 2 | Ariadna Caraballo | 3 | 2 |
| Agustín Rodríquez | 2 | Verónica Altamirano | 5 | 2 |
| Ariadna Caraballo | 3 | Agustín Rodríquez | 2 | 2 |
| Micaela Laboy | 4 | Juana Baeza | 1 | 1 |
| Verónica Altamirano | 5 | Agustín Rodríquez | 2 | 2 |
| Verónica Altamirano | 5 | Juana Baeza | 1 | 1 |
| Reina Rivero | 6 | Pilar Alarcón | 10 | 6 |
| Pilar Alarcón | 10 | Reina Rivero | 6 | 6 |
| Daniela Menéndez | 11 | Cristal Soliz | 13 | 11 |
| Daniela Menéndez | 11 | Marco Antonio Barrera | 12 | 11 |
| Marco Antonio Barrera | 12 | Cristal Soliz | 13 | 12 |
| Marco Antonio Barrera | 12 | Daniela Menéndez | 11 | 11 |
| Cristal Soliz | 13 | Daniela Menéndez | 11 | 11 |
| Cristal Soliz | 13 | Marco Antonio Barrera | 12 | 12 |
Рекурсивно устанавливаем метки
WITH recursive labeled AS (
SELECT person.NAME AS NAME,
person.ident AS label
FROM person
UNION -- not 'union all'
SELECT person.NAME AS NAME,
labeled.label AS label
FROM (
person
JOIN bi_contact ON person.NAME = bi_contact.LEFT
)
JOIN labeled ON bi_contact.RIGHT = labeled.NAME
WHERE labeled.label < person.ident
)
SELECT NAME,
min(label) AS group_id
FROM labeled
GROUP BY NAME
ORDER BY label,
NAME;
| name | group_id |
|-----------------------|----------|
| Agustín Rodríquez | 1 |
| Ariadna Caraballo | 1 |
| Juana Baeza | 1 |
| Micaela Laboy | 1 |
| Verónica Altamirano | 1 |
| Pilar Alarcón | 6 |
| Reina Rivero | 6 |
| Elias Merino | 7 |
| Minerva Guerrero | 8 |
| Mauro Balderas | 9 |
| Cristal Soliz | 11 |
| Daniela Menéndez | 11 |
| Marco Antonio Barrera | 11 |
| Bernardo Narváez | 14 |
| Óscar Barrios | 15 |
-
определяем рекурсивное выражение
labeled
— оно начинается с базового запроса, который выбирает имена и идентификаторы из таблицы person,-
затем используется
UNION
для объединения с другим запросом, который соединяет таблицыperson
иbi_contact
, используя столбцыname
иleft
вbi_contact
и имена и метки изperson
-
затем объединяет результаты этого соединения с ранее помеченными записями из
labeled
-
WHERE
устанавливает условие, что метка предыдущей записи должна быть меньше, чем идентификатор текущей записиperson
-
-
выполняем основной запрос — выбираем имена из
labeled
и вычисляем минимальную метку для каждого имени какgroup_id
с помощью функцииmin()
-
результат группируем по именам и сортируем сначала по метке, а затем по имени
-
attention: только не используйте тут
UNION ALL
, иначе возникнет бесконечная рекурсия)
Работа с SQL в Python при помощи sqlite3
import sqlite3
connection = sqlite3.connect("db/penguins.db")
cursor = connection.execute("SELECT count(*) FROM penguins;")
rows = cursor.fetchall()
print(rows)
[(344,)]
-
импортируем библиотечку
sqlite3
(к слову, она является одной из стандартных библиотек) для работы с SQLite -
устанавливаем соединение с БД, расположенной в файле
"db/penguins.db"
, используя методsqlite3.connect()
. Если этого файл не существует, то он будет создан -
создаём объект
cursor
для выполнения SQL-запросов -
select count(*) from penguins;
— подсчитываем количество всех записей в таблицеpenguins
-
fetchall()
— получаем результат выполнения запроса, сохраняем его в переменнуюrows
Инкрементная выборка
import sqlite3
connection = sqlite3.connect("db/penguins.db")
cursor = connection.cursor()
cursor = cursor.execute("SELECT species, island FROM penguins LIMIT 5;")
while row := cursor.fetchone():
print(row)
('Adelie', 'Torgersen')
('Adelie', 'Torgersen')
('Adelie', 'Torgersen')
('Adelie', 'Torgersen')
('Adelie', 'Torgersen')
-
коннектимся к БД с помощью
sqlite3.connect("db/penguins.db")
-
connection.cursor()
— создаём объектcursor
, это указатель на результат выполнения запросов -
select species, island from penguins limit 5;
— выбираем первые 5 записей из таблицыpenguins
, возвращая значения столбцовspecies
иisland
-
пока переменная
row
изcursor.fetchone()
непустая, печатаем её (мы сразу создаём переменнуюrow
и тут же используем её при помощи:=
)
Простые операции CREATE, INSERT, DELETE и другие с помощью sqlite3
import sqlite3
connection = sqlite3.connect(":memory:")
cursor = connection.cursor()
cursor.execute("CREATE TABLE example(num integer);")
cursor.execute("INSERT INTO example VALUES (10),(20);")
print("after insertion", cursor.execute("SELECT * FROM example;").fetchall())
cursor.execute("DELETE FROM example WHERE num < 15;")
print("after deletion", cursor.execute("SELECT * FROM example;").fetchall())
after insertion [(10,), (20,)]
after deletion [(20,)]
-
connection = sqlite3.connect(":memory:")
— создаём подключение к БД SQLite, созданной в оперативной памяти -
cursor = connection.cursor()
— создаём объект курсора, который используется для выполнения операций в БД -
cursor.execute("CREATE TABLE example(num integer);")
— создаём новую таблицу с именемexample
и одним столбцомnum
для хранения целых чисел -
cursor.execute("INSERT INTO example VALUES (10),(20);")
— вставляем 2 строки вexample
с числами 10 и 20 в столбецnum
-
print("after insertion", cursor.execute("SELECT * FROM example;").fetchall())
— выводим содержимое таблицыexample
после вставки строк; выполняем операциюSELECT
, чтобы выбрать все строки из таблицы, используя метод.fetchall()
для извлечения результатов запроса -
cursor.execute("DELETE FROM example WHERE num < 15;")
— удаляем строки из таблицыexample
, в которых значение столбцаnum
меньше 15 -
ну и в конце выводим содержимое таблицы
example
после удаления строк; также выполняемSELECT
, чтобы выбрать все строки из таблицы, используя метод.fetchall()
для извлечения результатов запроса
Интерполируем значения
import sqlite3
connection = sqlite3.connect(":memory:")
cursor = connection.cursor()
cursor.execute("CREATE TABLE example(num integer);")
cursor.executemany("insert into example values (?);", [(10,), (20,)])
print("after insertion", cursor.execute("SELECT * FROM example;").fetchall())
after insertion [(10,), (20,)]
-
connection = sqlite3.connect(":memory:")
— устанавливаем соединение с БД SQLite в оперативной памяти -
cursor = connection.cursor()
— создаём объект курсора, который используется для выполнения операций БД -
cursor.execute("create table example(num integer);")
— создаём таблицуexample
с одним столбцомnum
типа integer -
cursor.executemany("insert into example values (?);", [(10,), (20,)])
— вставляем значения 10 и 20 в столбецnum
таблицыexample
с использованием параметризованного запроса
Выполнение полноценных SQL-запросов в Python
import sqlite3
SETUP = """
DROP TABLE IF EXISTS example;
CREATE TABLE example(num integer);
INSERT INTO example
VALUES (10),
(20);
"""
connection = sqlite3.connect(":memory:")
cursor = connection.cursor()
cursor.executescript(SETUP)
print("after insertion", cursor.execute("SELECT * FROM example;").fetchall())
after insertion [(10,), (20,)]
-
удаляем таблицу
example
, если она существует -
создаём таблицу
example
с одним столбцомnum
типа integer -
вставляем 2 записи в таблицу
example
с числами 10 и 20 -
выполняем
SETUP
с помощью метода курсораexecutescript()
, который создает новую таблицу и вставляет данные -
выводим
after insertion
для обозначения того, что последующий запрос к базе данных будет относиться к состоянию после вставки данных -
выполняем запрос к БД для выбора всех записей из таблицы
example
с помощью методаexecute()
иfetchall()
для извлечения результатов
Исключения SQLite в Python
import sqlite3
SETUP = """
CREATE TABLE example(num integer check(num > 0));
INSERT INTO example
VALUES (10);
INSERT INTO example
VALUES (-1);
INSERT INTO example
VALUES (20);
"""
connection = sqlite3.connect(":memory:")
cursor = connection.cursor()
try:
cursor.executescript(SETUP)
except sqlite3.Error as exc:
print(f"SQLite exception: {exc}")
print("after execution", cursor.execute("SELECT * FROM example;").fetchall())
SQLite exception: CHECK constraint failed: num > 0
after execution [(10,)]
-
устанавливаем соединение с БД в оперативной памяти с помощью
sqlite3.connect(":memory:")
-
создаём курсор для выполнения операци
-
создаём таблицу
example
и вставляем в нее 3 значения с помощьюexecutescript()
-
в блоке
try-except
обрабатывается исключениеsqlite3.Error
, если произойдет какая-либо ошибка при выполнении запросов -
выводим содержимое таблицы
example
после выполнения запросов с помощью методаfetchall()
Python и SQLite, ещё некоторые возможности
import sqlite3
SETUP = """
CREATE TABLE example(num integer);
INSERT INTO example
VALUES (-10),
(10),
(20),
(30);
"""
def clip(value):
if value < 0:
return 0
if value > 20:
return 20
return value
connection = sqlite3.connect(":memory:")
connection.create_function("clip", 1, clip)
cursor = connection.cursor()
cursor.executescript(SETUP)
for row in cursor.execute("SELECT num, clip(num) FROM example;").fetchall():
print(row)
(-10, 0)
(10, 10)
(20, 20)
(30, 20)
-
создаём БД SQLite в оперативной памяти, создаём табличку
example
, заполняем её таблицу значениями (-10, 10, 20, 30) -
затем определяем функцию
clip
, которая принимает один аргумент и возвращает этот аргумент, если он находится между 0 и 20, или возвращает 0, если аргумент меньше 0, или возвращает 20, если аргумент больше 20 -
выбираем значения из столбца
num
таблицыexample
и применяет функциюclip
к каждому значению
Работа с датой и временем
from datetime import date
import sqlite3
# Convert date to ISO-formatted string when writing to database
def _adapt_date_iso(val):
return val.isoformat()
sqlite3.register_adapter(date, _adapt_date_iso)
# Convert ISO-formatted string to date when reading from database
def _convert_date(val):
return date.fromisoformat(val.decode())
sqlite3.register_converter("date", _convert_date)
SETUP = """
CREATE TABLE events(happened date NOT NULL,
description text NOT NULL);
"""
connection = sqlite3.connect(":memory:", detect_types=sqlite3.PARSE_DECLTYPES)
cursor = connection.cursor()
cursor.execute(SETUP)
cursor.executemany(
"insert into events values (?, ?);",
[(date(2024, 1, 10), "started tutorial"), (date(2024, 1, 29), "finished tutorial")],
)
for row in cursor.execute("SELECT * FROM EVENTS;").fetchall():
print(row)
(datetime.date(2024, 1, 10), 'started tutorial')
(datetime.date(2024, 1, 29), 'finished tutorial')
-
определяем функцию
_adapt_date_iso(val)
— она принимает дату и возвращает ее строковое представление в форматеISO
-
определяем функцию
_convert_date(val)
— она принимает строку в форматеISO
и возвращает объект типаdate
-
затем эти функции регистрируются в SQLite, чтобы обеспечить корректное преобразование данного типа данных при записи и чтении из базы данных
-
после этого создается строка
SETUP
, которая содержит SQL-команду для создания таблицыevents
с двумя столбцами:happened
типаdate
иdescription
типаtext
-
с помощью
cursor.executemany
в таблицуevents
вставляются 2 записи с использованием значений типаdate
и строк -
с помощью
select * from events
иcursor.execute
извлекаем значения всех строк из таблицыevents
SQL в Jupyter Notebooks
pip install jupysql
%load_ext sql
%sql sqlite:///data/penguins.db
Connecting to 'sqlite:///data/penguins.db'
-
Подключение к БД:
-
sqlite://
— протокол с 2 слэшами в конце -
/data/penguins.db
— 1 слэш спереди, это путь к локальной БД
-
-
1 знак процента
%sql
— для выполнения однострочных SQL-запросов -
2 знака процента
%%sql
показывает, что вся ячейка будет восприниматься как один SQL-запрос
%%sql
SELECT species,
count(*) AS num
FROM penguins
GROUP BY species;
Running query in 'sqlite:///data/penguins.db'
species |
num |
---|---|
Adelie |
152 |
Chinstrap |
68 |
Gentoo |
124 |
Pandas и SQL
pip install pandas
import pandas as pd
import sqlite3
connection = sqlite3.connect("db/penguins.db")
query = "SELECT species, count(*) AS num FROM penguins GROUP BY species;"
df = pd.read_sql(query, connection)
print(df)
species num
0 Adelie 152
1 Chinstrap 68
2 Gentoo 124
-
select species, count(*) as num from penguins group by species;
— извлекаем информацию о количестве пингвинов каждого вида изpenguins
и группируем результаты по видам -
выполняем запрос к БД с использованием метода
read_sql
библиотекиpandas
, который читает результаты запроса и преобразует их в объектDataFrame
(df)
Polars и SQL
pip install polars pyarrow adbc-driver-sqlite
import polars as pl
query = "SELECT species, count(*) AS num FROM penguins GROUP BY species;"
uri = "sqlite:///db/penguins.db"
df = pl.read_database_uri(query, uri, engine="adbc")
print(df)
shape: (3, 2)
┌───────────┬─────┐
│ species ┆ num │
│ --- ┆ --- │
│ str ┆ i64 │
╞═══════════╪═════╡
│ Adelie ┆ 152 │
│ Chinstrap ┆ 68 │
│ Gentoo ┆ 124 │
└───────────┴─────┘
-
импортирует библиотеку Polars - она похожа на pandas, но с фокусом на параллельную обработку данных
-
выбираем столбец
species
и вычисляем количество записей для каждого вида пингвинов из таблицыpenguins
; результат группируем по столбцуspecies
-
устанавливаем строку подключения к базе данных SQLite в переменной
uri
-
используем
pl.read_database_uri
для выполнения SQL-запросаquery
к БД, указанной вuri
, используя движокadbc
-
выводим результат выполнения запроса в виде таблицы данных
ORM
from sqlmodel import Field, Session, SQLModel, create_engine, select
class Department(SQLModel, table=True):
ident: str = Field(default=None, primary_key=True)
name: str
building: str
engine = create_engine("sqlite:///db/assays.db")
with Session(engine) as session:
statement = select(Department)
for result in session.exec(statement).all():
print(result)
building='Chesson' name='Genetics' ident='gen'
building='Fashet Extension' name='Histology' ident='hist'
building='Chesson' name='Molecular Biology' ident='mb'
building='TGVH' name='Endocrinology' ident='end'
-
создаём класс
Department
, который представляет модель данных для отделов; каждый атрибут класса соответствует столбцу в таблице БД -
создаём объект
engine
, который представляет собой подключение к SQLite БД, гдеassays.db
- это имя файла БД -
создаём
Session
для взаимодействия с базой данных через созданныйengine
-
формируем SQL-запрос с помощью
select(Department)
, который выбирает все данные из таблицы, представленной модельюDepartment
-
выполняем запрос к БД через
session.exec(statement).all()
, который возвращает все строки, удовлетворяющие условию запроса
Продолжаем работать с ORM
class Staff(SQLModel, table=True):
ident: str = Field(default=None, primary_key=True)
personal: str
family: str
dept: Optional[str] = Field(default=None, foreign_key="department.ident")
age: int
engine = create_engine("sqlite:///db/assays.db")
SQLModel.metadata.create_all(engine)
with Session(engine) as session:
statement = select(Department, Staff).where(Staff.dept == Department.ident)
for dept, staff in session.exec(statement):
print(f"{dept.name}: {staff.personal} {staff.family}")
Histology: Divit Dhaliwal
Molecular Biology: Indrans Sridhar
Molecular Biology: Pranay Khanna
Histology: Vedika Rout
Genetics: Abram Chokshi
Histology: Romil Kapoor
Molecular Biology: Ishaan Ramaswamy
Genetics: Nitya Lal
-
объявляем класс
Staff
; он использует SQLModel, что позволяет использовать этот класс как схему для создания таблицы в БД. Указаниеtable=True
в качестве аргумента класса говорит SQLModel о том, что данный класс должен отображаться в базу данных как таблица. УStaff
есть несколько атрибутов :-
ident
- строковое поле, которое будет использоваться в качестве первичного ключа в базе данных. Оно имеет значение по умолчаниюNone
и задается как первичный ключ (primary_key=True
) -
personal
- строковое поле -
family
- строковое поле -
dept
- опциональное строковое поле; имеет значение по умолчаниюNone
и устанавливается как внешний ключ (foreign_key="department.ident"
) -
age
- целочисленное поле
-
-
после определения
Staff
, создается экземпляр движка для работы с БД SQLite с помощью вызова функцииcreate_engine
из библиотеки SQLAlchemy -
затем вызываем метод
create_all
у метаданных SQLModel, что приводит к созданию всех таблиц, определенных в виде классов с помощью SQLModel, на основе ранее созданного движка базы данных -
далее устанавливаем сессия БД с использованием созданного ранее движка
-
формируется SQL-запрос, который выбирает данные из таблиц
Department
иStaff
, объединяя их по условию, что полеStaff.dept
равно полюDepartment.ident
-
выполняем этот запрос в сессии БД, и для каждой строки результата выводится название отдела и персональные данные сотрудника
The end
Что ж, пользуйтесь этими примерами SQL-запросов на здоровье; особенно эта подборка может быть полезной, если хочется кому-то объяснить что-то из SQL, и нужен подходящий пример
Всех с пятницей!
Автор: uproger