Опубликован: 25.11.2008 | Доступ: свободный | Студентов: 4438 / 685 | Оценка: 4.46 / 4.18 | Длительность: 26:08:00
Лекция 12:

Встроенный SQL

Операторы, связанные с многострочными запросами

Рассмотрим более сложные многострочные запросы.

Для реализации многострочных запросов вводится новое понятие — понятие курсора или указателя набора записей. Для работы с курсором добавляется несколько новых операторов SQL:

  1. Оператор DECLARE CURSOR — определяет выполняемый запрос, задает имя курсора и связывает результаты запроса с заданным курсором. Этот оператор не является исполняемым для запроса, он только определяет структуру будущего множества записей и связывает ее с уникальным именем курсора. Этот оператор подобен операторам описания данных в языках программирования.
  2. Оператор OPEN дает команду СУБД выполнить описанный запрос, создать виртуальный набор строк, который соответствует заданному запросу. Оператор OPEN устанавливает указатель записей (курсор) перед первой строкой виртуального набора строк результата.
  3. Оператор FETCH продвигает указатель записей на следующую позицию в виртуальном наборе записей. В большинстве коммерческих СУБД оператор перемещения FETCH реализует более широкие функции перемещения, он позволяет
  4. перемещать указатель на произвольную запись, вперед и назад, допускает как абсолютную адресацию, так и относительную адресацию, позволяет установить курсор на первую или последнюю запись виртуального набора.
  5. Оператор CLOSE закрывает курсор и прекращает доступ к виртуальному набору записей. Он фактически ликвидирует связь между курсором и результатом выполнения базового запроса. Однако в коммерческих СУБД оператор CLOSE не всегда означает уничтожение виртуального набора записей. Мы коснемся этого далее, когда будем рассматривать работу с курсором в MS SQL Server 7.0.

Оператор определения курсора

Стандарт определяет следующий синтаксис оператора определения курсора:

DECLARE <имя_курсора> 
CURSOR FOR <спецификация_курсора> 
<спецификация курсора>::= <выражение_запроса SELECT>

Имя курсора — это допустимый идентификатор в базовом языке программирования.

В объявлении курсора могут быть использованы базовые переменные. Однако необходимо помнить, что на момент выполнения оператора OPEN значения всех базовых переменных, используемых в качестве входных переменных, связанных с условиями фильтрации значений в базовом запросе, должны быть уже заданы.

Определим курсор, который содержит список всех должников нашей библиотеки. Должниками назовем читателей, которые имеют на руках хотя бы одну книгу, срок сдачи которой уже прошел.

DECLARE Debtor_reader_cursor CURSOR FOR
SELECT READERS.FIRST_NAME, READERS.LAST_NAME, READERS.ADRES,
READERS.HOME_PHON, READERS.WORK_PHON, BOOKS.TITLE 
FROM READERS,BOOKS,EXEMPLAR 
WHERE READERS.READER_ID = EXEMPLAR.READER_ID 
AND BOOKS.ISBN = EXEMPLARE.ISBN 
AND EXEMPLAR.DATA_OUT > Getdate() 
ORDER BY READERS.FIRST_NAME

При определении курсора мы снова использовали функцию Transact SQL Getdate(), которая возвращает значение текущей даты. Таким образом, определенный курсор будет создавать набор строк, содержащих перечень должников, с указанием названий книг, которые они не вернули вовремя в библиотеку.

В соответствии со стандартом SQL2 Transact SQL содержит расширенное определение курсора

DECLARE <имя_курсора> [INSENSITIVE] [SCROLL] CURSOR
FOR <оператор выбора SELECT>
[FOR {READ ONLY | UPDATE [OF <имя_столбца 1> [,...n]]}]

Параметр INSENSITIVE (нечувствительный) определяет режим создания набора строк, соответствующего определяемому курсору, при котором все изменения в исходных таблицах, произведенные после открытия курсора другими пользователями, не видны в нем. Такой набор данных нечувствителен ко всем изменениям, которые могут проводиться другими пользователями в исходных таблицах, этот тип курсора соответствует некоторому мгновенному слепку с БД.

СУБД более быстро и экономно может обрабатывать такой курсор, поэтому если для вас действительно важно рассмотреть и обработать состояние БД на некоторый конкретный момент времени, то имеет смысл создать "нечувствительный курсор".

Ключевое слово SCROLL определяет, что допустимы любые режимы перемещения по курсору ( FIRST, LAST, PRIOR, NEXT, RELATIVE, ABSOLUTE ) в операторе FETCH.

Если не указано ключевое слово SCROLL, то считается доступной только стандартное перемещение вперед: спецификация NEXT в операторе FETCH.

Если указана спецификация READ ONLY (только для чтения), то изменения и обновления исходных таблиц не будут выполняться с использованием данного курсора. Курсор с данной спецификацией может быть самым быстрым в обработке, однако если вы не укажите специально спецификацию READ ONLY, то СУБД будет считать, что вы допускаете операции модификации с базовыми таблицами, и в этом случае для обеспечения целостности БД СУБД будет гораздо медленнее обрабатывать ваши операции с курсором.

При использовании параметра UPDATE [OF <имя столбца 1> [,...<имя столбца n>]] мы задаем перечень столбцов, в которых допустимы изменения в процессе нашей работы с курсором. Такое ограничение упростит и ускорит работу СУБД. Если этот параметр не указан, то предполагается, что допустимы изменения всех столбцов курсора.

Вернемся к нашему примеру. Если мы преследуем цель мгновенного слепка БД, дающего сведения о должниках, то применим все параметры, позволяющие ускорить работу с нашим курсором. Тогда оператор описания курсора будет выглядеть следующим образом:

