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

Учебное пособие СанктПетербург бхвпетербург


Скачать 1.88 Mb.
НазваниеУчебное пособие СанктПетербург бхвпетербург
Дата12.02.2023
Размер1.88 Mb.
Формат файлаpdf
Имя файлаsql_primer.pdf
ТипУчебное пособие
#933464
страница8 из 20
1   ...   4   5   6   7   8   9   10   11   ...   20
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) требует использования типа данных numer- ic, поскольку, как мы уже говорили ранее, денежные суммы должны записываться с определенной точностью, а гарантировать ее может только тип данных numeric.
Число цифр после запятой принимается равным двум.
Оба атрибута, составляющих первичный ключ, в свою очередь, сами являются внеш- ними ключами.
113

Глава 5. Основы языка определения данных
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.
Обратите внимание, что эта таблица имеет связь типа 1:1 с таблицей «Перелеты». Это объясняется тем, что пассажир, купивший билет на конкретный рейс, при регистра- ции получает только один посадочный талон. Конечно, если пассажир не явился на регистрацию, он не получает талона. Поэтому число строк в таблице «Посадочные талоны» может в общем случае оказаться меньше числа строк в таблице «Переле- ты». Логично ожидать, что первичные ключи у этих двух таблиц будут одинаковы- ми: они включают атрибуты «Номер билета» (ticket_no) и «Идентификатор рейса»
(flight_id). Поскольку таблица «Перелеты» все же является главной в этой связке таблиц, то в таблице «Посадочные талоны» создается внешний ключ, ссылающийся на нее. А поскольку тип связи между таблицами — 1:1, то внешний ключ совпадает с первичным ключом.
Известно, что номер конкретного места в самолете пассажир получает при регистра- ции билета, а не при его бронировании, поэтому атрибут «Номер места» (seat_no)
находится в таблице «Посадочные талоны», а не в таблице «Перелеты».
Нельзя допустить, чтобы на одно место в салоне были направлены два и более пас- сажиров, поэтому создается уникальный ключ с атрибутами «Идентификатор рейса»
(flight_id) и «Номер места» (seat_no). Еще один уникальный ключ призван га- рантировать несовпадение номеров посадочных талонов на данном рейсе, он вклю- чает атрибуты «Идентификатор рейса» (flight_id) и «Номер посадочного талона»
(boarding_no).
114

5.2. Создание и удаление таблиц
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)
Наше рассмотрение команд для определения данных было бы неполным без такой важной команды, как DROP TABLE. Поскольку у вас есть файл demo_small.sql, то вос- создать таблицы базы данных будет совсем нетрудно, поэтому вы можете смело вы- полнять команды удаления таблиц.
Давайте сначала попытаемся удалить таблицу aircrafts:
DROP TABLE aircrafts;
115

Глава 5. Основы языка определения данных
Казалось бы, не должно быть никаких проблем, но в результате СУБД выдает сооб- щение об ошибке:
ОШИБКА: удалить объект таблица aircrafts нельзя, так как от него зависят другие объекты
ПОДРОБНОСТИ: ограничение flights_aircraft_code_fkey в отношении таблица flights зависит от объекта таблица aircrafts ограничение seats_aircraft_code_fkey в отношении таблица seats зависит от объекта таблица aircrafts
ПОДСКАЗКА: Для удаления зависимых объектов используйте DROP ... CASCADE.
Дело в том, что таблица «Самолеты» (aircrafts) является ссылочной для таблиц
«Рейсы» (flights) и «Места» (seats), что и отражено в этом сообщении. Выполнив команду
\d flights
мы увидим внешний ключ, ссылающийся на таблицу «Самолеты». В сообщении со- держится также и подсказка, рекомендующая в команду DROP TABLE добавить фразу
CASCADE, означающую каскадное удаление зависимых объектов. Давайте так и сде- лаем:
DROP TABLE aircrafts CASCADE;
Теперь удаление таблицы прошло успешно, при этом из таблиц «Рейсы» и «Места»
были удалены внешние ключи, ссылающиеся на удаленную таблицу 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;
116

5.3. Модификация таблиц
Ничего непоправимого не случится, просто СУБД выдаст сообщение об ошибке:
ОШИБКА: таблица "aircrafts" не существует
Однако бывают ситуации, когда заранее известно, что возможна попытка удаления несуществующей таблицы. В таких случаях обычно стараются избежать ненужных со- общений об ошибке отсутствия таблицы. Делается это путем добавления в команду
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. Если потребовался внешний ключ, то можно добавить и его.
В качестве объектов для экспериментов будем использовать таблицы базы данных
«Авиаперевозки».
Предположим, что нам понадобилось иметь в базе данных сведения о крейсер- ской скорости полета всех моделей самолетов, которые эксплуатируются в нашей авиакомпании. Следовательно, необходимо добавить столбец в таблицу «Самолеты»
117

