Извлечение значений переменных
Кроме помещения значений переменных в таблицы, используя команды SQL, вы можете использовать SQL, чтобы получать значения для этих переменных. Один из способов делать это — с помощью разновидности команды SELECT, которая содержит предложение INTO. Давайте вернемся к нашему предыдущему примеру и переместим строку Peel из таблицы Продавцов в наши переменные главного языка.
EXEC SQL SELECT snum, sname, city, comm
INTO :id_num, :salesperson, :loc, :comm
FROM Salespeople
WHERE snum = 1001;
Выбранные значения помещаются в переменные с упорядоченными именами указанными в предложении INTO. Разумеется, переменные с именами, указанными в предложении INTO, должны иметь соответствующий тип, чтобы принять эти значения, и должна быть своя переменная для каждого выбранного столбца.
Если не учитывать присутствие предложения INTO, то этот запрос похож на любой другой. Однако предложение INTO добавляет значительное ограничение к запросу. Запрос должен извлекать не более одной строки. Если он извлекает много строк, все они не могут быть вставлены одновременно в одну и ту же переменную. Команда естественно потерпит неудачу. По этой причине, SELECT INTO должно использоваться только при следующих условиях:
· когда вы используете предикат, проверяющий значения, которые, как вы знаете, могут быть уникальны, как в этом примере. Значения, которые, как вы знаете, могут быть уникальными — это те значения, которые имеют принудительное ограничение уникальности или уникальный индекс, как это говорилось в Главах 17 и 18.
· когда вы используете одну или более агрегатных функций и не используете GROUP BY.
· когда вы используете SELECT DISTINCT во внешнем ключе с предикатом, ссылающимся на единственное значение родительского ключа (обеспечивая вашей системе предписание справочной целостности), как в следующем примере:
EXEC SQL SELECT DISTINCT snum
INTO :salesnum
FROM Customers
WHERE snum = (SELECT snum
FROM Salespeople
WHERE sname = 'Motika');
Предполагалось, что Salespeople.snameиSalespeople.snum—это соответственно, уникальный и первичный ключи этой таблицы, а Customers.snum — это внешний ключ, ссылающийся на Salespeople.snum, и вы предполагали, что этот запрос произведет единственную строку.
Имеются другие случаи, когда вы точно знаете, что запрос должен произвести единственную строку вывода, но они мало известны и, в большинстве случаев, вы основываетесь на том, что ваши данные имеют целостность, которая не может быть предписана с помощью ограничений. Не полагайтесь на это! Вы создаете программу, которая, вероятно, будет использоваться в течение некоторого времени, и лучше всего проиграть ее, чтобы быть гарантированным в будущем от возможных отказов. Во всяком случае, нет необходимости группировать запросы, которые производят одиночные строки, поскольку SELECT INTO используется только для удобства.
Как вы увидите, вы можете использовать запросы, выводящие многочисленные строки, используя курсор.
Курсор
Одно из сильных качеств SQL — это способность функционировать на всех строках таблицы, чтобы встретить определенное условие как блок-запись, не зная, сколько таких строк там может быть. Если десять строк удовлетворяют предикату, то запрос может вывести все десять строк. Если десять миллионов строк определены, все десять миллионов строк будут выведены. Это несколько затруднительно, когда вы попробуете связать это с другими языками. Как вы сможете назначать вывод запроса для переменных, когда вы не знаете, как велик будет вывод? Решение состоит в том, чтобы использовать то, что называется курсором.
Вы вероятно знакомы с курсором, как с мигающей черточкой, которая отмечает вашу позицию на экране компьютера. Вы можете рассматривать SQL курсор как устройство, которое аналогично этому, отмечает ваше место в выводе запроса, хотя аналогия не полная.
Курсор — это вид переменной, которая связана с запросом. Значением этой переменной может быть каждая строка, которая выводится при запросе. Подобно главным переменным, курсоры должны быть объявлены прежде, чем они будут использованы. Это делается командой DECLARE CURSOR, следующим образом:
EXEC SQL DECLARE CURSOR Londonsales FOR
SELECT *
FROM Salespeople
WHERE city = 'London';
Запрос не выполнится немедленно; он — только определяется. Курсор немного напоминает представление, в котором курсор содержит запрос, а содержание курсора напоминает любой вывод запроса, каждый раз, когда курсор становится открытым. Однако, в отличие от базовых таблиц или представлений, строки курсора упорядочены: имеются первая, вторая... ...и последняя строка курсора. Этот порядок может быть произвольным, с явным управлением с помощью предложения ORDER BY в запросе или же по умолчанию следовать какому-то упорядочению, определяемому инструментальной схемой.
Когда вы находите точку в вашей программе, в которой вы хотите выполнить запрос, вы открываете курсор с помощью следующей команды:
EXEC SQL OPEN CURSOR Londonsales;
Значения в курсоре могут быть получены, когда вы выполняете именно эту команду, но не предыдущую команду DECLARE и не последующую команду FETCH. Затем, вы используете команду FETCH, чтобы извлечь вывод из этого запроса, по одной строке в каждый момент времени.
EXEC SQL FETCH Londonsales INTO :id_num, :salesperson, :loc, :comm;
Это выражение переместит значения из первой выбранной строки, в переменные. Другая команда FETCH выведет следующий набор значений. Идея состоит в том, чтобы поместить команду FETCH внутрь цикла, так, чтобы, выбрав строку, вы могли, переместив набор значений из этой строки в переменные, возвращались обратно в цикл, чтобы переместить следующий набор значений в те же самые переменные.
Например, вам нужно, чтобы вывод выдавался по одной строке, спрашивая каждый раз у пользователя, хочет ли он продолжить, чтобы увидеть следующую строку
Look_at_more:=True;
EXEC SQL OPEN CURSOR Londonsales;
while Look_at_more do
begin
EXEC SQL FETCH Londonsales
INTO :id_num, :Salesperson, :loc, :comm;
writeln (id_num, Salesperson, loc, comm);
writeln ('Do you want to see more data? (Y/N)');
readln (response);
it response = 'N' then Look_at_more:=False
end;
EXEC SQL CLOSE CURSOR Londonsales;
В Паскале, знак := означает "является назначенным значением из", в то время как = еще имеет обычное значение "равно". Процедура writeln записывает ее вывод, и затем переходит к новой строке.
В результате этого фрагмента, Булева переменная с именем Look_at_more должна быть установлена в состояние "верно", открыт курсор, и введен цикл. Внутри цикла, строка выбирается из курсора и выводится на экран. У пользователя спрашивают, хочет ли он видеть следующую строку. Пока он не ответилN (Нет), цикл повторяется, и следующая строка значений будет выбрана.
Хотя переменные Look_at_more и response должны быть объявлены как boolean и char переменная соответственно в разделе объявлений переменных Паскаля, они не должны быть включены в раздел объявлений SQL, потому что они не используются в командах SQL.
Как вы можете видеть, двоеточия перед именами переменных не используются для не-SQL операторов. Далее обратите внимание, что имеется оператор CLOSE CURSORсоответствующий оператору OPEN CURSOR. Он, как вы поняли, освобождает курсор значений, поэтому запрос будет нужно выполнить повторно с оператором OPEN CURSOR, прежде чем перейти к выбору следующих значений.
Это необязательно для тех строк, которые были выбраны запросом после закрытия курсора, хотя это и обычная процедура.
Пока курсор закрыт, SQL не следит за тем, какие строки были выбраны. Если вы открываете курсор снова, запрос повторно выполняется с этой точки, и вы начинаете все сначала.
Этот пример не обеспечивает автоматический выход из цикла, когда все строки уже будут выбраны. Когда у FETCH нет больше строк, которые надо извлекать, он просто не меняет значений в переменных предложения INTO. Следовательно, если данные исчерпались, эти переменные будут неоднократно выводиться с идентичными значениями, до тех пор, пока пользователь не завершит цикл, введя ответ — N.
SQLCODE
Хорошо было бы знать, когда данные будут исчерпаны, так чтобы можно было сообщить об этом пользователю, и цикл завершился бы автоматически. Это — даже более важно чем, например, знать, что команда SQL выполнена с ошибкой. Переменная SQLCODE (называемая еще SQLCOD в ФОРТРАНе) предназначена обеспечить эту функцию. Она должна быть определена как переменная главного языка и должна иметь тип данных, который в главном языке соответствует одному из точных числовых типов SQL, как это показано в Приложении B. Значение SQLCODE устанавливается каждый раз, когда выполняется команда SQL. В основном существуют три возможности:
1. Команда выполнилась без ошибки, но не произвела никакого действия. Для различных команд это выглядит по-разному:
а) Для SELECT ни одна строка не выбрана запросом.
б) Для FETCH последняя строка уже была выбрана, или ни одной строки не выбрано запросом в курсоре.
в) Для INSERT ни одной строки не было вставлено (подразумевается, что запрос использовался, чтобы сгенерировать значения для вставки, и был отвергнут при попытке извлечения любой строки).
г) Для UPDATE и DELETE ни одна строка не ответила условию предиката, и, следовательно, никаких изменений сделано в таблице не будет.
В любом случае, будет установлен код SQLCODE = 100.
2. Команда выполнилась нормально, не удовлетворив ни одному из выше указанных условий. В этом случае, будет установлен код SQLCODE = 0.
3. Команда сгенерировала ошибку. Если это случилось, изменения сделанные к базе данных текущей транзакцией, будут восстановлены (см. Главу 23).
В этом случае будет установлен код SQLCODE = некоторому отрицательному числу, определяемому проектировщиком. Задача этого числа — идентифицировать проблему так точно, насколько это возможно. В принципе, ваша система должна быть снабжена подпрограммой, которая, в этом случае, должна выполниться, чтобы выдать для вас информацию, расшифровывающую значение негативного числа, определенного вашим проектировщиком. В этом случае некоторое сообщение об ошибке будет выведено на экран или записано в файл протокола, а программа в это время выполнит восстановление изменений для текущей транзакции, отключится от базы данных и выйдет из нее.