Главная страница

Учебнопрактическое пособие москва 2017 удк 004. 655 Ббк 32. 973. 26018. 2 М79 Моргунов, Е. П


Скачать 0.9 Mb.
НазваниеУчебнопрактическое пособие москва 2017 удк 004. 655 Ббк 32. 973. 26018. 2 М79 Моргунов, Е. П
Дата14.11.2022
Размер0.9 Mb.
Формат файлаpdf
Имя файла4_1_1_SQL_Postgres_Bazovy_kurs_2017.pdf
ТипУчебно-практическое пособие
#787182
страница11 из 28
1   ...   7   8   9   10   11   12   13   14   ...   28
\d flights_flight_id_seq
В базе данных есть еще одна таблица, не имеющая внешних ключей, — «Бронирова-
ния» (bookings)
. Это довольно простая таблица. В ней всего три атрибута. Атрибут
«Номер бронирования» (book_ref) является первичным ключом. Поскольку он пред- ставляет собой шестизначную комбинацию латинских букв и цифр, то в качестве ти- па данных для него выбран тип character (сокращенно — char). Для атрибута «Дата бронирования» (book_date) выбран тип данных timestamptz — временная отметка с часовым поясом, т. к. билеты могут приобретаться в городах, находящихся в различ- ных часовых поясах. В главе 4 мы уже говорили о том, что в случаях, требующих точных вычислений, необходимо использовать числа с фиксированной точностью.
Работа с денежными суммами как раз и является одним из таких случаев. Поэтому для атрибута «Полная сумма бронирования» (total_amount) выбирается тип данных numeric, при этом масштаб, т. е. число цифр справа от десятичной точки (запятой),
будет равен 2.
CREATE TABLE bookings
( book_ref
char( 6 )
NOT NULL,
-- Номер бронирования
book_date
timestamptz NOT NULL,
-- Дата бронирования
total_amount numeric( 10, 2 ) NOT NULL, -- Полная стоимость
-- бронирования
PRIMARY KEY ( book_ref )
);
С таблицей «Бронирования» (bookings) по внешнему ключу связана таблица «Би-
леты» (tickets)
. В качестве первичного ключа служит атрибут «Номер билета»
(ticket_no). Хотя уникальные тринадцатизначные номера билетов — числовые, но в них могут присутствовать лидирующие нули, поэтому числовой тип данных здесь не годится, а приходится использовать тип character (сокращенно — char). В каче- стве идентификатора пассажира будет использоваться номер документа, удостове- ряющего личность, а номера таких документов могут содержать, например, лиди- рующие нули, поэтому атрибут «Идентификатор пассажира» (passenger_id) будет не числовым, а символьным — varchar. Атрибут «Имя пассажира» (passenger_name) со- держит имя и фамилию пассажира, записанные заглавными латинскими буквами, а вот отчество не используется. Тип данных, конечно, text. Очень интересный атрибут
«Контактные данные пассажира» (contact_data). Его особенность в том, что эти дан- ные могут иметь некоторую структуру, но при этом создавать дополнительные атри- буты в таблице нецелесообразно. С такими данными — их называют полуструктури- рованными — PostgreSQL хорошо умеет работать: для них предусмотрены типы json и jsonb. В нашей таблице используется тип jsonb и вот почему: хотя ввод данных та- кого типа несколько замедляется из-за необходимости выполнения разбора данных,
84
но этот разбор выполняется однократно, только при вводе, а последующая обработ- ка уже разобранных данных ускоряется. Подробно типы json и jsonb рассмотрены в главе 4.
Внешним ключом будет атрибут «Номер бронирования» (book_ref), поскольку в рам- ках каждой процедуры бронирования может быть оформлено более одного билета.
CREATE TABLE tickets
( ticket_no
char( 13 ) NOT NULL,
-- Номер билета
book_ref
char( 6 ) NOT NULL,
-- Номер бронирования
passenger_id
varchar( 20 ) NOT NULL, -- Идентификатор пассажира
passenger_name text NOT NULL,
-- Имя пассажира
contact_data
jsonb,
-- Контактные данные пассажира
PRIMARY KEY ( ticket_no ),
FOREIGN KEY ( book_ref )
REFERENCES bookings ( book_ref )
);
Информация обо всех перелетах сохраняется в таблице «Перелеты» (ticket_flights).
Перелет — это перемещение конкретного пассажира из одного города в другой на конкретном авиарейсе. Перелеты вписываются в электронные билеты, при этом в каждый электронный билет может быть вписано более одного перелета. Поэтому первичным ключом будет комбинация двух атрибутов: «Номер билета» (ticket_no)
и «Идентификатор рейса» (flight_id). С каждым перелетом связан класс обслужива- ния, значения этого атрибута подлежат проверке с помощью ограничения CHECK.
Точно такое же ограничение есть и в таблице «Места» (seats), в которой каждому ме- сту в салоне конкретного типа самолета присваивается определенный класс обслу- живания. Атрибут «Стоимость перелета» (amount) требует использования типа дан- ных numeric, поскольку, как мы уже говорили ранее, денежные суммы должны запи- сываться с определенной точностью, а гарантировать ее может только тип данных numeric. Число цифр после запятой принимается равным двум.
Оба атрибута, составляющих первичный ключ, в свою очередь, сами являются внеш- ними ключами.
CREATE TABLE ticket_flights
( ticket_no
char( 13 ) NOT NULL,
-- Номер билета
flight_id
integer NOT NULL,
-- Идентификатор рейса
fare_conditions varchar( 10 ) NOT NULL,
-- Класс обслуживания
amount
numeric( 10, 2 ) NOT NULL, -- Стоимость перелета
CHECK ( amount >= 0 ),
CHECK ( fare_conditions IN ( 'Economy', 'Comfort', 'Business' ) ),
PRIMARY KEY ( ticket_no, flight_id ),
FOREIGN KEY ( flight_id )
REFERENCES flights ( flight_id ),
FOREIGN KEY ( ticket_no )
REFERENCES tickets ( ticket_no )
);
Последняя таблица нашей базы — это «Посадочные талоны» (boarding_passes). Все атрибуты, представленные в ней, за исключением атрибута «Номер посадочного та- лона» (boarding_no), вам уже известны из других таблиц. А номер посадочного тало- на — это просто целое число, порядковый номер пассажира при регистрации билетов на конкретный рейс, поэтому тип данных выбирается integer.
85