Глава 5. Основы языка определения данных
(aircrafts). Дадим ему имя speed (наверное, можно предложить и более длинное имя — cruise_speed). Тип данных для этого столбца выберем integer, добавим ограничение NOT NULL. Наложим ограничение и на минимальное значение крейсер- ской скорости, выраженное в километрах в час: CHECK( speed >= 300 ). В результате сформируем такую команду для добавления столбца:
ALTER TABLE airports
ADD COLUMN speed integer NOT NULL CHECK( speed >= 300 );
При попытке выполнить эту команду СУБД выдает сообщение об ошибке:
ОШИБКА: столбец "speed" содержит значения NULL
Как понимать это сообщение: кто виноват и что делать? Дело в том, что в таблице
«Самолеты» уже есть строки. Однако во время добавления тех строк столбец 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
118

5.3. Модификация таблиц
Конечно, если необходимость наличия того или иного ограничения отпадет, его мож- но удалить:
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 поддерживает и выполнение более одного действия за один раз.
Сначала посмотрим, с какой точностью выводятся значения этих атрибутов до изме- нения типа данных, затем изменим тип данных для двух столбцов, опять выведем содержимое таблицы на экран и убедимся, что значения были округлены в соответ- ствии с правилами округления.
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), поэтому операция замены типа прошла ав- томатически.
119

Глава 5. Основы языка определения данных
Однако если исходный и целевой типы данных относятся к разным группам, тогда потребуются некоторые дополнительные усилия с нашей стороны. В качестве при- мера рассмотрим следующую ситуацию. Предположим, что по результатам опытной эксплуатации базы данных «Авиаперевозки» мы пришли к выводу о том, что необ- ходимо создать таблицу, содержащую коды и наименования классов обслуживания.
Назовем ее «Классы обслуживания» (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' );
Поскольку мы ввели в обращение числовые коды для классов обслуживания, то необходимо модифицировать определение таблицы «Места», а именно: тип дан- ных столбца «Класс обслуживания» (fare_conditions) изменить с varchar(10) на integer. Для реализации такой задачи служит фраза USING команды ALTER TABLE.
Однако такой вариант команды не сработает:
ALTER TABLE seats
ALTER COLUMN fare_conditions SET DATA TYPE integer
USING ( CASE WHEN fare_conditions = 'Economy' THEN 1
WHEN fare_conditions = 'Business' THEN 2
ELSE 3
END );
Для замены исходных значений на новые мы используем конструкцию CASE WHEN
... THEN ... ELSE ... END.
Выполнить операцию не удастся, СУБД выдаст сообщение об ошибке:
ОШИБКА: ограничение-проверку "seats_fare_conditions_check" нарушает некоторая строка
120

5.3. Модификация таблиц
И в самом деле, в определении таблицы «Места» есть ограничение CHECK, которое требует, чтобы значение столбца fare_conditions выбиралось из списка: Economy,
Comfort, Business. При замене символьных значений на числовые это ограничение будет заведомо нарушаться. Следовательно, необходимо в команду ALTER TABLE до- бавить операцию удаления этого ограничения. Пробуем новый вариант команды:
ALTER TABLE seats
DROP CONSTRAINT seats_fare_conditions_check,
ALTER COLUMN fare_conditions SET DATA TYPE integer
USING ( CASE WHEN fare_conditions = 'Economy' THEN 1
WHEN fare_conditions = 'Business' THEN 2
ELSE 3 END
);
Проверим результат работы с помощью команды
SELECT * FROM seats;
aircraft_code | seat_no | fare_conditions
---------------+---------+-----------------
319
| 2A
|
2 319
| 2C
|
2 319
| 2D
|
2
Теперь мы видим, что необходимо связать таблицы «Места» и «Классы обслужива- ния» по внешнему ключу. Сделаем это:
ALTER TABLE seats
ADD FOREIGN KEY ( fare_conditions )
REFERENCES fare_conditions ( fare_conditions_code );
Посмотрев описание таблицы «Места», увидим, что внешний ключ успешно создан.
\d seats
"seats_fare_conditions_fkey" FOREIGN KEY (fare_conditions)
REFERENCES fare_conditions(fare_conditions_code)
Из теории известно, что атрибуты внешнего ключа не обязательно должны ссылать- ся только на одноименные атрибуты ссылочной таблицы. Сейчас мы на практике успешно проверили это утверждение. Однако для удобства сопровождения базы дан- ных имеет смысл переименовать столбец fare_conditions в таблице «Места», т. е.
121

Глава 5. Основы языка определения данных
дать ему имя fare_conditions_code, поскольку в этой таблице хранится именно код класса обслуживания.
Давайте так и поступим:
ALTER TABLE seats
RENAME COLUMN fare_conditions TO fare_conditions_code;
Если теперь посмотреть описание таблицы, то можно заметить, что имя атрибута,
являющегося внешним ключом, изменилось, а вот имя ограничения осталось неиз- менным (seats_fare_conditions_fkey), хотя оно и было первоначально сформи- ровано самой СУБД. Это шаблонное имя ограничения составляется из имени таблицы и имени первого (и единственного в данном случае) атрибута внешнего ключа.
"seats_fare_conditions_fkey" FOREIGN KEY (fare_conditions_code)
REFERENCES fare_conditions(fare_conditions_code)
Давайте переименуем это ограничение, чтобы поддержать соблюдение правила име- нования ограничений:
1   ...   4   5   6   7   8   9   10   11   ...   20


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