МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ INTERNATIONAL BANKING INSTITUTE Тема 4. Операторы манипулирования данными языка SQL Цель: изучить операторы изменения данных в реляционных базах данных, которые составляют подъязык манипулирования данными языка SQL (DML Data manipulation Language) в рамках существующих стандартов. Задачи: изучить синтаксис операторов манипулирования данными в SQL; научиться писать произвольные запросы изменения данных к БД; изучить ограничения, которые накладываются на возможность выполнения операций модификации данных; получить представление о графическом языке запросов QBE (Query By Example) и сравнить его с возможностями стандартного SQL. Оглавление 4.1. Оператор ввода данных INSERT ........................................................................... 1 4.2. Оператор удаления данных DELETE .................................................................... 3 4.3. Оператор обновления данных UPDATE ............................................................... 5 Вопросы для самопроверки ........................................................................................ 9 4.1. Оператор ввода данных INSERT SQL является полноценным языком, предназначенным для работы с базами данных, поэтому в отличие от реляционной алгебры кроме запросов к содержимому БД на этом языке можно выполнять операции изменения содержимого таблиц БД и даже изменять схему БД, т. е. набор таблиц, их атрибутов и связей между ними. Данная глава посвящена операторам манипулирования данными: эти операторы позволяют только изменять содержимое таблиц, входящих в БД. В операции манипулирования данными входят три операции: операция удаления записей — ей соответствует оператор DELETE, операция добавления или ввода новых записей — ей соответствует оператор INSERT и операция изменения (обновления записей) — ей соответствует оператор UPDATE. Рассмотрим каждый из операторов подробнее. Следует отметить, что все операторы изменить данные только в одной таблице. манипулирования данными позволяют Оператор ввода данных INSERT имеет следующий синтаксис: INSERT INTO имя_таблицы [(<список столбцов>) ] VALUES (<список значений>) Подобный синтаксис позволяет ввести только одну строку в таблицу. Список столбцов является необязательным параметром в данном операторе. Можно не задавать список столбцов в том случае, если вводимая строка содержит полный перечень значений столбцов таблицы, и они приводятся в том порядке, как они заданы при создании таблицы. Например, рассмотрим БД «Библиотека». В данной БД есть таблица, которая хранит каталог всех книг, которые присутствуют в нашей библиотеке. Внешне таблица выглядит следующим образом: 1 МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ INTERNATIONAL BANKING INSTITUTE BOOKS ISBN Title Publishing _ house Уникальный Название Издательство идентификатор книги книги Year _ of _ the _ edition Год издания Pages Количество страниц Во второй строке таблицы приведена расшифровка значения имен атрибутов (столбцов таблицы). Почему же у нас в таблице не присутствует столбец с фамилией автора книги? Следует отметить, что авторов у книги может быть несколько, поэтому в таблицу книги они попасть не могут — это по определению реляционное отношение, которому соответствует наша таблица (см. тему 2), и, кроме того, есть книги, у которых не указываются авторы — это энциклопедии, справочники, сборники трудов. Введем новую книгу в таблицу BOOKS: INSERT INTO BOOKS (ISBN,TITLE, Publishing_house, Year_of_the_edition, PAGES) V ALUES (’5-88782-290-2’, ’ Аппаратные средства IBM PC, Энциклопедия ’, ’Питер’,2003,816) Так как мы вводим полную строку, то мы можем не задавать список столбцов, ограничиться только заданием перечня значений, в этом случае оператор ввода будет выглядеть следующим образом: INSERT INTO BOOKS VALUES (’ PC. Энциклопедия’, ’Питер’,2003,816) 5-88782-290-2 ’, ’Аппаратные средства IBM Результаты работы обоих операторов одинаковы. Наконец, мы можем ввести неполный перечень значений, т. е. не вводить, например, количество страниц, если мы его не знаем. Но в этом случае мы должны задать список вводимых столбцов, тогда оператор ввода будет выглядеть следующим образом: INSERT INTO BOOKS (ISBN,TITLE, Publishing_house, Year_of_the_edition) V ALUES (’588782-290-2’, ’ Аппаратные средства IBM P. Энциклопедия ’, ’ Питер ’, 2003) Столбцу PAGES будет присвоено в этом случае значение NULL (не определено). Какие столбцы должны быть заданы при вводе данных? Это определяется тем, как описаны эти столбцы при описании соответствующей таблицы, и будет рассмотрено более подробно при описании языка DDL (Data Definition Language) в теме 5. Здесь мы пока отметим, что если столбец или атрибут имеет признак обязательный (NOT NULL) при описании таблицы, то оператор INSERT должен обязательно содержать данные для ввода в каждую строку данного столбца. Поэтому если в таблице все столбцы обязательные, то каждая вводимая строка должна содержать полный перечень вводимых значений, а указание имен столбцов в этом случае не обязательно. В противном случае, если имеется хотя бы один необязательный столбец, и вы не вводите в него значений, задание списка имен столбцов — обязательно. В набор значений могут быть включены специальные функции и выражения. Ограничением здесь является то, что значения этих функций должны быть определены на момент ввода данных. Например, если мы знаем, что вводим книгу, которая издана в текущем году, то вместо числа, соответствующего году издания можем включить функцию Year (GetDate ()) — здесь мы использовали две функции из внутреннего языка TransactSQL 2 МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ INTERNATIONAL BANKING INSTITUTE сервера баз данных MS SQL Server. Функция Year () использует в качестве параметра календарную дату и позволяет по заданной дате определить год. Внутренняя функция GetDate () позволяет определить текущую календарную дату. В этом случае оператор ввода данных будет выглядеть следующим образом: INSERT INTO BOOKS VALUES (’ 5-94723-275-9 ’, ’Теория и практика построения баз данных’, ’Питер’, Year (GetDate ()),796) Операция ввода данных связана с анализом взаимосвязей между таблицами БД. Нельзя ввести данные в подчиненную таблицу, если вводимая запись связана с данными из главной родительской таблицы, а в нее соответствующая строка еще не введена. При вводе данных необходимо соблюдать строгий порядок: сначала заполнять основные таблицы и только потом заполнять подчиненные таблицы, при этом если одна подчиненная таблица связана с двумя основными, то необходимо сначала заполнить обе основные таблицы и только после этого вводить данные в подчиненную таблицу. Оператор ввода данных позволяет ввести сразу множество строк, если их можно выбрать из некоторой другой таблицы. Допустим, что у нас есть таблица со студентами и в ней указаны основные данные о студентах: их фамилии, адреса, домашние телефоны и даты рождения, а для учета читателей есть таблица READERS, которая имеет следующую схему: READERS N_reader NAME_READER ADRESS Номер ФИО читателя Адрес читательского билета HOOME_PHONE BIRTH_DAY Домашний телефон Дата рождения Тогда мы можем сделать всех студентов читателями нашей библиотеки одним оператором: INSERT INTO READER (NAME_READER, ADRESS, HOOM_PHONE, BIRTH_DAY) SELECT (NAME_STUDENT, ADRESS, HOOM_PHONE, BIRTH_DAY) FROM STUDENT При этом номер читательского билета может назначаться автоматически, с использованием инкрементного поля, поэтому мы не вводим значения этого столбца в таблицу. Инкрементные поля ведут себя как поля типа «Счетчик» в MS Access, т. е. при добавлении новой строки СУБД сама присваивает очередное значение данному полю. 4.2. Оператор удаления данных DELETE Оператор удаления данных позволяет удалить одну или несколько строк из таблицы в соответствии с условиями, которые задаются для удаляемых строк. Синтаксис оператора DELETE следующий: DELETE FROM имя_таблицы [WHERE условия_отбора] Если условия отбора не задаются, то из таблицы удаляются все строки. Однако это не означает, что удаляется вся таблица. Исходная таблица остается, но она остается пустой, незаполненной. Например, если нам надо удалить результаты прошедшей сессии из отношения R1, то мы можем удалить все строки таблицы R1 следующей командой: 3 МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ INTERNATIONAL BANKING INSTITUTE DELETE FROM R1 Условия отбора в части WHERE имеют тот же вид, что и условия фильтрации в операторе SELECT. Эти условия определяют, какие строки из исходного отношения будут удалены. Например, если мы исключим студента Миронова А. В., и мы помним, что списки студентов хранятся в таблице R2, то мы должны написать следующую команду: DELETE FROM R2 WHERE ФИО = ’ Миронов А. В.’ В части WHERE кроме простых условий фильтрации может находиться также встроенный запрос. Например, если нам надо исключить неуспевающих студентов, то по закону о высшем образовании не успевающим считается студент, имеющий две и более задолженности по послед ней сессии. Тогда нам в условиях отбора надо найти студентов, имеющих либо две или более двоек, либо два и более несданных экзамена из числа тех, которые студент сдавал. Для поиска таких горе-студентов нам надо выбрать из отношения R1 все строки с оценкой 2 или с неопределенным значением, потом надо сгруппировать полученный результат по атрибуту ФИО и, подсчитав количество строк в каждой группе, которое соответствует количеству несданных экзаменов каждым студентом, отобрать те группы, у которых количество строк не менее двух. Теперь попробуем просто записать эту сложную конструкцию на SQL и убедимся, что этот сложный запрос записывается достаточно компактно. DELETE FROM R2 WHERE R2. ФИО IN (SELECT R1. ФИО FROM R1 WHERE Оценка = 2 OR Оценка IS NULL GROUP BY R1. ФИО HAVING COUNT(*) >= 2) Этот запрос будет корректно работать в том случае, если в отношении R1 находятся записи о несдававших студентах, и там вместо оценки стоит неопределенное значение NULL. А как готовить запрос в том случае, если в таблице R1 зарегистрированы только все попытки сдачи экзаменов, а те студенты, которые по каким-либо причинам не пришли сдавать экзамен, вообще в данной таблице отсутствуют. Ну мы уже умеем и в этом случае вычислять количество не сданных студентом экзаменов. Снова вспомним нашу БД «Сессия», восстановим структуру таблиц: R1 = <ФИО, Дисциплина, Оценка> R2 = <ФИО, Группа> R3 = <Группы, Дисциплина> Сформулируем следующим образом запрос: удаляем тех студентов, у которых число экзаменов, которые им требуется сдать, минус два превышает число сданных ими экзаменов. Delete from R2 Where FIO in (Select FIO from R2, R3 Where R2. Группа= R3. Группа Group by FIO 4 МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ INTERNATIONAL BANKING INSTITUTE Having count (distinct Дисциплина)-2 > (Select count(*)from R1 Where Mark>2 and R1.FIO =R2.FIO) ) Все операции манипулирования данными связаны с понятием целостности базы данных, которое будет рассматриваться в теме 5. В настоящий момент хотелось бы отметить только то, что операции манипулирования данными не всегда выполнимы, даже если синтаксически они написаны правильно. Действительно, если бы мы захотели удалить какую-нибудь группу из отношения R3, то СУБД не позволила бы нам это сделать, так как в отношениях R1 и R2 есть строки, связанные с удаляемой строкой в отношении R3. Почему так делается, мы узнаем позднее, а пока просто примем к сведению, что не все операторы манипулирования выполнимы. 4.3. Оператор обновления данных UPDATE Операция обновления данных UPDATE требуется тогда, когда происходят изменения во внешнем мире и их надо адекватно отразить в базе данных, так как надо всегда помнить, что база данных отражает некоторую предметную область. Например, в нашем учебном заведении произошло счастливое событие, которое связано с тем, что госпожа Степанова К. Е. пересдала экзамен по дисциплине «Базы данных» с «двойки» сразу на «четверку». В этом случае нам надо срочно выполнить соответствующую корректировку таблицы R1. Операция обновления имеет следующий формат: UPDATE имя_таблицы SET {имя_столбца = новое_значение [,...]} [WHERE условие_отбора] Выражение, которое находится в фигурных скобках означает, что мы за одну операцию обновления можем изменить сразу несколько столбцов, при этом ключевое слово SET не повторяется, а выражение «имя_столбца = новое_значение» может повторяться несколько раз для разных столбцов, и при этом каждая пара отделяется от следующей разделителем — запятой. Часть WHERE является необязательной, как и в операторе DELETE. Она играет здесь ту же роль, что и в операторе DELETE, — позволяет отобрать строки, к которым будет применена операция модификации. Если условие отбора не задается, то операция модификации будет применена ко всем строкам таблицы. Для решения ранее поставленной задачи нам необходимо выполнить следующую операцию: UPDATE R1 SET R1. Оценка = 4 WHERE R1.ФИО = «Степанова К.Е.» AND R1.Дисциплина = «Базы данных» В каких случаях требуется провести изменение в нескольких строках? Это не такая уж редкая задача. Например, если мы расширим нашу учебную базу данных еще одним отношением, которое содержит перечень курсов, на которых учатся наши студенты, то можно с помощью операции обновления промоделировать операцию перевода групп на следующий курс. Пусть новое отношение R4 имеет следующую схему: R 4 = <Группа, Курс > 5 МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ INTERNATIONAL BANKING INSTITUTE R4 Группа Курс 4906 3 4807 4 В этом случае перевод на следующий курс можно выполнить следующей операцией обновления: UPDATE R4 SET R4.Курс = R4.Курс + 1 Группа Курс 4906 4 4807 5 Операция модификации, как и операция удаления, может использовать сложные подзапросы. В нашем институте поощряется хорошая успеваемость. Студенты, которые хорошо учатся, платят за обучение существенно меньше стандартно установленной платы. Расширим нашу базу еще одним отношением, которое будет содержать перечень студентов, с указанием процента скидки, которую они получают за отличную учебу. Исходно там могут находиться все студенты с указанием неопределенного размера стипендии. По мере анализа отношения R1 мы можем постепенно заменять неопределенные значения на конкретные размеры скидки. Отношение R5 имеет следующий вид: R5 ФИО Группа Скидка Петров Ф. И. 4906 <Null> Сидоров К. А. 4906 <Null> Миронов А. В. 4906 <Null> Крылова Т. С. 4906 <Null> Владимиров В. А. 4906 <Null> Трофимов П. А. 4807 <Null> Иванова Е. А. 4807 <Null> Уткина Н. В. 4807 <Null> Будем считать наличие трех «пятерок» по сессии признаком максимальной скидки — 50 % от размера базового уровня оплаты обучения, наличие двух «пятерок» из сданных экзаменов и отсутствие «двоек» и «троек» за сданные экзамены — признаком снижения оплаты на 25 %, наличие хотя бы одной «двойки» за сданные экзамены — признаком повышенной оплаты, т. е. +10 % к размеру основной оплаты. При отсутствии «двоек» за сданные экзамены назначим обычную оплату со скидкой 0 %. Однако все эти изменения мы должны будем сделать отдельными операциями обновления. Назначение минимальной оплаты со скидкой 5 %: UPDATE R 5 SET R 5.Скидка = 50 % WHERE R5. ФИО IN (SELECT R1. ФИО FROM R1 WHERE R1. Оценка = 5 GROUP BY R1. ФИО 6 МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ INTERNATIONAL BANKING INSTITUTE HAVING COUNT(*) =3) Назначение скидки 25 %: UPDATE R 5 SET R5. Скидка = 25 % WHERE R5. ФИО IN (SELECT R1. ФИО FROM R1 GROUP BY A. ФИО HAVING MAX(R1. Оценка)>=4 and MIN(R1. Оценка) = 4) AND R5.FIO NOT IN (SELECT R1.FIO FROM R1 WHERE R1. Оценка <=2 OR R1. Оценка IS NULL) Назначение стандартной скидки: UPDATE R 5 SET R 5.Скидка = 0 % WHERE R5. ФИО IN (SELECT R1. ФИО FROM R1 GROUP BY A. ФИО HAVING MAX(R1. Оценка)>=3 and MIN(R1. Оценка) = 3 and Count(R1. Оценка)=3) Повышение оплаты за «двойки», полученные в сессию и несданные экзамены: UPDATE R5 SET R5. скидка = — 10 % WHERE R5. ФИО IN (SELECT R1. ФИО FROM R1 WHERE R1. Оценка <= 2 OR R1. Оценка IS NULL) Почему мы в первом запросе на обновление не использовали дополнительную проверку на отсутствие «двоек», «троек» и несданных экзаменов, как мы сделали это при назначении следующих видов оплаты? Просто мы учли особенности нашей предметной области: у нас в соответствии с исходными данными только 3 экзамена. Но если мы предположим, что число экзаменов может быть произвольным и изменяться от семестра к семестру, то нам надо изменить наш запрос. Запрос — это некоторый алгоритм решения конкретной задачи, которую мы формулируем заранее на естественном языке. От того, что наша задача решается всего одним оператором языка SQL, она не становится примитивной. Мощность языка SQL и состоит в том, что он позволяет одним предложением сформулировать ответы на достаточно сложные запросы, для реализации которых на традиционных языках программирования понадобилось бы писать сложную программу. При этом одну и ту же задачу можно решить разными способами, и все они могут быть правильными. Так, запрос о назначении стандартной оплаты мы написали иначе, чем два предыдущих. Здесь 7 МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ INTERNATIONAL BANKING INSTITUTE мы использовали агрегатные функции MAX (R 1.Оценка) и MIN (R 1.Оценка) и COUNT (R 1.Оценка). Мы выбрали таких студентов, которые имеют максимальную оценку в сессию не менее 3, минимальную оценку 3 и общее количество сданных экзаменов 3. А почему мы не могли использовать функцию COUNT (*)? В этом случае мы бы получили неверный результат, ведь функция COUNT (*) подсчитывает общее число строк в группе, а там могут быть и неопределенные значения для атрибута R 1.Оценка. А функция COUNT (R 1.Оценка). подсчитывает количество определенных значений атрибута R 1.Оценка в группе. Поэтому наш запрос действительно поставит 0 % в столбец со скидкой только тем студентам, которые сдали все 3 экзамена, но имеют одну или несколько троек при этом. А теперь попробуем написать универсальный запрос, который не содержал бы константного значения 3, определяющего количество экзаменов в сессию. Итак, подумаем, как нам надо изменить текст нашего запроса на обновление для назначения повышенной стипендии при любом количестве сданных экзаменов. Прежде всего, каждая группа может иметь свое число экзаменов в сессию, это зависит от специальности и учебного плана, по которому учится данная группа. Поэтому для каждого студента нам надо знать, сколько экзаменов он должен был сдать и сколько экзаменов он сдал на «пять», и в том случае, когда эти два числа равны, мы можем назначить ему повышенную стипендию. Будем решать нашу задачу по шагам. В конечном счете, нам все равно надо знать, сколько экзаменов должен сдать каждый конкретный студент. А почему нельзя сосчитать количество экзаменов, которые должна сдать группа, в которой учится студент? Ведь все студенты, которые учатся в одной группе, должны сдать одинаковое количество экзаменов. Действительно, это справедливо. Однако мы будем сравнивать число, которое записано в части HAVING, оно получено в результате выполнения группировки по атрибуту «ФИО», и по синтаксису SQL мы в подчиненном запросе можем для фильтрации использовать только имена столбцов группировки, а столбца группа там нет. Это мы делать умеем. Для этого надо сделать запрос SELECT над отношениями R 2,R3, соединив их по атрибуту «Группа», потом сгруппировав по атрибуту «ФИО» и вывести для каждого студента количество дисциплин, по которым он должен сдавать экзамены. Если мы учтем, что в одной сессии по одной дисциплине не бывает более одного экзамена, то можно просто подсчитать количество строк в каждой группе. SELECT R2.ФИО, COUNT(*) «Число_экзаменов» FROM R2, R3 Where WHERE R2.Группа = R3.Группа GROUP BY R 2.ФИО А теперь построим запрос, который выберет студентов, сдавших все требуемые экзамены на 5. Select ФИО from R2 WHERE ФИО IN (SELECT R1.[ ФИО ] FROM R1 WHERE R1.[ Оценка ] = 5 GROUP BY [ ФИО ] HAVING COUNT(*) = (SELECT COUNT(*) FROM R3,R2 8 МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ INTERNATIONAL BANKING INSTITUTE WHERE R2.[ Группа ] = R3.[ Группа] and R1.[ ФИО ]=R2.[ ФИО ])) А почему мы в починенном запросе в части FROM не написали имя первого отношения R1? Имя этого отношения мы укажем для связи с вышестоящим запросом в части Where подчиненного запроса. Теперь назначение максимальной скидки будет выглядеть следующим образом: UPDATE R5 SET R5. Скидка = 50% WHERE ФИО IN (SELECT R1.[ ФИО ] FROM R1 WHERE R1.[ Оценка ] = 5 GROUP BY [ ФИО ] HAVING COUNT(*) = (SELECT COUNT(*) FROM R3,R2 WHERE R2.[ Группа ] = R3.[ Группа ] and R1.[ ФИО ]=R2.[ ФИО ])) Вот какой сложный запрос мы построили. Это ведь практически один оператор, а какую сложную задачу он решает. Действительно, мощность языка SQL иногда удивляет даже профессионалов. Первоначально кажется, что невозможно построить один запрос для решения конкретной задачи, но когда начинаешь поэтапно его конструировать — все получается. Самое сложное — это сделать переход от словесной формулировки задачи к представлению ее в терминах нашего SQL, но этот процесс сродни процессу алгоритмизации при решении задач традиционного программирования, а он всегда был самым трудным, творческим и неформализуемым процессом. Недаром на заре развития программирования известный американский специалист по программированию Дональд Е. Кнут озаглавил свой многотомный капитальный труд по теории и практике программирования «Искусство программирования для ЭВМ» («The art of computer programming»). Вопросы для самопроверки 1. Сколько операций входит в язык манипулирования данными? 2. Можно ли с использованием операций манипулирования данными изменить сразу несколько таблиц? 3. Можно ли с использованием операций манипулирования данными изменить сразу несколько строк? 4. В каких случаях задание списка имен атрибутов при вводе новой строки в операторе INSERT не обязательно? 5. Как можно вводить неопределенные значения? 6. Когда задание списка имен атрибутов оператора INSERT обязательно? 7. Как можно ввести много строк в одну таблицу? 8. Сколько строк удалит оператор Delete from R1? 9. Можно ли одним оператором обновить сразу несколько строк? 10. Можно ли одним оператором обновить сразу несколько столбцов? Пенсионный возраст у женщин и мужчин наступает в разное время. Если у меня задана дата рождения каждого сотрудника, могу ли я одним оператором заполнить 9 МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ INTERNATIONAL BANKING INSTITUTE для всех столбец «Пенсионер», занеся туда «Да» для всех пенсионеров? Если да, то напишите соответствующий оператор обновления. 10