Обратите внимание, что эта таблица имеет связь с таблицей «Перелеты»
(ticket_flights) типа 1:1. Это объясняется тем, что пассажир, купивший билет на конкретный рейс, при регистрации получает только один посадочный талон. Конеч- но, если пассажир на регистрацию не явился, он не получает талона. Поэтому число строк в таблице «Посадочные талоны» может в общем случае оказаться меньше числа строк в таблице «Перелеты». Логично ожидать, что первичные ключи у этих двух таблиц будут одинаковыми: они включают атрибуты «Номер билета» (ticket_no)
и «Идентификатор рейса» (flight_id). Поскольку таблица «Перелеты» все же является главной в этой связке таблиц, то в таблице «Посадочные талоны» создается внешний ключ, ссылающийся на нее. А поскольку тип связи между таблицами — 1:1, то внешний ключ совпадает с первичным ключом.
Известно, что номер конкретного места в самолете пассажир получает при регистра- ции билета, а не при его бронировании, поэтому атрибут «Номер места» (seat_no) на- ходится в таблице «Посадочные талоны», а не в таблице «Перелеты». Нельзя допу- стить, чтобы на одно место в салоне были направлены два и более пассажиров, по- этому создается уникальный ключ с атрибутами «Идентификатор рейса» (flight_id) и
«Номер места» (seat_no). Еще один уникальный ключ призван гарантировать несов- падение номеров посадочных талонов на данном рейсе, он включает атрибуты
«Идентификатор рейса» (flight_id) и «Номер посадочного талона» (boarding_no).
CREATE TABLE boarding_passes
( ticket_no
char( 13 ) NOT NULL,
-- Номер билета
flight_id
integer NOT NULL,
-- Идентификатор рейса
boarding_no integer NOT NULL,
-- Номер посадочного талона
seat_no
varchar( 4 ) NOT NULL, -- Номер места
PRIMARY KEY ( ticket_no, flight_id ),
UNIQUE ( flight_id, boarding_no ),
UNIQUE ( flight_id, seat_no ),
FOREIGN KEY ( ticket_no, flight_id )
REFERENCES ticket_flights ( ticket_no, flight_id )
);
Вы можете, как и раньше, посмотреть описание таблицы:
\d boarding_passes
В процессе создания таблиц между ними образовывались связи за счет внешних клю- чей. Эти связи в описании таблицы можно увидеть, образно говоря, с двух сторон:
таблицы, на которые ссылается данная таблица, указываются во фразе «Ограничения внешнего ключа», а таблицы, которые ссылаются на данную таблицу, указываются во фразе «Ссылки извне». Например:
\d tickets
Ограничения внешнего ключа:
"tickets_book_ref_fkey" FOREIGN KEY (book_ref)
REFERENCES bookings(book_ref)
Ссылки извне:
TABLE "ticket_flights"
CONSTRAINT "ticket_flights_ticket_no_fkey"
FOREIGN KEY (ticket_no)
REFERENCES tickets(ticket_no)
86

