Контрольные вопросы и задания
19.* В разделе 6.4 мы использовали рекурсивный алгоритм в общем табличном вы- ражении. Изучите этот пример, чтобы лучше понять работу рекурсивного алго- ритма:
WITH RECURSIVE ranges ( min_sum, max_sum )
AS (
VALUES( 0,
100000 ),
( 100000, 200000 ),
( 200000, 300000 )
UNION ALL
SELECT min_sum + 100000, max_sum + 100000
FROM ranges
WHERE max_sum < ( SELECT max( total_amount ) FROM bookings )
)
SELECT * FROM ranges;
min_sum | max_sum
---------+---------
0 | 100000
исходные строки
100000 | 200000 200000 | 300000 100000 | 200000
результат первой итерации
200000 | 300000 300000 | 400000 200000 | 300000
результат второй итерации
300000 | 400000 400000 | 500000 300000 | 400000 400000 | 500000 500000 | 600000 1000000 | 1100000
результат (n-3)-й итерации
1100000 | 1200000 1200000 | 1300000 1100000 | 1200000
результат (n-2)-й итерации
1200000 | 1300000 1200000 | 1300000
результат (n-1)-й итерации (предпоследней)
(36 строк)
201
Глава 6. Запросы
Здесь мы с помощью предложения VALUES специально создали виртуальную таблицу из трех строк, хотя для получения требуемого результата достаточно только одной строки (0, 100000). Еще важно то, что предложение UNION ALL не удаляет строки-дубликаты, поэтому мы можем видеть весь рекурсивный про- цесс порождения новых строк.
При рекурсивном выполнении запроса
SELECT min_sum + 100000, max_sum + 100000
FROM ranges
WHERE max_sum < ( SELECT max( total_amount ) FROM bookings )
каждый раз выполняется проверка в условии WHERE. И на (n − 2)-й итерации это условие отсеивает одну строку, т. к. после (n − 3)-й итерации значение атрибута max_sum в третьей строке было равно 1 300 000.
Ведь запрос
SELECT max( total_amount ) FROM bookings;
выдаст значение max
------------
1204500.00
(1 строка)
Таким образом, после (n − 2)-й итерации во временной области остается всего две строки, после (n−1)-й итерации во временной области остается только одна строка.
Заключительная итерация уже не добавляет строк в результирующую таблицу,
поскольку единственная строка, поданная на вход команде SELECT, будет от- клонена условием WHERE. Работа алгоритма завершается.
Задание 1.
Модифицируйте запрос, добавив в него столбец level (можно на- звать его и iteration). Этот столбец должен содержать номер текущей итера- ции, поэтому нужно увеличивать его значение на единицу на каждом шаге. Не забудьте задать начальное значение для добавленного столбца в предложении
VALUES.
Задание 2.
Для завершения экспериментов замените UNION ALL на UNION и выполните запрос. Сравните этот результат с предыдущим, когда мы исполь- зовали UNION ALL.
202
Контрольные вопросы и задания
20.* В тексте главы есть такой запрос, вычисляющий распределение сумм брониро- ваний по диапазонам в 100 тысяч рублей:
WITH RECURSIVE ranges ( min_sum, max_sum )
AS (
VALUES( 0, 100000 )
UNION ALL
SELECT min_sum + 100000, max_sum + 100000
FROM ranges
WHERE max_sum < ( SELECT max( total_amount ) FROM bookings )
)
SELECT r.min_sum,
r.max_sum,
count( b.* )
FROM bookings b
RIGHT OUTER JOIN ranges r
ON b.total_amount >= r.min_sum
AND b.total_amount < r.max_sum
GROUP BY r.min_sum, r.max_sum
ORDER BY r.min_sum;
Как вы думаете, почему функция count получает в качестве параметра выра- жение b.*, а не просто *? Что изменится, если оставить только *, и почему?
21. В тексте главы был приведен запрос, выводящий список городов, в которые нет рейсов из Москвы.
SELECT DISTINCT a.city
FROM airports a
WHERE NOT EXISTS (
SELECT * FROM routes r
WHERE r.departure_city = 'Москва'
AND r.arrival_city = a.city
)
AND a.city <> 'Москва'
ORDER BY city;
Можно предложить другой вариант, в котором используется одна из операций над множествами строк: объединение, пересечение или разность.
Вместо знака «?» поставьте в приведенном ниже запросе нужное ключевое сло- во — UNION, INTERSECT или EXCEPT — и обоснуйте ваше решение.
203
Глава 6. Запросы
SELECT city
FROM airports
WHERE city <> 'Москва'
?
SELECT arrival_city
FROM routes
WHERE departure_city = 'Москва'
ORDER BY city;
22. В тексте главы мы рассматривали такой запрос: получить перечень аэропортов в тех городах, в которых больше одного аэропорта.
SELECT aa.city, aa.airport_code, aa.airport_name
FROM (
SELECT city, count( * )
FROM airports
GROUP BY city
HAVING count( * ) > 1
) AS a
JOIN airports AS aa ON a.city = aa.city
ORDER BY aa.city, aa.airport_name;
Как вы думаете, обязательно ли наличие функции count в подзапросе в пред- ложении SELECT или можно написать просто
SELECT city FROM airports
Сначала попробуйте дать ответ теоретически, а потом проверьте вашу гипотезу на компьютере.
23. Предположим, что департамент развития нашей авиакомпании задался вопро- сом: каким будет общее число различных маршрутов, которые теоретически можно проложить между всеми городами?
Если в каком-то городе имеется более одного аэропорта, то это учитывать не будем, т. е. маршрутом будем считать путь между городами, а не между аэро-
портами
. Здесь мы используем соединение таблицы с самой собой на основе неравенства значений атрибутов.
SELECT count( * )
FROM ( SELECT DISTINCT city FROM airports ) AS a1
JOIN ( SELECT DISTINCT city FROM airports ) AS a2
ON a1.city <> a2.city;
204
Контрольные вопросы и задания
count
-------
10100
(1 строка)
Задание.
Перепишите этот запрос с общим табличным выражением.
24. В тексте главы мы рассмотрели использование подзапросов в предикатах
EXISTS и IN. Существуют также предикаты многократного сравнения ANY и
ALL. Они представлены в документации в разделе 9.22 «Выражения подзапро- сов». Самостоятельно ознакомьтесь с этими предикатами и напишите несколь- ко запросов с их применением.
Предикаты ANY и ALL имеют некоторую связь с предикатом IN. В частности,
использование IN эквивалентно использованию конструкции = ANY, а исполь- зование NOT IN эквивалентно использованию конструкции <> ALL.
Пример двух эквивалентных запросов, выбирающих аэропорты в часовых поя- сах Asia/Novokuznetsk и Asia/Krasnoyarsk:
SELECT * FROM airports
WHERE timezone IN ( 'Asia/Novokuznetsk', 'Asia/Krasnoyarsk' );
SELECT * FROM airports
WHERE timezone = ANY (
VALUES ( 'Asia/Novokuznetsk' ), ( 'Asia/Krasnoyarsk' )
);
Еще один пример. В тексте главы мы рассматривали запрос, подсчитывающий количество маршрутов, проложенных из самых восточных аэропортов.
SELECT departure_city, count( * )
FROM routes
GROUP BY departure_city
HAVING departure_city IN (
SELECT city
FROM airports
WHERE longitude > 150
)
ORDER BY count DESC;
В этом запросе можно заменить IN на ANY таким образом:
HAVING departure_city = ANY ( ... )
205
Глава 6. Запросы
25.* При планировании новых маршрутов и оценке экономической эффективности уже существующих может потребоваться информация о том, какова усреднен- ная степень заполнения самолетов на всех направлениях.
Будем учитывать только уже прибывшие рейсы.
WITH tickets_seats
AS (
SELECT f.flight_id,
f.flight_no,
f.departure_city,
f.arrival_city,
f.aircraft_code,
count( tf.ticket_no ) AS fact_passengers,
( SELECT count( s.seat_no )
FROM seats s
WHERE s.aircraft_code = f.aircraft_code
) AS total_seats
FROM flights_v f
JOIN ticket_flights tf ON f.flight_id = tf.flight_id
WHERE f.status = 'Arrived'
GROUP BY 1, 2, 3, 4, 5
)
SELECT ts.departure_city,
ts.arrival_city,
sum( ts.fact_passengers ) AS sum_pass,
sum( ts.total_seats ) AS sum_seats,
round( sum( ts.fact_passengers )::numeric /
sum( ts.total_seats )::numeric, 2 ) AS frac
FROM tickets_seats ts
GROUP BY ts.departure_city, ts.arrival_city
ORDER BY ts.departure_city;
departure_city | arrival_city
| sum_pass | sum_seats | frac
----------------+-----------------+----------+-----------+------
Абакан
| Tomsk
|
258 |
360 | 0.72
Абакан
| Novosibirsk
|
217 |
348 | 0.62
Абакан
| Moscow
|
466 |
1044 | 0.45
Якутск
| Санкт-Петербург |
352 |
3596 | 0.10
(361 строка)
Для того чтобы лучше уяснить, как работает запрос в целом, вычлените из него отдельные подзапросы и выполните их, посмотрите, что они выводят.
206
Контрольные вопросы и задания
Как вы считаете, равносильно ли в данном запросе
SELECT count( s.seat_no )
и
SELECT count( s.* )
Почему?
Задание.
Модифицируйте этот запрос, чтобы он выводил те же отчетные дан- ные, но с учетом классов обслуживания, т. е. Business, Comfort и Economy.
26.* Предположим, что некая контролирующая организация потребовала информа- цию о размещении пассажиров одного из рейсов Кемерово — Москва в салоне самолета. Для определенности выберем конкретный рейс из тех рейсов, кото- рые уже прибыли на момент времени, соответствующий текущему моменту.
Текущий момент времени в базе данных «Авиаперевозки» определяется с по- мощью функции bookings.now.
Выполним запрос:
SELECT *
FROM flights_v
WHERE departure_city = 'Кемерово'
AND arrival_city = 'Москва'
AND actual_arrival < bookings.now();
Выберем для дальнейшей работы рейс, у которого значения атрибутов flight_id — 27584, aircraft_code — SU9.
Получим список пассажиров этого рейса с местами, которые им были назначе- ны в салоне самолета.
SELECT t.passenger_name, b.seat_no
FROM (
ticket_flights tf
JOIN tickets t ON tf.ticket_no = t.ticket_no
)
JOIN boarding_passes b
ON tf.ticket_no = b.ticket_no
AND tf.flight_id = b.flight_id
WHERE tf.flight_id = 27584
ORDER BY t.passenger_name;
207
Глава 6. Запросы
passenger_name
| seat_no
---------------------+---------
ALEKSANDR ABRAMOV
| 1A
ALEKSANDR GRIGOREV | 5C
ALEKSANDR SERGEEV
| 6F
ALEKSEY FEDOROV
| 11D
ALEKSEY MELNIKOV
| 18A
VLADIMIR POPOV
| 11A
YAROSLAV KUZMIN
| 18F
YURIY ZAKHAROV
| 10F
(44 строки)
Отсортируем строки по фамилиям пассажиров:
SELECT t.passenger_name,
substr(
t.passenger_name,
strpos( t.passenger_name, ' ' ) + 1
) AS lastname,
left(
t.passenger_name,
strpos( t.passenger_name, ' ' ) - 1
) AS firstname,
b.seat_no
FROM (
ticket_flights tf
JOIN tickets t ON tf.ticket_no = t.ticket_no
)
JOIN boarding_passes b
ON tf.ticket_no = b.ticket_no
AND tf.flight_id = b.flight_id
WHERE tf.flight_id = 27584
ORDER BY 2, 3;
passenger_name
| lastname | firstname | seat_no
---------------------+-----------+-----------+---------
ALEKSANDR ABRAMOV
| ABRAMOV
| ALEKSANDR | 1A
NIKITA ANDREEV
| ANDREEV
| NIKITA
| 6D
ANTONINA ANISIMOVA | ANISIMOVA | ANTONINA | 11F
YURIY ZAKHAROV
| ZAKHAROV | YURIY
| 10F
ELENA ZOTOVA
| ZOTOVA
| ELENA
| 20E
(44 строки)
208
Контрольные вопросы и задания
Получим список мест в салоне самолета и пассажиров, которые сидели на этих местах. При этом незанятые места также должны быть выведены (поэтому ис- пользуем левое внешнее соединение LEFT OUTER JOIN).
SELECT s.seat_no, p.passenger_name
FROM seats s
LEFT OUTER JOIN (
SELECT t.passenger_name, b.seat_no
FROM (
ticket_flights tf
JOIN tickets t ON tf.ticket_no = t.ticket_no
)
JOIN boarding_passes b
ON tf.ticket_no = b.ticket_no
AND tf.flight_id = b.flight_id
WHERE tf.flight_id = 27584
) AS p
ON s.seat_no = p.seat_no
WHERE s.aircraft_code = 'SU9'
ORDER BY s.seat_no;
seat_no | passenger_name
---------+---------------------
10A
|
10C
|
10D
| NATALYA POPOVA
10E
|
10F
| YURIY ZAKHAROV
11A
| VLADIMIR POPOV
11C
| ANNA KUZMINA
8F
|
9A
| MAKSIM CHERNOV
9C
|
9D
| LYUDMILA IVANOVA
9E
|
9F
| SOFIYA KULIKOVA
(97 строк)
Предположим, что нас попросили отсортировать места в порядке их расположе- ния в салоне самолета и вывести также адреса электронной почты пассажиров
(у кого они были указаны при бронировании). Для выполнения второго требова- ния воспользуемся столбцом contact_data. В нем содержатся JSON-объекты,
209
Глава 6. Запросы
содержащие контактные данные пассажиров. Ряд из них имеет ключ email. Мо- дифицированный запрос будет таким:
SELECT s.seat_no, p.passenger_name, p.email
FROM seats s
LEFT OUTER JOIN (
SELECT t.passenger_name, b.seat_no,
t.contact_data->'email' AS email
FROM (
ticket_flights tf
JOIN tickets t ON tf.ticket_no = t.ticket_no
)
JOIN boarding_passes b
ON tf.ticket_no = b.ticket_no
AND tf.flight_id = b.flight_id
WHERE tf.flight_id = 27584
) AS p
ON s.seat_no = p.seat_no
WHERE s.aircraft_code = 'SU9'
ORDER BY
left( s.seat_no, length( s.seat_no ) - 1 )::integer,
right( s.seat_no, 1 );
seat_no |
passenger_name | email
---------+-------------------+------------------------------------
1A
| ALEKSANDR ABRAMOV |
1C
|
|
1D
| DENIS PETROV
|
1F
| LEONID BARANOV
| "baranov.l.1967@postgrespro.ru"
2A
|
|
2C
|
|
9F
| SOFIYA KULIKOVA
| "sofiya.kulikova_041963@postgre..."
10A
|
|
10C
|
|
10D
| NATALYA POPOVA
| "popova.n_13031976@postgrespro.ru"
20E
| ELENA ZOTOVA
|
20F
| LILIYA OSIPOVA
|
(97 строк)
Задание.
Перепишите последний запрос с использованием общего табличного выражения и добавьте столбец «Класс обслуживания» (fare_conditions).
210
1 ... 8 9 10 11 12 13 14 15 ... 20