DECLARE Debtor_reader_cursor INSENSITIVE CURSOR
FOR SELECT READERS.FIRST_NAME, READERS.LAST_NAME, READERS.ADRES,
READERS.HOME_PHON, READERS.WORK_PHON, BOOKS.TITLE 
FROM READERS,BOOKS,EXEMPLAR 
WHERE READERS.READER_ID = EXEMPLAR.READER_ID AND
BOOKS.ISBN = EXEMPLARE.ISBN AND
EXEMPLAR.DATA_OUT > Getdate()
ORDER BY READERS.FIRST_NAME FOR READ ONLY

При описании курсора нет ограничений на вид оператора SELECT, который используется для создания базового набора строк, связанного с курсором. В операторе SELECT могут использоваться группировки и встроенные подзапросы и вычисляемые поля.

Оператор открытия курсора

Оператор открытия курсора имеет следующий синтаксис:

OPEN <имя_курсора> 
[USING <список базовых переменных>]

Именно оператор открытия курсора инициирует выполнение базового запроса, соответствующего описанию курсора, заданному в операторе DECLARE … CURSOR. При выполнении оператора OPEN СУБД производит семантическую проверку курсора, то есть выполняет этапы со 2 по 5 в алгоритме выполнения запросов (рис. 12.1), поэтому именно здесь СУБД возвращает коды ошибок прикладной программе, сообщающие ей о результатах выполнения базового запроса. Ошибки могут возникнуть в результате неправильного задания имен полей или имен исходных таблиц или при попытке извлечь данные из таблиц, к которым данный пользователь не имеет доступа.

По стандарту СУБД возвращает код завершения операции в специальной системной переменной SQLCODE. В прикладной программе пользователь может анализировать эту переменную, что необходимо делать после выполнения каждого оператора SQL. При неудачном выполнении операции открытия курсора СУБД возвращает отрицательное значение SQLCODE.

В случае удачного завершения выполнения оператора открытия курсора набор данных, сформированный в результате базового запроса, остается доступным пользователю до момента выполнения оператора закрытия курсора.

Однако надо помнить, что СУБД автоматически закрывает все курсоры в случае завершения транзакции ( COMMIT ) или отката транзакции ( ROLLBACK ). После того как курсор закрыт его можно открыть снова, но при этом соответствующий запрос выполнится заново. Поэтому допустимо, что содержимое первого курсора будет не соответствовать его содержимому при повторном открытии, потому что за это время изменилось состояние БД.

Оператор чтения очередной строки курсора

После открытия указатель текущей строки установлен перед первой строкой курсора. Стандартно оператор FETCH перемещает указатель текущей строки на следующую строку и присваивает базовым переменным значение столбцов, соответствующее текущей строке.

Простой оператор FETCH имеет следующий синтаксис:

FETCH <имя_курсора> 
  INTO <список переменных базового языка >

Оператор извлечения очередной строки из курсора будет выглядеть следующим образом:

FETCH Debtor_reader_cursor into 
     @FIRST_NAME, @LAST_NAME, 
     @ADRES, @HOME_PHON, 
     @WORK_PHON, @TITLE

Расширенный оператор FETCH имеет следующий синтаксис:

FETCH [NEXT | PRIOR | FIRST | LAST
ABSOLUTE {n | <имя_переменной>}
|RELATIVE{n|<имя_переменной>}] FROM
<имя_курсора> 
INTO <список базовых переменных>

Здесь параметр NEXT задает выбор следующей строки после текущей из базового набора строк, связанного с курсором. Параметр PRIOR задает перемещение на предыдущую строку по отношению к текущей. Параметр FIRST задает перемещение на первую строку набора, а параметр LAST задает перемещение на последнюю строку набора.

Кроме того, в расширенном операторе перемещения допустимо переместиться сразу на заданную строку, при этом допустима как абсолютная адресация, заданием параметра ABSOLUTE, так и относительная адресация, заданием параметра RELATIVE. При относительной адресации положительное число сдвигает указатель вниз от текущей записи, отрицательное число сдвигает вверх от текущей записи.

Однако для применения расширенного оператора FETCH в соответствии со стандартом SQL2 описание курсора обязательно должно содержать ключевое слово SCROLL. Иногда такие курсоры называют в литературе прокручиваемыми курсорами. В стандарт эти курсоры вошли сравнительно недавно, поэтому в коммерческих СУБД очень часто операторы по работе с подобными курсорами серьезно отличаются. Правда, реалии сегодняшнего дня заставляют поставщиков коммерческих СУБД более строго соблюдать последний стандарт SQL. В технической документации можно встретить две версии синтаксиса оператора FETCH: одну, которая соответствует стандарту, и другую, которая расширяет стандарт дополнительными возможностями, предоставляемыми только данной СУБД для работы с курсором.

Если вы предполагаете, что ваша БД может быть перенесена на другую платформу, а это надо всегда предусматривать, то лучше пользоваться стандартными возможностями. В этом случае ваше приложение будет более платформенно-не-зависимым и легче будет его перенести на другую СУБД.

Михаил Дубовик
Михаил Дубовик

В лекции как пример отношения в третьей нормальной форме приводится такая схема: (Номер зач. кн.\ ФИО \ Специальность \ Группа). Первичный ключ - Номер зач. кн. Но ведь существует следующая транзитивная зависимость: 

Номер зач. кн. -> Группа -> Специальность.

Получается, что отношение все же еще во второй нормальной форме. Или в моих рассуждениях ошибка?

Михаил Скок
Михаил Скок
Елена Веселова
Елена Веселова
Россия, Самара, Самарский Государственный институт культуры (СГИК), 1991