Наше рассмотрение команд для определения данных было бы неполным без такой важной команды, как DROP TABLE. Поскольку у вас есть файл demo_small.sql, то вос- создать таблицы базы данных будет совсем нетрудно, поэтому вы можете смело вы- полнять команды удаления таблиц. Давайте сначала попытаемся удалить таблицу aircrafts:
DROP TABLE aircrafts;
Казалось бы, не должно быть никаких проблем, но в результате СУБД выдает сооб- щение об ошибке:
ОШИБКА: удалить объект таблица aircrafts нельзя, так как от него зависят другие объекты
,→
ПОДРОБНОСТИ: ограничение flights_aircraft_code_fkey в отношении таблица flights зависит от объекта таблица aircrafts
,→
ограничение seats_aircraft_code_fkey в отношении таблица seats зависит от объекта таблица aircrafts
,→
ПОДСКАЗКА: Для удаления зависимых объектов используйте DROP ... CASCADE.
Дело в том, что таблица «Самолеты» (aircrafts) является ссылочной для таблиц «Рей- сы» (flights) и «Места» (seats), что и отражено в сообщении. Выполнив команду
\d flights
мы увидим внешний ключ, ссылающийся на таблицу «Самолеты» (aircrafts). В сооб- щении содержится также и подсказка, рекомендующая в команду DROP TABLE доба- вить фразу CASCADE, означающую каскадное удаление зависимых объектов. Давайте так и сделаем:
DROP TABLE aircrafts CASCADE;
Теперь удаление таблицы прошло успешно, при этом из таблиц «Рейсы» (flights) и
«Места» (seats) были удалены внешние ключи, ссылающиеся на удаленную таблицу aircrafts. Вот это сообщение:
ЗАМЕЧАНИЕ: удаление распространяется на еще 2 объекта
ПОДРОБНОСТИ: удаление распространяется на объект ограничение flights_aircraft_code_fkey в отношении таблица flights удаление распространяется на объект ограничение seats_aircraft_code_fkey в отношении таблица seats
,→
,→
,→
DROP TABLE
Теперь внешних ключей, ссылающихся на таблицу aircrafts в таблицах flights и seats нет. Можно проверить это с помощью команд
\d flights
\d seats
А что если выполнить команду для удаления той же самой таблицы повторно?
DROP TABLE aircrafts CASCADE;
Ничего непоправимого не случится, просто СУБД выдаст сообщение об ошибке:
ОШИБКА: таблица "aircrafts" не существует
87

