Команда `\d` без параметров выведет список всех таблиц и объектов в текущей базе данных. Если добавить символ `+`, для каждой таблицы можно увидеть дополнительную информацию такую как количество занимаемого места на диске.
```
\d+ weather
```
Существует множество `\d`-команд. Наиболее часто используемые:
В терминале вы также можете написать многострочный запрос. Вводите запрос как обычно, используя Enter для переноса строки. `psql` создаст так называемый буфер запроса. Запрос не будет интерпретирован, пока вы не введёте символ `;`. Чтобы сбросить буфер при ошибке ввода, используйте `\r`.
SQL запросы, связанные с модификацией данных таблиц называют DML (Data Modification Language) запросами. SQL запросы, связанные с модификацией базы данных называют DDL (Data Definition Language) запросами.
В реляционных базах данные разбиваются на таблицы так, чтобы избыточность хранимой информации была минимальной, запросы и долгосрочная поддержка были эффективными. Это приводит к необходимости объединять информацию из разных таблиц с помощью механизма `JOIN`.
В следующих примерах мы рассмотрим гипотетическую базу данных для управления человеческими ресурсами. Перед выполнением выполните инициализацию базы данных
Каждый сотрудник имеет свой идентификационный номер `employee_id`. В таблице хранится также важная информация как имя, фамилия, должность и зарплата. Принадлежность к отделу выражается в наличии связи с таблицей `departments` через поле `department_id`. Поле `manager_id` ведёт к менеджеру сотрудника.
### 3.1 Внутренние соединения (inner join)
Внутреннее соединение является наиболее распространенным и соединяет столбец в одной таблице и со столбцом в другой. В нашей базе данных HR мы хотели бы найти сотрудников и их отделы. Итак, допустим, мы хотим вывести сотрудников и отделы, в которых они числятся:
Обратите внимание, как мы обращаемся к `employee_id`, `first_name`, `last_name` из таблицы `employees` и к `department_name` из таблицы `departments`. Мы связыаем эти два множества информации в часи `FROM`. Обратите внимание, что `INNER JOIN` и `ON` зарезервированные слова. В примеры выше мы хотим получить пересечение элементов таблицы `employees` и `departments` по полю `department_id`.
Мы обратились к СУБД на специальном языке и СУБД взяла на себя самую сложную часть, избавив нас от необходимости писать специализированную программу для поиска и выгрузки информации.
### 3.2 Левые внешние соединения (left outer join)
При использовании механизма `LEFT OUTER JOIN` из левой таблицы вернуться все строки, и если справа не найдётся соответсвия, то в столбцах правой таблицы вместо значений будет присутствовать `NULL`.
В базе данных, с которой мы работаем младшие сотрудники зависят от старших. Ниже приведена схема третьей таблицы `dependents`, которая фиксирует эти отношения.
Этим запросом мы объединяем таблицу сотрудников слева и подчинённых справа. Если у сотрудника нет подчинённых, его строке будет сопоставлена строка с значениями `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 |
Узнаем, где географически расположены отделы компании
Результат говорят нам сообщают, что в городе Effingham штата Индиана США нет отдела. Значит офис был закрыт.
### 3.4 Cоединения на себя (self join)
Соединение на себя - это не технический, а концептуальный термин, который стоит упомянуть отдельно. Такой способ соединения полезен, когда таблица содержит иерархические данные. В нашем примере это таблица `employees`. Обратите внимание, что каждому сотруднику назначен менеджер `manager_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 выполняет все команды, даже самые простые в контексте транзакций.