Updated postgresql tasks.

pull/1/head
Vladimir Protsenko 2 years ago
parent 55fb0dde38
commit 00b2b24b92

@ -1,20 +1,19 @@
# Задания # Задания
## 1. ## 1.
Установите postgresql на любую виртуальную машину. Установите postgresql на любую виртуальную машину. Перейдите в пользователя `postgres`.
```
$ sudo su - postgres
```
## 2. Вы суперпользователь базы данных.
Пройдите все уроки https://www.crunchydata.com/developers/tutorials по SQL запросам. Далее следует перевод заданий.
При выполнении на локальном компьютере используйте скрипт `weather.sql` для инициализации базы данных. В `psql` выполните ## 2. Основы
В `psql` выполните инициализацю таблиц базы данных
``` ```
\i scripts/weather.sql \i scripts/weather.sql
``` ```
## 2.1 Основы
Вы суперпользователь.
Первая команда, которую нужно знать, это как получить помощь по командам: Первая команда, которую нужно знать, это как получить помощь по командам:
``` ```
\? \?
@ -41,7 +40,7 @@
\! ls -la \! ls -la
``` ```
### 2.1.1 Описание таблиц ### 2.1 Описание таблиц
Команда `\d` без параметров выведет список всех таблиц и объектов в текущей базе данных. Если добавить символ `+`, для каждой таблицы можно увидеть дополнительную информацию такую как количество занимаемого места на диске. Команда `\d` без параметров выведет список всех таблиц и объектов в текущей базе данных. Если добавить символ `+`, для каждой таблицы можно увидеть дополнительную информацию такую как количество занимаемого места на диске.
``` ```
@ -49,20 +48,21 @@
``` ```
Существует множество `\d`-команд. Наиболее часто используемые: Существует множество `\d`-команд. Наиболее часто используемые:
- `\dt` - вывод всех таблиц,
- `\dn` - вывод всех схем, - `\dn` - вывод всех схем,
- `\dv` - вывод всех представлений (views), - `\dv` - вывод всех представлений (views),
- `\du` - вывод всех пользователей, - `\du` - вывод всех пользователей,
- `\df` - вывод всех функций, - `\df` - вывод всех функций,
- `\dp` - вывод привилегий доступа для таблиц и представлений. - `\dp` - вывод привилегий доступа для таблиц и представлений.
### 2.1.2 Пейджер ## 2.2 Пейджер
По умолчанию пейджер `psql`, который разбивает вывод на страницы, включён. Вы можете отключить его установить значение параметра `pager` равным 0. По умолчанию пейджер `psql`, который разбивает вывод на страницы, включён. Вы можете отключить его установить значение параметра `pager` равным 0.
``` ```
\pset pager 1 \pset pager 1
``` ```
### 2.1.3 SQL запросы ## 2.3 SQL запросы
Попробуйте дать базе данных простой запрос Попробуйте дать базе данных простой запрос
```sql ```sql
@ -70,7 +70,7 @@ SELECT event_type FROM weather LIMIT 20;
``` ```
Это запрос на получение данных колонки `event_type` из таблицы `weather`. Количество результатов будет ограничено 20 записями. Это запрос на получение данных колонки `event_type` из таблицы `weather`. Количество результатов будет ограничено 20 записями.
### 2.1.3.1 Буфер многострочного запроса ### 2.3.1 Буфер многострочного запроса
В терминале вы также можете написать многострочный запрос. Вводите запрос как обычно, используя Enter для переноса строки. `psql` создаст так называемый буфер запроса. Запрос не будет интерпретирован, пока вы не введёте символ `;`. Чтобы сбросить буфер при ошибке ввода, используйте `\r`. В терминале вы также можете написать многострочный запрос. Вводите запрос как обычно, используя Enter для переноса строки. `psql` создаст так называемый буфер запроса. Запрос не будет интерпретирован, пока вы не введёте символ `;`. Чтобы сбросить буфер при ошибке ввода, используйте `\r`.
```sql ```sql
@ -80,7 +80,7 @@ FROM weather
WHERE state = 'HAWAII'; WHERE state = 'HAWAII';
``` ```
### 2.1.3.2 Замер времени выполнения запроса ### 2.3.2 Замер времени выполнения запроса
Вы можете замерить время выполнения запроса, включив его командой `\timing` Вы можете замерить время выполнения запроса, включив его командой `\timing`
```sql ```sql
@ -88,7 +88,7 @@ WHERE state = 'HAWAII';
SELECT DISTINCT(event_type) FROM weather LIMIT 40; SELECT DISTINCT(event_type) FROM weather LIMIT 40;
``` ```
### 2.1.4 DDL, DML запросы ### 2.4 DDL, DML запросы
SQL запросы, связанные с модификацией данных таблиц называют DML (Data Modification Language) запросами. SQL запросы, связанные с модификацией базы данных называют DDL (Data Definition Language) запросами. SQL запросы, связанные с модификацией данных таблиц называют DML (Data Modification Language) запросами. SQL запросы, связанные с модификацией базы данных называют DDL (Data Definition Language) запросами.
@ -101,40 +101,281 @@ WHERE episode_id = 57676;
Чтобы выйти из консоли введите `\q`. Чтобы выйти из консоли введите `\q`.
## 2.2 Соединения таблиц ## 3. Соединения таблиц
В реляционных базах данные разбиваются на таблицы так, чтобы избыточность хранимой информации была минимальной, запросы и долгосрочная поддержка были эффективными. Это приводит к необходимости объединять информацию из разных таблиц с помощью механизма `JOIN`. В реляционных базах данные разбиваются на таблицы так, чтобы избыточность хранимой информации была минимальной, запросы и долгосрочная поддержка были эффективными. Это приводит к необходимости объединять информацию из разных таблиц с помощью механизма `JOIN`.
Для этих примеров мы рассмотрим гипотетическую базу данных для приложения для человеческих ресурсов.Это приложение отслеживает отделы, сотрудников, их менеджеры и зарплаты. Перед выполнением выполните инициализацию базы данных В следующих примерах мы рассмотрим гипотетическую базу данных для управления человеческими ресурсами. Перед выполнением выполните инициализацию базы данных
``` ```sql
\i scripts/hr.sql \i scripts/hr.sql
``` ```
### 2.2.1 Внутренние соединения (inner join) Схема таблицы Отделы (departments)
```sql
\d departments
```
| Колонка | Связь с |
|-----------------|-----------------------------------------|
| department_id | |
| department_name | |
| location_id | таблицей locations колонкой location_id |
Схема таблицы Сотрудники (employees)
```
\d employees
```
| Колонка | Связь с |
|---------------|---------------------------------------------|
| employee_id | |
| first_name | |
| last_name | |
| start_date | |
| job_title | |
| salary | |
| manager_id | таблицей employees колонкой employee_id |
| department_id | таблицей departments колонкой department_id |
Каждый сотрудник имеет свой идентификационный номер `employee_id`. В таблице хранится также важная информация как имя, фамилия, должность и зарплата. Принадлежность к отделу выражается в наличии связи с таблицей `departments` через поле `department_id`. Поле `manager_id` ведёт к менеджеру сотрудника.
### 3.1 Внутренние соединения (inner join)
Внутреннее соединение является наиболее распространенным и соединяет столбец в одной таблице и со столбцом в другой. В нашей базе данных HR мы хотели бы найти сотрудников и их отделы. Итак, допустим, мы хотим вывести сотрудников и отделы, в которых они числятся:
```sql ```sql
SELECT e.employee_id, e.first_name, e.last_name, d.department_name SELECT e.employee_id, e.first_name, e.last_name, d.department_name
FROM employees e FROM employees e
INNER JOIN departments d ON (e.department_id = d.department_id); INNER JOIN departments d ON (e.department_id = d.department_id);
``` ```
Обратите внимание, как мы обращаемся к `employee_id`, `first_name`, `last_name` из таблицы `employees` и к `department_name` из таблицы `departments`. Мы связыаем эти два множества информации в часи `FROM`. Обратите внимание, что `INNER JOIN` и `ON` зарезервированные слова. В примеры выше мы хотим получить пересечение элементов таблицы `employees` и `departments` по полю `department_id`.
Мы обратились к СУБД на специальном языке и СУБД взяла на себя самую сложную часть, избавив нас от необходимости писать специализированную программу для поиска и выгрузки информации.
### 2.2.2 Левые внешние соединения (left outer join) ### 3.2 Левые внешние соединения (left outer join)
При использовании механизма `LEFT OUTER JOIN` из левой таблицы вернуться все строки, и если справа не найдётся соответсвия, то в столбцах правой таблицы вместо значений будет присутствовать `NULL`.
В базе данных, с которой мы работаем младшие сотрудники зависят от старших. Ниже приведена схема третьей таблицы `dependents`, которая фиксирует эти отношения.
Схема таблицы Подчиненные (dependents)
```
\d dependents
```
| Колонка | Связь с |
|---------------|---------------------------------------------|
| employee_id | |
| first_name | |
| last_name | |
| employee_id | таблицей employees колонкой employee_id |
Допустим, что кроме отдела сотрудника нам также нужно вывести его подчинённых:
```sql ```sql
SELECT e.employee_id, e.first_name, e.last_name, d.department_name, dep.first_name || ' ' || dep.last_name AS dependent SELECT e.employee_id, e.first_name, e.last_name, d.department_name, dep.first_name || ' ' || dep.last_name AS dependent
FROM employees e FROM employees e
INNER JOIN departments d ON (e.department_id = d.department_id) INNER JOIN departments d ON (e.department_id = d.department_id)
LEFT OUTER JOIN dependents dep ON (e.employee_id = dep.employee_id); LEFT OUTER JOIN dependents dep ON (e.employee_id = dep.employee_id);
``` ```
### 2.2.3 Правые внешние соединения (right outer join) Этим запросом мы объединяем таблицу сотрудников слева и подчинённых справа. Если у сотрудника нет подчинённых, его строке будет сопоставлена строка с значениями `NULL`. Мы можем увидеть, чтоб PostgreSQL вернула сотрудника John Smith у которого в подчинении два человека: Spoiled and DoNothing Smith, и сотрудника Howard TheDuck, возможно middle python-программиста, у которого никого в подчинении нет.
Если бы мы использовали `INNER JOIN`, то исключили бы обычных сотрудников из результатов запроса.
### 3.3 Правые внешние соединения (right outer join)
В ситуации с `RIGHT OUTER JOIN` всё происходит наоборот. При отсутствии соответствующей записи слева для записи таблицы указанной справа значениями `NULL` заполняются столбцы левой таблицы.
Ранее в схеме departments мы видели, что каждый отдел связан с географическим положением через поле `location_id` таблицы Местоположения (locations).
Схема таблицы Местоположения (locations)
```
\d locations
```
| Колонка |
|----------------|
| location_id |
| street_address |
| postal_code |
| city |
| state_province |
| country_id |
Узнаем, где географически расположены отделы компании
```sql ```sql
SELECT d.department_name, l.city, l.state_province, l.country_id SELECT d.department_name, l.city, l.state_province, l.country_id
FROM departments d FROM departments d
RIGHT OUTER JOIN locations l ON (d.location_id = l.location_id); RIGHT OUTER JOIN locations l ON (d.location_id = l.location_id);
``` ```
### 2.2.4 Cоединения на себя (self join) Результат говорят нам сообщают, что в городе Effingham штата Индиана США нет отдела. Значит офис был закрыт.
### 3.4 Cоединения на себя (self join)
Соединение на себя - это не технический, а концептуальный термин, который стоит упомянуть отдельно. Такой способ соединения полезен, когда таблица содержит иерархические данные. В нашем примере это таблица `employees`. Обратите внимание, что каждому сотруднику назначен менеджер `manager_id`, который в свою очередь сам является сотрудником, а значит иметь своего менеджера уровнем выше.
Используя соединение на себя мы можем запросить у базы данных список сотрудников и их менеджеров
```sql ```sql
SELECT e.first_name, e.last_name, e.job_title, format('%s %s', m.first_name, m.last_name) AS manager SELECT e.first_name, e.last_name, e.job_title, format('%s %s', m.first_name, m.last_name) AS manager
FROM employees e LEFT OUTER JOIN employees m ON (e.manager_id = m.employee_id); FROM employees e LEFT OUTER JOIN employees m ON (e.manager_id = m.employee_id);
``` ```
Поздравляем! Вы познакомились с наиболее типичными видами соединений таблиц.
## 4. Транзакции
Транзакия - это набор действий, которые выполняются атомарно для внешнего наблюдателя. Если часть действий выполнится не может, откатываются все изменения транзакции.
Использование транзакций СУБД позволяет уменьшить количество кода необходимого для работы с данными. Если бы СУБД не управляли состоянием транзакций, тогда управляющий код пришлось бы реализовывать в каждом приложении, что усложняло бы код и приводило к ошибкам. Например, представьте типичную ситуацию регистрации пользователя в системе. На уровне приложения вам пришлось бы выполнить следующие действия:
1. Создать пользователя
2. Проверить, что он создан. Если не создан, завершить процесс с ошибкой.
3. Создать аккаунт.
4. Проверить, что он создан. Если не создан, удалить пользователя, завершить процесс с ошибкой.
5. Соединить аккаунт и ползьзователя.
6. Проверить, что соединение создано. Если не создано, удалить пользователя и аккаунт, завершить процесс с ошибкой.
С механизмом транзакций количество шагов уменьшается в 2 раза:
1. Создать пользователя
2. Создать аккаунт
3. Соединить аккаунт и ползьзователя
Транзакции позволяют не писать логику возврата в предыдущее состояние, до начала транзакции.
### 4.1 Простейшие транзакции
Самая простая транзакия состоит из одного запроса на модификацию данны, заключенного между `BEGIN` и `COMMIT`.
```sql
BEGIN;
INSERT INTO employees (first_name, last_name, start_date, salary, job_title, manager_id, department_id)
VALUES ('Cris', 'Winslett', current_date, 1, 'Director of the Highwire', 2, 2)
RETURNING employee_id;
COMMIT;
```
Чтобы подтвердить, что данные были успешно внесены в БД выполните запрос
```sql
SELECT * FROM employees
WHERE
first_name = 'Cris' AND
last_name = 'Winslett';
```
Однако, если вы решите откатить транзакцию после изменений вводом команды `ROLLBACK`, то вы увидите другой результат
```sql
BEGIN;
INSERT INTO employees (first_name, last_name, start_date, salary, job_title, manager_id, department_id)
VALUES ('Elizabeth', 'Christensen', current_date, 1, 'Master of the Highwire', 2, 2)
RETURNING employee_id;
ROLLBACK;
```
Добавленная строка имела идентификатор 10, но была удалена процедурой отката транзакции.
```sql
SELECT * FROM employees
WHERE
first_name = 'Elizabeth' AND
last_name = 'Christensen';
```
Однако некоторые изменения всё-таки происходят даже в случае отката. Убедитесь в том, что `employee_id` монотонно возрастает, выполнив транзакцию с откатом ещё несколько раз. Это происходит, чтобы у каждой транзакции гарантированно было уникальное значение, взятое из последовательности идентификаторов.
Команда `ROLLBACK` всегда завершает транзакцию и отбрасывает изменения. `COMMIT` выполняется только тогда, когда в ходе выполнения действий транзакции не было ошибок. Попробуйте выполнить следующий пример с ошибкой, чтобы в этом убедиться
```sql
BEGIN;
INSERT INTO employees (first_name, last_name)
VALUES ('Tome', 'Jones')
RETURNING employee_id;
COMMIT;
```
### 4.2 Комплексные транзакции
В реальной жизни транзакции состоят из множества действий.
```sql
BEGIN;
INSERT INTO employees (first_name, last_name, start_date, salary, job_title, manager_id, department_id)
VALUES ('Chris', 'Winslett', current_date, 1, 'Jr Director of the Highwire', 2, 2);
INSERT INTO dependents (first_name, last_name, employee_id)
VALUES ('oldest', 'kid', (SELECT employee_id FROM employees WHERE first_name = 'Chris' AND last_name = 'Winslett'));
INSERT INTO dependents (first_name, last_name, employee_id)
VALUES ('youngest', 'kid', (SELECT employee_id FROM employees WHERE first_name = 'Chris' AND last_name = 'Winslett'));
COMMIT;
```
Если вы попробуете выполнить эту транзакцию два раза подряд, то второй раз получите ошибку. Это произойдет потому, что вложенный запрос второй раз вернёт 2 результата - для внесённого ранее сотрудника и внесённого только что. Таким образом транзакци не даст создать запись-дубликат.
В следующем запросе попробуем внести `null` значение в обязательное поле `first_name` для второго подчинённого. Транзакция не выполнится из-за нарушеного ограничения.
```sql
BEGIN;
INSERT INTO employees (first_name, last_name, start_date, salary, job_title, manager_id, department_id)
VALUES ('Bob', 'Young', current_date, 1, 'Jr Director of the Highwire', 2, 2);
INSERT INTO dependents (first_name, last_name, employee_id)
VALUES ('oldest', 'kid', (SELECT employee_id FROM employees WHERE first_name = 'Bob' AND last_name = 'Young'));
INSERT INTO dependents (first_name, last_name, employee_id)
VALUES (null, 'kid', (SELECT employee_id FROM employees WHERE first_name = 'Bob' AND last_name = 'Young'));
COMMIT;
```
Проверьте, что Bob Young не добавился.
### 4.3 Область видимости изменений транзакции
Что произойдёт, если вы запустили транзакцию, а в этом время к данным, которые вы модифицируете в новой транзакции обратился другой пользователь? Изменения в транзакциях невидимы другим транзакциям, пока транзакция производящие измнения не завершились.
В Postgres изменения структуры таблиц также не реализуются (committed) пока транзакция не выполнится успешно. Это уникальное свойство мире баз данных. Проведём два эксперимента - с корректной и некорректной транзакциями.
```sql
BEGIN;
ALTER TABLE employees
ADD COLUMN niddle_name VARCHAR(50) DEFAULT NULL;
COMMIT;
```
Проверьте, что структура таблицы изменилась командой `\d employees`.
Теперь попробуем транзакцию с ошибкой:
```sql
BEGIN;
ALTER TABLE employees ADD COLUMN address_line_1 VARCHAR(50) DEFAULT NULL;
ALTER TABLE employees ADD COLUMN address_line_2 VARCHAR(50) DEFAULT NULL;
ALTER TABLE employees ADD COLUMN city VARCHAR(50) DEFAULT NULL;
ALTER TABLE employees ADD COLUMN province VARCHAR(50) DEFAULT NULL;
ALTER TABLE employees ADD COLUMN postal_code VARCHAR(50) NOT NULL;
COMMIT;
```
Из-за того, что изменения структуры были внутри одной большой транзакции и значение по умолчанию для `postal_code` не было указано никакие изменения не были применены. Проверьте, что структура таблицы не изменилась.
Такой вид транзакций ещё называют транзакционным язык описания данных (Transactional DDL). Они жизненно важны для миграций баз данных.
### 4.4 Продвинутые транзакции с вложенными точками отката
Для некоторый специфичный случаев могут потребоваться вложенные транзакции. Postgres позволяет сделать даже больше - делать точки сохранения внутри транзакции. Точка сохранения (SAVEPOINT) предоставляет указатель на который может ссылаться комадна `ROLLBACK`.
```sql
BEGIN;
INSERT INTO employees (first_name, last_name, start_date, salary, job_title, manager_id, department_id)
VALUES ('Bob', 'Young', current_date, 1, 'Jr Director of the Highwire', 2, 2);
SAVEPOINT saved_employee;
INSERT INTO dependents (first_name, last_name, employee_id)
VALUES ('oldest', 'kid', (SELECT employee_id FROM employees WHERE first_name = 'Bob' AND last_name = 'Young'));
INSERT INTO dependents (first_name, last_name, employee_id)
VALUES (null, 'kid', (SELECT employee_id FROM employees WHERE first_name = 'Bob' AND last_name = 'Young'));
ROLLBACK TO SAVEPOINT saved_employee;
COMMIT;
```
Теперь, если вы попробуете найти Bob Young - вы найдёте его, но не найдёте его подчинённых. Что случилось? Эту транзакцию стало возможным выполнить частично из-за использования команды `SAVEPOINT`.
### 4.5 Я в транзакции?
Чтобы узнать идентификатор транзакции, в которой вы находитесь, вызовите команду
```
SELECT txid_current();
```
Вы можете удивиться, ведь вы не запускали транзакцию. На самом деле Postgres выполняет все команды, даже самые простые в контексте транзакций.
## Релевантные источники
- Мартин Грабер — Понимание SQL
- https://www.postgresql.org/docs/
- https://theartofpostgresql.com
- https://www.crunchydata.com/developers/tutorials
Loading…
Cancel
Save