Однако бывают ситуации, когда заранее известно, что возможна попытка удаления несуществующей таблицы. В таких случаях обычно стараются избежать ненужных со- общений об ошибке отсутствия таблицы. Делается это путем добавления в команду
DROP TABLE фразы IF EXISTS. Например:
DROP TABLE IF EXISTS aircrafts CASCADE;
При использовании этой фразы в случае наличия интересующей нас таблицы выпол- няется ее удаление, в случае же ее отсутствия выводится замечание, а не ошибка, а также сообщение об успешном выполнении команды удаления таблицы:
ЗАМЕЧАНИЕ: таблица "aircrafts" не существует, пропускается
DROP TABLE
5.3 Модификация таблиц
Модифицировать таблицы приходится по различным причинам. Например, при необходимости добавить к какому-нибудь атрибуту ограничение DEFAULT, т. е. зна- чение «по умолчанию». Конечно, если в таблицах еще нет данных, то их можно просто пересоздать, внеся изменения в их определения. Но если таблицы содержат большое количество строк, то пересоздать их не всегда возможно, в этом случае на помощь приходит команда ALTER TABLE.
Эта команда очень многообразна и логична. Она предусматривает, наверное, все си- туации, которые могут возникнуть в реальной работе. Например, может возникнуть необходимость добавить новый столбец в таблицу — команда ALTER TABLE имеет для этого фразу ADD COLUMN. Возможна и обратная ситуация, когда нужно удалить стол- бец из таблицы — для этого есть фраза DROP COLUMN. Если нужно добавить ограни- чение, то помогут фразы ADD CHECK и ADD CONSTRAINT. Если потребовался внеш- ний ключ, то можно добавить и его.
В качестве объектов для экспериментов будем использовать таблицы базы данных
«Авиаперевозки».
Предположим, что нам понадобилось иметь в базе данных сведения о крейсер- ской скорости полета всех моделей самолетов, которые эксплуатируются в нашей авиакомпании. Следовательно, необходимо добавить столбец в таблицу «Самолеты»
(aircrafts). Дадим ему имя speed (наверное, можно предложить и более длинное имя —
cruise_speed). Тип данных для этого столбца выберем integer, добавим ограничение
NOT NULL. Наложим и ограничение на минимальное значение крейсерской скоро- сти, выраженное в километрах в час: CHECK( speed >= 300 ). В результате сформируем такую команду для добавления столбца:
ALTER TABLE airports
ADD COLUMN speed integer NOT NULL CHECK( speed >= 300 );
При попытке выполнить эту команду СУБД выдает сообщение об ошибке:
ОШИБКА: столбец "speed" содержит значения NULL
88

