Учебное пособие СанктПетербург бхвпетербург
Скачать 1.88 Mb.
|
Изменение данных Эта глава будет посвящена операциям изменения данных: вставке новых строк в таблицы, обновле- нию уже существующих строк и их удалению. С простыми приемами использования команд INSERT, UPDATE и DELETE, предназначенных для выполнения указанных операций, вы уже познакомились, поэтому мы расскажем о некоторых более интересных способах применения этих команд. 7.1. Вставка строк в таблицы Для работы нам потребуется создать еще две таблицы в базе данных «Авиаперевозки» (demo). Мы будем создавать их как временные таблицы, которые будут удаляться при отключении от базы данных. Использование временных таблиц позволит нам прово- дить эксперименты, будучи уверенными в том, что данные в постоянных таблицах модифицированы не будут, поэтому все запросы, которые вы выполняли ранее, бу- дут работать так, как и работали. Итак, создадим две копии таблицы «Самолеты» (aircrafts). Первая таблица-копия предназначена для хранения данных, взятых из таблицы-прототипа, а вторая табли- ца-копия будет использоваться в качестве журнальной таблицы: будем записывать в нее все операции, проведенные с первой таблицей. Создадим первую таблицу, причем копировать данные из постоянной таблицы air- crafts не будем, о чем говорит предложение WITH NO DATA. Если бы мы решили скопировать в новую таблицу и все строки, содержащиеся в таблице-прототипе, то- гда в команде CREATE TABLE мы могли бы использовать предложение WITH DATA или вообще не указывать его: по умолчанию строки копируются в создаваемую таблицу. CREATE TEMP TABLE aircrafts_tmp AS SELECT * FROM aircrafts WITH NO DATA; Наложим на таблицу необходимые ограничения: они не создаются при копировании таблицы. При массовом вводе данных гораздо более эффективным с точки зрения производительности было бы сначала добавить строки в таблицу, а уже потом накла- дывать ограничения на нее. Однако в нашем случае речь о массовом вводе не идет, 211 Глава 7. Изменение данных поэтому мы начнем с наложения ограничений, а уже потом добавим строки в таб- лицу. ALTER TABLE aircrafts_tmp ADD PRIMARY KEY ( aircraft_code ); ALTER TABLE aircrafts_tmp ADD UNIQUE ( model ); Теперь создадим вторую таблицу, и также не будем копировать в нее данные из по- стоянной таблицы aircrafts. CREATE TEMP TABLE aircrafts_log AS SELECT * FROM aircrafts WITH NO DATA; Ограничения в виде первичного и уникального ключей этой таблице не требуются, но потребуются еще два столбца: первый будет содержать дату/время выполнения операции над таблицей aircrafts_tmp, а второй — наименование этой операции (INSERT, UPDATE или DELETE). ALTER TABLE aircrafts_log ADD COLUMN when_add timestamp; ALTER TABLE aircrafts_log ADD COLUMN operation text; Поскольку в рассматриваемой ситуации копировать данные из постоянных таблиц во временные не требуется, то в качестве альтернативного способа создания временных таблиц можно было бы воспользоваться командой CREATE TEMP TABLE с предложе- нием LIKE. Например: CREATE TEMP TABLE aircrafts_tmp ( LIKE aircrafts INCLUDING CONSTRAINTS INCLUDING INDEXES ); Но так как уникального индекса по столбцу model в таблице aircrafts нет, то для временной таблицы его пришлось бы сформировать с помощью команды ALTER TABLE, как и при использовании первого способа ее создания. Добавим, что пред- ложение LIKE можно применять для создания не только временных таблиц, но и постоянных. Поскольку у нас есть журнальная таблица aircrafts_log, мы можем записывать в нее все операции с таблицей aircrafts_tmp, т. е. вести историю изменений данных таблицы aircrafts_tmp. 212 7.1. Вставка строк в таблицы Начнем работу с того, что скопируем в таблицу aircrafts_tmp все данные из табли- цы aircrafts. Для выполнения не только «полезной» работы, но и ведения журнала изменений мы используем команду INSERT с общим табличным выражением. Вообще, при классическом подходе для ведения учета изменений, внесенных в таб- лицы, используют триггеры или правила (rules), но их рассмотрение выходит за рам- ки этого пособия. Поэтому наш пример нужно рассматривать как иллюстрацию воз- можностей общих табличных выражений (CTE), а не как единственно верный подход. WITH add_row AS ( INSERT INTO aircrafts_tmp SELECT * FROM aircrafts RETURNING * ) INSERT INTO aircrafts_log SELECT add_row.aircraft_code, add_row.model, add_row.range, current_timestamp, 'INSERT' FROM add_row; INSERT 0 9 Давайте рассмотрим эту команду более подробно. Обратите внимание, что вся «по- лезная» работа выполняется в рамках конструкции WITH add_row AS (...). Здесь строки с помощью команды SELECT выбираются из таблицы aircrafts и вставля- ются в таблицу aircrafts_tmp. При вставке строк, выбранных из одной таблицы, в другую таблицу необходимо, чтобы число атрибутов и их типы данных во вставляе- мых строках были согласованы с числом столбцов и их типами данных в целевой таб- лице. Завершается конструкция WITH add_row AS (...) предложением RETURNING *, которое просто возвращает внешнему запросу все строки, успешно добавленные в таблицу aircrafts_tmp. Конечно же, при этом из таблицы aircrafts_tmp добав- ленные строки никуда не исчезают. Запрос получает имя add_row, на которое может ссылаться внешний запрос, когда он «хочет» обратиться к строкам, возвращенным с помощью предложения RETURNING *. Теперь обратимся к внешнему запросу. В нем также присутствует команда INSERT, которая получает данные для ввода в таблицу aircrafts_log от запроса SELECT. Этот запрос, в свою очередь, получает данные от временной таблицы add_row, указанной в предложении FROM. Поскольку в предложении RETURNING мы указали в качестве возвращаемого значения *, то будут возвращены все столбцы таблицы aircrafts_tmp, т. е. той таблицы, в которую строки были вставлены. Следователь- но, в команде SELECT внешнего запроса можно ссылаться на имена этих столбцов: SELECT add_row.aircraft_code, add_row.model, add_row.range, ... 213 Глава 7. Изменение данных Поскольку в таблице aircrafts_log существует еще два столбца, то для них мы до- полнительно передаем значения current_timestamp и 'INSERT'. Проверим, что получилось: SELECT * FROM aircrafts_tmp ORDER BY model; aircraft_code | model | range ---------------+---------------------+------- 319 | Airbus A319-100 | 6700 320 | Airbus A320-200 | 5700 321 | Airbus A321-200 | 5600 733 | Boeing 737-300 | 4200 763 | Boeing 767-300 | 7900 773 | Boeing 777-300 | 11100 CR2 | Bombardier CRJ-200 | 2700 CN1 | Cessna 208 Caravan | 1200 SU9 | Sukhoi SuperJet-100 | 3000 (9 строк) Проверим также и содержимое журнальной таблицы: SELECT * FROM aircrafts_log ORDER BY model; -[ RECORD 1 ]--+--------------------------- aircraft_code | 319 model | Airbus A319-100 range | 6700 when_add | 2017-01-31 18:28:49.230179 operation | INSERT -[ RECORD 2 ]--+--------------------------- aircraft_code | 320 model | Airbus A320-200 range | 5700 when_add | 2017-01-31 18:28:49.230179 operation | INSERT При вставке новых строк могут возникать ситуации, когда нарушается ограничение первичного или уникального ключей, поскольку вставляемые строки могут иметь значения ключевых атрибутов, совпадающие с теми, что уже имеются в таблице. Для таких случаев предусмотрено специальное средство — предложение ON CONFLICT, 214 7.1. Вставка строк в таблицы оно предусматривает два варианта действий на выбор программиста. Первый вари- ант — отменять добавление новой строки, для которой имеет место конфликт зна- чений ключевых атрибутов, и при этом не порождать сообщения об ошибке. Второй вариант заключается в замене операции добавления новой строки операцией обнов- ления существующей строки, с которой конфликтует добавляемая строка. Начнем с первого варианта. Попробуем добавить строку, которая гарантированно бу- дет конфликтовать с уже существующей строкой, причем как по первичному ключу aircraft_code, так и по уникальному ключу model. WITH add_row AS ( INSERT INTO aircrafts_tmp VALUES ( 'SU9', 'Sukhoi SuperJet-100', 3000 ) ON CONFLICT DO NOTHING RETURNING * ) INSERT INTO aircrafts_log SELECT add_row.aircraft_code, add_row.model, add_row.range, current_timestamp, 'INSERT' FROM add_row; Обратите внимание, что не будет выведено никаких сообщений об ошибках, как это и предполагалось. Строка добавлена не будет: INSERT 0 0 Нужно учитывать, что сообщение о нуле строк относится к таблице aircrafts_log, т. е. к команде в главном запросе, а не в общем табличном выражении, в котором мы работаем с таблицей aircrafts_tmp. Проверьте, не была ли добавлена строка в таблицу aircrafts_tmp. В том случае, когда в предложении ON CONFLICT не указана дополнительная инфор- мация об именах столбцов или ограничений, по которым предполагается возможный конфликт, проверка выполняется по первичному ключу и по уникальным ключам. Укажем конкретный столбец для проверки конфликтующих значений. Пусть это бу- дет aircraft_code, т. е. первичный ключ. Для упрощения команды не будем ис- пользовать общее табличное выражение. Добавляемая строка будет конфликтовать с существующей строкой как по столбцу aircraft_code, так и по столбцу model. INSERT INTO aircrafts_tmp VALUES ( 'SU9', 'Sukhoi SuperJet-100', 3000 ) ON CONFLICT ( aircraft_code ) DO NOTHING RETURNING *; 215 Глава 7. Изменение данных Получим только такое сообщение: aircraft_code | model | range ---------------+-------+------- (0 строк) INSERT 0 0 Сообщение было выведено потому, что в команду включено предложение RETURNING *. Сообщение о дублировании значений столбца model не выводится. Давайте в команде INSERT изменим значение столбца aircraft_code, чтобы оно стало уникальным: INSERT INTO aircrafts_tmp VALUES ( 'S99', 'Sukhoi SuperJet-100', 3000 ) ON CONFLICT ( aircraft_code ) DO NOTHING RETURNING *; Поскольку конфликта по столбцу aircraft_code нет, то далее проверяется выпол- нение требования уникальности по столбцу model. В результате мы получим тради- ционное сообщение об ошибке, относящееся к столбцу model: ОШИБКА: повторяющееся значение ключа нарушает ограничение уникальности "aircrafts_tmp_model_key" ПОДРОБНОСТИ: Ключ "(model)=(Sukhoi SuperJet-100)" уже существует. Теперь рассмотрим второй вариант обработки предложения ON CONFLICT, когда опе- рация вставки новой строки заменяется операцией обновления существующей стро- ки, с которой и возник конфликт значений столбцов. Для реализации этой возмож- ности служит предложение DO UPDATE. Давайте модифицируем команду и добавим предложение DO UPDATE. Выберем та- кую политику для работы с таблицей aircrafts_tmp: если при вставке новой строки имеет место дублирование по атрибутам первичного ключа со строкой, находящей- ся в таблице, тогда мы будем обновлять значения всех остальных атрибутов в этой строке, независимо от того, совпадают ли они со значениями в новой строке или нет. В качестве примера сделаем так: в добавляемой строке значение атрибута model сде- лаем отличающимся от того, которое уже есть в таблице (вместо Sukhoi SuperJet-100 будет Sukhoi SuperJet), а значение атрибута range оставим без изменений (3000). Внесем еще одно изменение: вместо имени столбца, образующего первичный ключ, с помощью предложения ON CONSTRAINT укажем наименование ограничения пер- вичного ключа. Вот так выглядит команда с предложением DO UPDATE: 216 7.1. Вставка строк в таблицы INSERT INTO aircrafts_tmp VALUES ( 'SU9', 'Sukhoi SuperJet', 3000 ) ON CONFLICT ON CONSTRAINT aircrafts_tmp_pkey DO UPDATE SET model = excluded.model, range = excluded.range RETURNING *; Поскольку мы включили в команду предложение RETURNING *, то СУБД сообщит о том, какие значения получат атрибуты обновленной строки. Как и планировалось, изменилось только значение атрибута model. aircraft_code | model | range ---------------+-----------------+------- SU9 | Sukhoi SuperJet | 3000 (1 строка) В случае конфликта по столбцу aircraft_code будет обновлена та строка в табли- це aircrafts_tmp, с которой конфликтовала вновь добавляемая строка. В резуль- тате новая строка добавлена не будет, а будет обновлено значение столбца model в строке, уже находящейся в таблице. А где PostgreSQL возьмет значение для ис- пользования в команде UPDATE? Это значение будет взято из специальной таблицы excluded, которая поддерживается самой СУБД. В этой таблице хранятся все строки, предлагаемые для вставки в рамках текущей команды INSERT. Вот это значение — excluded.model. Значение столбца range также будет обновлено, но его новое зна- чение — excluded.range — совпадает со старым. Обратите внимание, что в предложении DO UPDATE не указывается имя таблицы, т. к. таблица будет та же самая, которая указана в предложении INSERT. Предложение ON CONFLICT DO UPDATE гарантирует атомарное выполнение опера- ции вставки или обновления строк. Атомарность означает, что проверка наличия конфликта и последующее обновление выполняются как неделимая операция, т. е. другие транзакции не могут изменить значение столбца, вызывающее конфликт, так, чтобы в результате конфликт исчез и уже стало возможным выполнить операцию INSERT, а не UPDATE, или, наоборот, в случае отсутствия конфликта он вдруг появил- ся, и уже операция INSERT стала бы невозможной. Такая атомарная операция даже имеет название UPSERT — «UPDATE или INSERT». Для массового ввода строк в таблицы используется команда COPY. Эта команда может копировать данные из файла в таблицу. Причем, в качестве файла может служить и стандартный ввод. Хотя в этом разделе пособия мы, в основном, говорим о вставке строк в таблицы, но нужно сказать и о том, что эта команда может также копировать данные из таблиц в файлы и на стандартный вывод. 217 Глава 7. Изменение данных В качестве примера ввода данных из файла давайте добавим две строки в табли- цу aircrafts_tmp. Сначала необходимо подготовить текстовый файл, содержащий новые данные. В этом файле каждая строка соответствует одной строке таблицы. Зна- чения атрибутов разделяются символами табуляции, поэтому пробелы, которые есть в столбце model, можно вводить в файл без каких-либо дополнительных экранирую- щих символов. Заключать строковые значения в одинарные кавычки не нужно, ина- че они также будут введены в таблицу. Завершить файл нужно строкой, содержащей только символы «\.». Получим файл следующего содержания: IL9 Ilyushin IL96 9800 I93 Ilyushin IL96-300 9800 \. Теперь нужно ввести команду COPY, указав полный путь к вашему файлу: COPY aircrafts_tmp FROM '/home/postgres/aircrafts.txt'; В результате будет выведено сообщение об успешном добавлении двух строк: COPY 2 Давайте проверим, что получилось: SELECT * FROM aircrafts_tmp; Вы увидите, что новые строки были добавлены, но все те, что уже находились в таб- лице, удалены не были. При использовании команды COPY выполняются проверки всех ограничений, нало- женных на таблицу, поэтому ввести дублирующие данные не получится. Эту команду можно использовать и для вывода данных из таблицы в файл: COPY aircrafts_tmp TO '/home/postgres/aircrafts_tmp.txt' WITH ( FORMAT csv ); Предложение FORMAT csv говорит о том, что при выводе данных значения столбцов разделяются запятыми (CSV — Comma Separated Values). Получим файл такого вида: 773,Boeing 777-300,11100 763,Boeing 767-300,7900 SU9,Sukhoi SuperJet-100,3000 Если формат не указывать, то данные будут выведены с использованием символов табуляции в качестве разделителей значений атрибутов. 218 7.2. Обновление строк в таблицах 7.2. Обновление строк в таблицах Команда UPDATE предназначена для обновления данных в таблицах. Начнем с того, что покажем, как и при изучении команды INSERT, как можно организовать запись выполненных операций в журнальную таблицу. Эта команда аналогична команде, уже рассмотренной в предыдущем разделе. В ней также «полезная» работа выполня- ется в общем табличном выражении, а запись в журнальную таблицу — в основном запросе. WITH update_row AS ( UPDATE aircrafts_tmp SET range = range * 1.2 WHERE model '^Bom' RETURNING * ) INSERT INTO aircrafts_log SELECT ur.aircraft_code, ur.model, ur.range, current_timestamp, 'UPDATE' FROM update_row ur; Выполнив команду, в ответ получим сообщение INSERT 0 1 Напомним, что выведенное сообщение относится непосредственно к внешнему за- просу, в котором выполняется операция INSERT, добавляющая строку в журнальную таблицу. Конечно, если бы строка в таблице aircrafts_tmp не была успешно обнов- лена, тогда предложение RETURNING * не возвратило бы внешнему запросу ни одной строки, и, следовательно, тогда просто не было бы данных для формирования новой строки в таблице aircrafts_log. При использовании команды UPDATE в общем табличном выражении нужно учиты- вать, что главный запрос может получить доступ к обновленным данным только че- рез временную таблицу , которую формирует предложение RETURNING: ... FROM update_row ur; Можно выполнить выборку из журнальной таблицы aircrafts_log, чтобы посмот- реть — правда, не очень длинную — историю изменений строки с описанием само- лета Bombardier CRJ-200. 219 Глава 7. Изменение данных SELECT * FROM aircrafts_log WHERE model '^Bom' ORDER BY when_add; -[ RECORD 1 ]--+--------------------------- aircraft_code | CR2 model | Bombardier CRJ-200 range | 2700 when_add | 2017-02-05 00:27:38.591958 operation | INSERT -[ RECORD 2 ]--+--------------------------- aircraft_code | CR2 model | Bombardier CRJ-200 range | 3240 when_add | 2017-02-05 00:27:56.688933 operation | UPDATE Представим себе такую ситуацию: руководство компании хочет видеть динамику продаж билетов по всем направлениям, а именно: общее число проданных билетов и дату/время последнего увеличения их числа для конкретного направления. Создадим временную таблицу tickets_directions с четырьмя столбцами: – города отправления и прибытия — departure_city и arrival_city; – дата/время последнего увеличения числа проданных билетов — last_ticket_time; – число проданных билетов на этот момент времени по данному направлению — tickets_num. Создадим таблицу с помощью запроса к представлению «Маршруты» и заполним данными, однако в ней сначала будет только два первых столбца. CREATE TEMP TABLE tickets_directions AS SELECT DISTINCT departure_city, arrival_city FROM routes; Ключевое слово DISTINCT является здесь обязательным: ведь нам нужны только уни- кальные пары городов отправления и прибытия. Добавим еще два столбца и заполним столбец-счетчик нулевыми значениями. ALTER TABLE tickets_directions ADD COLUMN last_ticket_time timestamp; ALTER TABLE tickets_directions ADD COLUMN tickets_num integer DEFAULT 0; 220 7.2. Обновление строк в таблицах Поскольку PostgreSQL не требует обязательного создания первичного ключа, то не будем создавать его. Это не помешает нам однозначно идентифицировать строки в таблице tickets_directions. Поскольку в команде ALTER TABLE нет предложения WHERE, в котором было бы усло- вие, ограничивающее множество обновляемых строк, то будут обновлены все строки таблицы — во все будет записано значение 0 в столбец tickets_num. Для того чтобы не усложнять изложение материала, создадим временную таблицу, являющуюся аналогом таблицы «Перелеты», однако без внешних ключей. Поэтому мы сможем добавлять в нее строки, не заботясь о добавлении строк в таблицы «Биле- ты» и «Бронирования». Тем не менее первичный ключ все же создадим, чтобы проде- монстрировать, что в случае попытки ввода строк с дубликатными значениями пер- вичного ключа значения счетчиков в таблице tickets_directions наращиваться не будут. CREATE TEMP TABLE ticket_flights_tmp AS SELECT * FROM ticket_flights WITH NO DATA; ALTER TABLE ticket_flights_tmp ADD PRIMARY KEY ( ticket_no, flight_id ); Теперь представим команду, которая и будет добавлять новую запись о продаже би- лета и увеличивать в таблице tickets_directions значение счетчика проданных билетов. WITH sell_ticket AS ( INSERT INTO ticket_flights_tmp ( ticket_no, flight_id, fare_conditions, amount ) VALUES ( '1234567890123', 30829, 'Economy', 12800 ) RETURNING * ) UPDATE tickets_directions td SET last_ticket_time = current_timestamp, tickets_num = tickets_num + 1 WHERE ( td.departure_city, td.arrival_city ) = ( SELECT departure_city, arrival_city FROM flights_v WHERE flight_id = ( SELECT flight_id FROM sell_ticket ) ); UPDATE 1 221 Глава 7. Изменение данных Этот запрос работает следующим образом. Добавление новой записи о бронирова- нии авиаперелета производится в общем табличном выражении, а наращивание со- ответствующего счетчика — в главном запросе. Поскольку в общем табличном вы- ражении присутствует предложение RETURNING *, значения атрибутов добавлен- ной строки будут доступны в главном запросе посредством обращения к временной таблице sell_ticket. Конечно, если строка фактически не будет добавлена из-за дублирования значения первичного ключа, тогда будет сгенерировано сообщение об ошибке, в результате главный запрос выполнен не будет, следовательно, таблица tickets_directions не будет обновлена. В главном запросе мы обновляем всего два атрибута, причем значение атрибута tickets_num может увеличиться только на единицу, поскольку мы добавляем од- ну строку в таблицу ticket_flights_tmp. Остается выяснить, каким образом мож- но определить ту строку в таблице tickets_directions, атрибуты которой нужно обновить. Нам требуется на основе значения идентификатора рейса flight_id, на который был забронирован билет (перелет), определить города отправления и при- бытия, которые как раз и идентифицируют строку в таблице tickets_directions. Эти три атрибута присутствуют в представлении flights_v. Подзапрос обращается к этому представлению, а вложенный подзапрос возвращает значение идентифика- тора рейса flight_id, на который был забронирован билет (перелет). Назначение вложенного подзапроса в том, чтобы в условии WHERE flight_id = ... не дубли- ровать значение атрибута flight_id, использованное в команде INSERT (в данном примере это 30829). Тем самым должен быть снижен риск ошибки при вводе данных. Обратите внимание, что подзапрос в предложении WHERE возвращает два столбца, и сравнение выполняется также сразу с двумя столбцами. Посмотрим, что получилось: SELECT * FROM tickets_directions WHERE tickets_num > 0; -[ RECORD 1 ]-----+--------------------------- departure_city | Сочи arrival_city | Красноярск last_ticket_time | 2017-02-04 21:15:32.903687 tickets_num | 1 Представим другой вариант этой команды. Его принципиальное отличие от пер- вого варианта состоит в том, что для определения обновляемой строки в таблице 222 7.2. Обновление строк в таблицах tickets_directions используется операция соединения таблиц. Здесь в глав- ном запросе UPDATE присутствует предложение FROM, однако в этом предложе- нии указывается только представление flights_v, а таблицу tickets_directions в предложение FROM включать не нужно, хотя она и участвует в выполнении соедине- ния таблиц. Конечно, в предложении SET присваивать новые значения можно только атрибутам таблицы tickets_directions, поскольку именно она приведена в пред- ложении UPDATE. WITH sell_ticket AS ( INSERT INTO ticket_flights_tmp (ticket_no, flight_id, fare_conditions, amount ) VALUES ( '1234567890123', 7757, 'Economy', 3400 ) RETURNING * ) UPDATE tickets_directions td SET last_ticket_time = current_timestamp, tickets_num = tickets_num + 1 FROM flights_v f WHERE td.departure_city = f.departure_city AND td.arrival_city = f.arrival_city AND f.flight_id = ( SELECT flight_id FROM sell_ticket ); UPDATE 1 Посмотрим, что получилось: SELECT * FROM tickets_directions WHERE tickets_num > 0; --[ RECORD 1 ]----+--------------------------- departure_city | Сочи arrival_city | Красноярск last_ticket_time | 2017-02-04 21:15:32.903687 tickets_num | 1 --[ RECORD 2 ]----+--------------------------- departure_city | Москва arrival_city | Сочи last_ticket_time | 2017-02-04 21:18:40.353408 tickets_num | 1 Чтобы увидеть комбинированную строку, которая получилась при соединении таб- лиц tickets_directions и flights_v, можно включить в команду UPDATE пред- ложение RETURNING *. 223 Глава 7. Изменение данных 7.3. Удаление строк из таблиц Начнем рассмотрение команды DELETE, предназначенной для удаления данных из таблиц, с того, что, как и при изучении команды INSERT, покажем, как можно орга- низовать запись выполненных операций в журнальную таблицу. Эта команда анало- гична команде, уже рассмотренной в предыдущем разделе. В ней также «полезная» работа выполняется в общем табличном выражении, а запись в журнальную табли- цу — в основном запросе. WITH delete_row AS ( DELETE FROM aircrafts_tmp WHERE model '^Bom' RETURNING * ) INSERT INTO aircrafts_log SELECT dr.aircraft_code, dr.model, dr.range, current_timestamp, 'DELETE' FROM delete_row dr; Выполнив команду, в ответ получим сообщение INSERT 0 1 Напомним, что выведенное сообщение относится непосредственно к внешнему за- просу, в котором выполняется операция INSERT, добавляющая строку в журнальную таблицу. Посмотрим историю изменений строки с описанием самолета Bombardier CRJ-200: SELECT * FROM aircrafts_log WHERE model '^Bom' ORDER BY when_add; -[ RECORD 1 ]--+--------------------------- aircraft_code | CR2 model | Bombardier CRJ-200 range | 2700 when_add | 2017-02-05 00:27:38.591958 operation | INSERT -[ RECORD 2 ]--+--------------------------- aircraft_code | CR2 model | Bombardier CRJ-200 range | 3240 when_add | 2017-02-05 00:27:56.688933 operation | UPDATE 224 7.3. Удаление строк из таблиц -[ RECORD 3 ]--+--------------------------- aircraft_code | CR2 model | Bombardier CRJ-200 range | 3240 when_add | 2017-02-05 00:34:59.510911 operation | DELETE Для удаления конкретных строк из данной таблицы можно использовать информа- цию не только из нее, но также и из других таблиц. Выбирать строки для удаления можно двумя способами: использовать подзапросы к этим таблицам в предложении WHERE или указать дополнительные таблицы в предложении USING, а затем в пред- ложении WHERE записать условия соединения таблиц. Поскольку первый способ яв- ляется традиционным, то мы покажем второй из них. Предположим, что руководство авиакомпании решило удалить из парка самолетов машины компаний Boeing и Airbus, имеющие наименьшую дальность полета. Решим эту задачу следующим образом. В общем табличном выражении с помощью условия model '^Airbus' OR model '^Boeing' в предложении WHERE отберем модели только компаний Boeing и Airbus. Затем воспользуемся оконной функцией rank и произведем ранжирование моделей каждой компании по возрастанию даль- ности полета. Те модели, ранг которых окажется равным 1, будут иметь наименьшую дальность полета. В предложении USING сформируем соединение таблицы aircrafts_tmp с времен- ной таблицей min_ranges, а затем в предложении WHERE зададим условия для отбо- ра строк. WITH min_ranges AS ( SELECT aircraft_code, rank() OVER ( PARTITION BY left( model, 6 ) ORDER BY range ) AS rank FROM aircrafts_tmp WHERE model '^Airbus' OR model '^Boeing' ) DELETE FROM aircrafts_tmp a USING min_ranges mr WHERE a.aircraft_code = mr.aircraft_code AND mr.rank = 1 RETURNING *; 225 Глава 7. Изменение данных Мы включили в команду DELETE предложение RETURNING * для того, чтобы пока- зать, как выглядят комбинированные строки, сформированные с помощью предло- жения USING. Конечно, удаляются не они, а только оригинальные строки из таблицы aircrafts_tmp. aircraft_code | model | range | aircraft_code | rank ---------------+-----------------+-------+---------------+------ 321 | Airbus A321-200 | 5600 | 321 | 1 733 | Boeing 737-300 | 4200 | 733 | 1 (2 строки) В заключение этого раздела упомянем еще команду TRUNCATE, которая позволяет быстро удалить все строки из таблицы. Следующие две команды позволяют удалить все строки из таблицы aircrafts_tmp: DELETE FROM aircrafts_tmp; TRUNCATE aircrafts_tmp; Однако команда TRUNCATE работает быстрее. Контрольные вопросы и задания 1. Добавьте в определение таблицы aircrafts_log значение по умолчанию current_timestamp и соответствующим образом измените команды INSERT, приведенные в тексте главы. 2. В предложении RETURNING можно указывать не только символ «∗», означающий выбор всех столбцов таблицы, но и более сложные выражения, сформированные на основе этих столбцов. В тексте главы мы копировали содержимое таблицы «Самолеты» в таблицу aircrafts_tmp, используя в предложении RETURNING именно «∗». Однако возможен и другой вариант запроса: WITH add_row AS ( INSERT INTO aircrafts_tmp SELECT * FROM aircrafts RETURNING aircraft_code, model, range, current_timestamp, 'INSERT' ) INSERT INTO aircrafts_log SELECT ? FROM add_row; Что нужно написать в этом запросе вместо вопросительного знака? 226 Контрольные вопросы и задания 3. Если бы мы для копирования данных в таблицу aircrafts_tmp использовали команду INSERT без общего табличного выражения INSERT INTO aircrafts_tmp SELECT * FROM aircrafts; то в качестве выходного результата мы увидели бы сообщение INSERT 0 9 Как вы думаете, что будет выведено, если дополнить команду предложением RETURNING *? INSERT INTO aircrafts_tmp SELECT * FROM aircrafts RETURNING *; Проверьте ваши предположения на практике. Подумайте, каким образом мож- но использовать выведенный результат? 4. В тексте главы в предложениях ON CONFLICT команды INSERT мы использова- ли только выражения, состоящие из имени одного столбца. Однако в таблице «Места» (seats) первичный ключ является составным и включает два столбца. Напишите команду INSERT для вставки новой строки в эту таблицу и преду- смотрите возможный конфликт добавляемой строки со строкой, уже имеющей- ся в таблице. Сделайте два варианта предложения ON CONFLICT: первый — с ис- пользованием перечисления имен столбцов для проверки наличия дублирова- ния, второй — с использованием предложения ON CONSTRAINT. Для того чтобы не изменить содержимое таблицы «Места», создайте ее копию и выполняйте все эти эксперименты с таблицей-копией. 5. В предложении DO UPDATE команды INSERT может использоваться и условие WHERE. Самостоятельно ознакомьтесь с этой возможностью с помощью доку- ментации и напишите такую команду INSERT. 6. Команда COPY по умолчанию ожидает получения вводимых данных в формате text, когда значения данных разделяются символами табуляции. Однако мож- но представлять входные данные в формате CSV (Comma Separated Values), т. е. использовать в качестве разделителя запятую. COPY aircrafts_tmp FROM STDIN WITH ( FORMAT csv ); Вводите данные для копирования, разделяя строки переводом строки. Закончите ввод строкой '\.'. 227 Глава 7. Изменение данных IL9, Ilyushin IL96, 9800 I93, Ilyushin IL96-300, 9800 \. COPY 2 SELECT * FROM aircrafts_tmp; aircraft_code | model | range ---------------+---------------------+------- CN1 | Cessna 208 Caravan | 1200 CR2 | Bombardier CRJ-200 | 2700 IL9 | Ilyushin IL96 | 9800 I93 | Ilyushin IL96-300 | 9800 (11 строк) Как вы думаете, почему при выводе данных из таблицы вновь введенные зна- чения в столбце model оказались смещены вправо? 7. Команда COPY позволяет получить входные данные из файла и поместить их в таблицу. Этот файл должен быть доступен тому пользователю операцион- ной системы, от имени которого запущен серверный процесс, как правило, это пользователь postgres. Подготовьте файл, например, /home/postgres/aircrafts_tmp.csv, имеющий такую структуру: – каждая строка файла соответствует одной строке таблицы aircrafts_tmp; – значения данных в строке файла разделяются запятыми. Например: 773,Boeing 777-300,11100 763,Boeing 767-300,7900 SU9,Sukhoi SuperJet-100,3000 Введите в этот файл данные о нескольких самолетах, причем часть из них уже должна быть представлена в таблице, а часть — нет. Поскольку при выполнении команды COPY проверяются все ограничения це- лостности, наложенные на таблицу, то дублирующие строки добавлены, конеч- но же, не будут. А как вы думаете, строки, содержащиеся в этом же файле, но отсутствующие в таблице, будут добавлены или нет? 228 Контрольные вопросы и задания Проверьте свою гипотезу, выполнив вставку строк в таблицу из этого файла: COPY aircrafts_tmp FROM '/home/postgres/aircrafts_tmp.csv' WITH ( FORMAT csv ); 8.* В тексте главы был приведен запрос, предназначенный для учета числа биле- тов, проданных по всем направлениям на текущую дату. Однако тот запрос был рассчитан на одновременное добавление только одной записи в таблицу «Пере- леты» (ticket_flights_tmp). Ниже мы предложим более универсальный за- прос, который предусматривает возможность единовременного ввода несколь- ких записей о перелетах, выполняемых на различных рейсах. Для проверки работоспособности предлагаемого запроса выберем несколько рейсов по маршрутам: Красноярск — Москва, Москва — Сочи, Сочи — Москва, Сочи — Красноярск. Для определения идентификаторов рейсов сформируем вспомогательный запрос, в котором даты начала и конца рассматриваемого пе- риода времени зададим с помощью функции bookings.now. Использование этой функции необходимо, поскольку в будущих версиях базы данных могут быть представлены другие диапазоны дат. SELECT flight_no, flight_id, departure_city, arrival_city, scheduled_departure FROM flights_v WHERE scheduled_departure BETWEEN bookings.now() AND bookings.now() + INTERVAL '15 days' AND ( departure_city, arrival_city ) IN ( ( 'Красноярск', 'Москва' ), ( 'Москва', 'Сочи'), ( 'Сочи', 'Москва' ), ( 'Сочи', 'Красноярск' ) ) ORDER BY departure_city, arrival_city, scheduled_departure; Обратите внимание на предикат IN: в нем используются не индивидуальные значения, а пары значений. Предположим, что в течение указанного интервала времени пассажир плани- рует совершить перелеты по маршруту: Красноярск — Москва, Москва — Сочи, Сочи — Москва, Москва — Сочи, Сочи — Красноярск. Выполнив вспомогатель- ный запрос, выберем следующие идентификаторы рейсов (в этом же порядке): 13829, 4728, 30523, 7757, 30829. 229 Глава 7. Изменение данных WITH sell_tickets AS ( INSERT INTO ticket_flights_tmp ( ticket_no, flight_id, fare_conditions, amount ) VALUES ( '1234567890123', 13829, 'Economy', 10500 ), ( '1234567890123', 4728, 'Economy', 3400 ), ( '1234567890123', 30523, 'Economy', 3400 ), ( '1234567890123', 7757, 'Economy', 3400 ), ( '1234567890123', 30829, 'Economy', 12800 ) RETURNING * ) UPDATE tickets_directions td SET last_ticket_time = current_timestamp, tickets_num = tickets_num + ( SELECT count( * ) FROM sell_tickets st, flights_v f WHERE st.flight_id = f.flight_id AND f.departure_city = td.departure_city AND f.arrival_city = td.arrival_city ) WHERE ( td.departure_city, td.arrival_city ) IN ( SELECT departure_city, arrival_city FROM flights_v WHERE flight_id IN ( SELECT flight_id FROM sell_tickets ) ); UPDATE 4 В этой версии запроса предусмотрен единовременный ввод нескольких строк в таблицу ticket_flights_tmp, причем перелеты могут выполняться на раз- личных рейсах. Поэтому необходимо преобразовать список идентификаторов этих рейсов в множество пар «город отправления — город прибытия», посколь- ку именно для таких пар и ведется подсчет числа забронированных перелетов. Эта задача решается в предложении WHERE, где вложенный подзапрос форми- рует список идентификаторов рейсов, а внешний подзапрос преобразует этот список в множество пар «город отправления — город прибытия». Затем с помо- щью предиката IN производится отбор строк таблицы tickets_directions для обновления. Теперь обратимся к предложению SET. Подзапрос с функцией count вычисляет количество перелетов по каждому направлению. Это коррелированный подза- прос: он выполняется для каждой строки, отобранной в предложении WHERE. В нем используется соединение временной таблицы sell_tickets с представ- лением flights_v. Это нужно для того, чтобы подсчитать все перелеты, соот- 230 Контрольные вопросы и задания ветствующие паре атрибутов «город отправления — город прибытия», взятых из текущей обновляемой строки таблицы tickets_directions. Этот подза- прос позволяет учесть такой факт: рейсы могут иметь различные идентифика- торы flight_id, но при этом соответствовать одному и тому же направлению, а в таблице tickets_directions учитываются именно направления. В случае попытки повторного бронирования одного и того же перелета для дан- ного пассажира, т. е. ввода строки с дубликатом первичного ключа, такая строка будет отвергнута, и будет сгенерировано сообщение об ошибке. В таком случае и таблица tickets_directions не будет обновлена. Давайте посмотрим, что изменилось в таблице tickets_directions. SELECT departure_city AS dep_city, arrival_city AS arr_city, last_ticket_time, tickets_num AS num FROM tickets_directions WHERE tickets_num > 0 ORDER BY departure_city, arrival_city; По маршруту Москва — Сочи наш пассажир приобретал два билета, что и отра- жено в выборке. dep_city | arr_city | last_ticket_time | num ------------+------------+----------------------------+----- Красноярск | Москва | 2017-02-04 14:02:23.769443 | 1 Москва | Сочи | 2017-02-04 14:02:23.769443 | 2 Сочи | Красноярск | 2017-02-04 14:02:23.769443 | 1 Сочи | Москва | 2017-02-04 14:02:23.769443 | 1 (4 строки) А это информация о каждом перелете, забронированном нашим пассажиром: SELECT * FROM ticket_flights_tmp; ticket_no | flight_id | fare_conditions | amount ---------------+-----------+-----------------+---------- 1234567890123 | 13829 | Economy | 10500.00 1234567890123 | 4728 | Economy | 3400.00 1234567890123 | 30523 | Economy | 3400.00 1234567890123 | 7757 | Economy | 3400.00 1234567890123 | 30829 | Economy | 12800.00 (5 строк) 231 Глава 7. Изменение данных Задание. Модифицируйте запрос и таблицу tickets_directions так, чтобы учет числа забронированных перелетов по различным маршрутам выполнялся для каждого класса обслуживания: Economy, Business и Comfort. 9.* Предположим, что руководство нашей авиакомпании решило отказаться от ис- пользования самолетов компаний Boeing и Airbus, имеющих наименьшее ко- личество пассажирских мест в салонах. Мы должны соответствующим образом откорректировать таблицу «Самолеты» (aircrafts_tmp). Мы предлагаем такой алгоритм. Шаг 1. Для каждой модели вычислить общее число мест в салоне. Шаг 2. Используя оконную функцию rank, присвоить моделям ранги на основе числа мест (упорядочив их по возрастанию числа мест). Ранжирование выпол- няется в пределах каждой компании-производителя, т. е. для Boeing и для Airbus — отдельно. Ранг, равный 1, соответствует наименьшему числу мест. Шаг 3. Выполнить удаление тех строк из таблицы aircrafts_tmp, которые удо- влетворяют следующим требованиям: модель — Boeing или Airbus, а число мест в салоне — минимальное из всех моделей данной компании-производителя, т. е. модель имеет ранг, равный 1. WITH aicrafts_seats AS ( SELECT aircraft_code, model, seats_num, rank() OVER ( PARTITION BY left( model, strpos( model, ' ' ) - 1 ) ORDER BY seats_num ) FROM ( SELECT a.aircraft_code, a.model, count( * ) AS seats_num FROM aircrafts_tmp a, seats s WHERE a.aircraft_code = s.aircraft_code GROUP BY 1, 2 ) AS seats_numbers ) DELETE FROM aircrafts_tmp a USING aicrafts_seats a_s WHERE a.aircraft_code = a_s.aircraft_code AND left( a.model, strpos( a.model, ' ' ) - 1 ) IN ( 'Boeing', 'Airbus' ) AND a_s.rank = 1 RETURNING *; 232 Контрольные вопросы и задания Шаг 1 выполняется в подзапросе в предложении WITH. Шаг 2 — в главном запро- се в предложении WITH. Шаг 3 реализуется командой DELETE. Обратите внимание, что название компании-производителя мы определяем путем взятия подстроки от значения атрибута model: от начала строки до про- бельного символа (используем функции left и strpos). Мы включили предло- жение RETURNING *, чтобы увидеть, какие именно модели были удалены. Предложение WITH выдает такой результат: aircraft_code | model | seats_num | rank ---------------+---------------------+-----------+------ 319 | Airbus A319-100 | 116 | 1 320 | Airbus A320-200 | 140 | 2 321 | Airbus A321-200 | 170 | 3 733 | Boeing 737-300 | 130 | 1 763 | Boeing 767-300 | 222 | 2 773 | Boeing 777-300 | 402 | 3 CR2 | Bombardier CRJ-200 | 50 | 1 CN1 | Cessna 208 Caravan | 12 | 1 SU9 | Sukhoi SuperJet-100 | 97 | 1 (9 строк) Очевидно, что должны быть удалены модели с кодами 319 и 733. После выполнения запроса получим (это работает предложение RETURNING *): -[ RECORD 1 ]--+---------------- aircraft_code | 319 model | Airbus A319-100 range | 6700 aircraft_code | 319 model | Airbus A319-100 seats_num | 116 rank | 1 -[ RECORD 2 ]--+---------------- aircraft_code | 733 model | Boeing 737-300 range | 4200 aircraft_code | 733 model | Boeing 737-300 seats_num | 130 rank | 1 DELETE 2 233 Глава 7. Изменение данных Обратите внимание, что в результате были выведены комбинированные стро- ки, полученные при соединении таблицы aircrafts_tmp с временной табли- цей aicrafts_seats, указанной в предложении USING. Но удалены были, ко- нечно, строки из таблицы aircrafts_tmp. Задание. Предложите другой вариант решения этой задачи. Например, можно поступить так: оставить предложение WITH без изменений, из команды DELETE убрать предложение USING, а в предложении WHERE вместо соединения таблиц использовать подзапрос с предикатом IN для получения списка кодов удаляе- мых моделей самолетов. Еще один вариант решения задачи связан с использованием представлений, ко- торые мы рассматривали в главе 5. Можно создать представление на основе таблиц «Самолеты» (aircrafts) и «Места» (seats) и перенести конструкцию с функциями left и strpos в представление. В нем будут вычисляемые столб- цы: company — «Компания-производитель самолетов» и seats_num — «Число мест». CREATE VIEW aircrafts_seats AS ( SELECT a.aircraft_code, a.model, left( a.model, strpos( a.model, ' ' ) - 1 ) AS company, count( * ) AS seats_num FROM aircrafts a, seats s WHERE a.aircraft_code = s.aircraft_code GROUP BY 1, 2, 3 ); Имея это представление, можно использовать его в конструкции WITH. При этом вызов функции rank может упроститься: rank() OVER ( PARTITION BY company ORDER BY seats_num ) Для выбора удаляемых строк в команде DELETE можно использовать, например, подзапрос в предикате IN. При этом не забывайте, что значение столбца rank для них будет равно 1. Еще одна идея: для выбора минимальных значений числа мест в самолетах можно попытаться в качестве замены оконной функции rank использовать предложения LIMIT 1 и ORDER BY. В таком случае не потребуется также и функ- ция min. 234 Контрольные вопросы и задания 10.* В реальной работе иногда возникают ситуации, когда требуется быстро за- полнить таблицу тестовыми данными. В таком случае удобно воспользоваться командой INSERT с подзапросом. Конечно, число атрибутов и их типы данных в подзапросе SELECT должны быть такими, какие ожидает получить команда INSERT. Продемонстрируем такой прием на примере таблицы «Места» (seats). Для того чтобы выполнить команду, приведенную в этом упражнении, нужно либо сна- чала удалить все строки из таблицы seats, чтобы можно было добавлять строки в эту таблицу DELETE FROM seats; либо создать копию этой таблицы CREATE TABLE seats_tmp AS SELECT * FROM seats; чтобы работать с копией. Итак, как сформировать тестовые данные автоматическим способом? Для этого сначала нужно подготовить исходные данные, на основе которых и будут фор- мироваться результирующие значения для вставки в таблицу «Места». В рамках реляционной модели наиболее естественным будет представление ис- ходных данных в виде таблиц. Для формирования каждой строки таблицы «Ме- ста» нужно задать код модели самолета, класс обслуживания и номер места, который состоит из двух компонентов: номера ряда и буквенного идентифи- катора позиции в ряду. Поскольку размеры и компоновки салонов различаются, необходимо для каж- дой модели указать предельное число рядов кресел в салонах бизнес-класса и экономического класса, а также число кресел в каждом ряду. Это число можно задать с помощью указания буквенного идентификатора для самого последне- го кресла в ряду. Например, если в ряду всего шесть кресел, тогда их буквенные обозначения будут такими: A, B, C, D, E, F. Таким образом, последней будет бук- ва F. В салоне бизнес-класса число мест в ряду меньше, чем в салоне экономи- ческого класса, но для упрощения задачи примем эти числа одинаковыми. В результате получим первую исходную таблицу с атрибутами: – код модели самолета; – номер последнего ряда кресел в салоне бизнес-класса; 235 Глава 7. Изменение данных – номер последнего ряда кресел в салоне экономического класса; – буква, обозначающая позицию последнего кресла в ряду. Классы обслуживания также поместим в отдельную таблицу. В ней будет всего один атрибут — класс обслуживания. Список номеров рядов также поместим в отдельную таблицу. В ней будет также всего один атрибут — номер ряда. Так же поступим и с буквенными обозначениями кресел в ряду. В этой таблице будет один атрибут — латинская буква, обозначающая позицию кресла. В принципе можно было бы создать все четыре таблицы с помощью команды CREATE TABLE и ввести в них исходные данные, а затем использовать эти таб- лицы в команде SELECT. Но команда SELECT позволяет использовать в предло- жении FROM виртуальные таблицы, которые можно создавать с помощью пред- ложения VALUES. Для этого непосредственно в текст команды записываются группы значений, представляющие собой строки такой виртуальной таблицы. Каждая такая строка заключается в круглые скобки. Вся эта конструкция получа- ет имя таблицы, и к ней прилагается список атрибутов. Это выглядит, например, следующим образом: FROM ( VALUES ( 'SU9', 3, 20, 'F' ), ( '773', 5, 30, 'I' ), ( '763', 4, 25, 'H' ), ( '733', 3, 20, 'F' ), ( '320', 5, 25, 'F' ), ( '321', 4, 20, 'F' ), ( '319', 3, 20, 'F' ), ( 'CN1', 0, 10, 'B' ), ( 'CR2', 2, 15, 'D' ) ) AS aircraft_info ( aircraft_code, max_seat_row_business, max_seat_row_economy, max_letter ) Здесь aircraft_info определяет имя виртуальной таблицы, а список иденти- фикаторов — имена ее атрибутов (aircraft_code, max_seat_row_business, max_seat_row_economy, max_letter). Эти атрибуты можно использовать во всех частях команды SELECT, как если бы это были атрибуты обычной таблицы. 236 Контрольные вопросы и задания Остальные виртуальные таблицы создаются аналогичным способом. Для соединения таблиц используется ключевое слово CROSS JOIN, хотя в дан- ном случае вместо этого можно было просто поставить запятые. Как это и бывает всегда, четыре таблицы образуют декартово произведение из своих строк, а затем на основе условия WHERE «лишние» строки отбрасываются. В этом условии используется условный оператор CASE. Он позволяет нам поста- вить допустимый номер ряда в зависимость от класса обслуживания: WHERE CASE WHEN fare_condition = 'Business' THEN seat_row::integer <= max_seat_row_business WHEN fare_condition = 'Economy' THEN seat_row::integer > max_seat_row_business AND seat_row::integer <= max_seat_row_economy В этом выражении используется приведение типов: seat_row::integer. Эта операция необходима, т. к. в виртуальной таблице номера рядов представлены в виде символьных строк, а для выполнения сравнения числовых значений в данной ситуации нужен целый тип. При написании условного оператора нужно учесть, что в виртуальной таблице мы указали не количество рядов в бизнес- классе и экономическом классе, а номера последних рядов в этих классах. По- этому возникает конструкция THEN seat_row::integer > max_seat_row_business AND seat_row::integer <= max_seat_row_economy Также проверяем еще одно условие, сравнивая символьные строки: AND letter <= max_letter; Последний этап в работе оператора SELECT — это формирование списка выра- жений, которые будут выведены в качестве итоговых данных. Для формирова- ния номера места используется операция конкатенации ||, которая соединяет номер ряда с буквенным обозначением позиции в ряду. SELECT aircraft_code, seat_row || letter, fare_condition Итак, SQL-команда, которая позволит за одну операцию ввести в таблицу «Ме- ста» сразу необходимое число строк, выглядит так: 237 Глава 7. Изменение данных INSERT INTO seats ( aircraft_code, seat_no, fare_conditions ) SELECT aircraft_code, seat_row || letter, fare_condition FROM -- компоновки салонов ( VALUES ( 'SU9', 3, 20, 'F' ), ( '773', 5, 30, 'I' ), ( '763', 4, 25, 'H' ), ( '733', 3, 20, 'F' ), ( '320', 5, 25, 'F' ), ( '321', 4, 20, 'F' ), ( '319', 3, 20, 'F' ), ( 'CN1', 0, 10, 'B' ), ( 'CR2', 2, 15, 'D' ) ) AS aircraft_info ( aircraft_code, max_seat_row_business, max_seat_row_economy, max_letter ) CROSS JOIN -- классы обслуживания ( VALUES ( 'Business' ), ( 'Economy' ) ) AS fare_conditions ( fare_condition ) CROSS JOIN -- список номеров рядов кресел ( VALUES ( '1' ), ( '2' ), ( '3' ), ( '4' ), ( '5' ), ( '6' ), ( '7' ), ( '8' ), ( '9' ), ( '10' ), ( '11' ), ( '12' ), ( '13' ), ( '14' ), ( '15' ), ( '16' ), ( '17' ), ( '18' ), ( '19' ), ( '20' ), ( '21' ), ( '22' ), ( '23' ), ( '24' ), ( '25' ), ( '26' ), ( '27' ), ( '28' ), ( '29' ), ( '30' ) ) AS seat_rows ( seat_row ) CROSS JOIN -- список номеров (позиций) кресел в ряду ( VALUES ( 'A' ), ( 'B' ), ( 'C' ), ( 'D' ), ( 'E' ), ( 'F' ), ( 'G' ), ( 'H' ), ( 'I' ) ) AS letters ( letter ) WHERE CASE WHEN fare_condition = 'Business' THEN seat_row::integer <= max_seat_row_business WHEN fare_condition = 'Economy' THEN seat_row::integer > max_seat_row_business AND seat_row::integer <= max_seat_row_economy END AND letter <= max_letter; 238 Контрольные вопросы и задания Задание. Модифицируйте команду с учетом того, что в салоне бизнес-класса число мест в ряду должно быть меньше, чем в салоне экономического класса (в приведенном решении мы для упрощения задачи принимали эти числа оди- наковыми). Попробуйте упростить подзапрос, отвечающий за формирование списка номе- ров рядов кресел: ( VALUES ( '1' ), ( '2' ), ( '3' ), ( '4' ), ( '5' ), ... Воспользуйтесь функцией generate_series, описанной в разделе документации 9.24 «Функции, возвращающие множества». 239 |