Как понимать это сообщение: кто виноват и что делать? Дело в том, что в таблице
«Самолеты» (aircrafts) уже есть строки. Однако во время добавления тех строк стол- бец speed в таблице не присутствовал, поэтому при его добавлении сейчас значение данного атрибута в этих строках будет отсутствовать, т. е. будет равно NULL. А мы наложили ограничение NOT NULL, следовательно, ранее добавленные строки не от- вечают новому ограничению. Как же можно выйти из этой ситуации? Один из ва- риантов такой: сначала добавить столбец, не накладывая на его значения никаких ограничений, затем ввести значения нового атрибута в уже существующие строки,
причем, эти значения должны удовлетворять тем ограничениям, которые мы соби- раемся наложить. После этого накладываем все необходимые ограничения. Получаем такую группу команд:
ALTER TABLE aircrafts ADD COLUMN speed integer;
UPDATE aircrafts SET speed = 807 WHERE aircraft_code = '733';
UPDATE aircrafts SET speed = 851 WHERE aircraft_code = '763';
UPDATE aircrafts SET speed = 905 WHERE aircraft_code = '773';
UPDATE aircrafts SET speed = 840
WHERE aircraft_code IN ( '319', '320', '321' );
UPDATE aircrafts SET speed = 786 WHERE aircraft_code = 'CR2';
UPDATE aircrafts SET speed = 341 WHERE aircraft_code = 'CN1';
UPDATE aircrafts SET speed = 830 WHERE aircraft_code = 'SU9';
SELECT * FROM aircrafts;
ALTER TABLE aircrafts ALTER COLUMN speed SET NOT NULL;
ALTER TABLE aircrafts ADD CHECK( speed >= 300 );
Проверьте, как изменилось определение таблицы, с помощью команды
\d aircrafts
Конечно, если необходимость наличия того или иного ограничения отпадет, его мож- но удалить:
ALTER TABLE aircrafts ALTER COLUMN speed DROP NOT NULL;
ALTER TABLE aircrafts DROP CONSTRAINT aircrafts_speed_check;
Обратите внимание, что для удаления ограничения CHECK нужно указать его имя,
которое можно выяснить с помощью команды
\d aircrafts
Если мы решим не усложнять нашу базу данных дополнительной информацией, то можем удалить и столбец. Конечно, вовсе не обязательно предварительно удалять ограничения, наложенные на этот столбец.
ALTER TABLE aircrafts DROP COLUMN speed;
Еще одна полезная возможность команды ALTER TABLE — изменение типа данных для какого-либо столбца. Давайте изменим тип данных для атрибутов «Координаты аэропорта: долгота» (longitude) и «Координаты аэропорта: широта» (latitude) с float
(double precision) на numeric(5, 2). Сделать это можно с помощью одной команды, по- скольку команда ALTER TABLE поддерживает и выполнение более одного действия за один раз.
89

Сначала посмотрим, с какой точностью выводятся значения этих атрибутов до изме- нения типа данных, затем изменим тип данных для двух столбцов, опять выведем содержимое таблицы на экран и убедимся, что значения были округлены в соответ- ствии с правилами округления.
SELECT * FROM airports;
ALTER TABLE airports
ALTER COLUMN longitude SET DATA TYPE numeric( 5,2 ),
ALTER COLUMN latitude SET DATA TYPE numeric( 5,2 );
SELECT * FROM airports;
В том случае, когда один тип данных изменяется на другой тип данных в преде- лах одной группы, например, оба типа — числовые, то проблем обычно не возни- кает. В только что рассмотренном примере исходный тип данных был float (double precision), а новый — numeric(5, 2), поэтому операция замены типа прошла автома- тически.
Однако если исходный и целевой типы данных относятся к разным группам, тогда потребуются некоторые дополнительные усилия с нашей стороны. В качестве при- мера рассмотрим следующую ситуацию. Предположим, что по результатам опытной эксплуатации базы данных «Авиаперевозки» мы пришли к выводу о том, что необхо- димо создать таблицу, содержащую коды и наименования классов обслуживания. На- зовем ее «Классы обслуживания» (fare_conditions). В ее состав включим два столбца:
«Код класса обслуживания» и «Наименование класса обслуживания». Имена столбцам присвоим с учетом принципов формирования имен аналогичных столбцов в других таблицах, например, в таблице «Аэропорты» (airports).
CREATE TABLE fare_conditions
( fare_conditions_code integer,
fare_conditions_name varchar( 10 ) NOT NULL,
PRIMARY KEY ( fare_conditions_code )
);
Добавим в новую таблицу необходимые данные:
INSERT INTO fare_conditions
VALUES ( 1, 'Economy' ),
( 2, 'Business' ),
( 3, 'Comfort' );
Поскольку мы ввели в обращение числовые коды для классов обслуживания, то необ- ходимо модифицировать определение таблицы «Места» (seats), а именно: тип дан- ных столбца «Класс обслуживания» (fare_conditions) изменить с varchar(10) на integer.
Для реализации такой задачи служит фраза USING команды ALTER TABLE. Однако та- кой вариант команды не сработает:
1   ...   7   8   9   10   11   12   13   14   ...   28


написать администратору сайта