Глава 2. Язык SQL § 1. Основные сведения о языке SQL В этой

advertisement
Глава 2. Язык SQL
§ 1. Основные сведения о языке SQL
В этой главе рассмотрим подробно возможностей языка SQL (Structured Query
Language - язык структурированных запросов).
Язык SQL впервые был реализован фирмой IBM в начале 70-х годов. После
этого были выпущены различные модификации этого языка и соответственно были
приняты несколько стандартов. Наиболее популярными среди этих стандартов
являются стандарты SQL-89 и SQL-92.
Стандарт SQL-92, поддерживаемый Американским национальным институтом
стандартов (ANSI - American National Standards Institute)
и международной
организацией по стандартизации (ISO - International Standard Organization),
также
называют стандартом ANSI / ISO. Вследствие наличия нескольких стандартов и их
различных интерпретаций появилось множество диалектов языка SQL, более или менее
отличающихся друг от друга. Рассмотрим основные возможности версии языка SQL,
используемый в Delphi.
1.1. Синтаксис SQL-запроса
Как было сказано выше, SQL-запрос является текстовой строкой на языке SQL,
который определяет требуемые данные.
Ниже приведем некоторые особенности
формирования текста SQL-запроса, которые приняты в Delphi.
1. Для наглядности зарезервированные слова языка SQL будут писаться
прописными, а имена - преимущественно строчными буквами, хотя регистр букв не
влияет на интерпретацию инструкций языка.
2. Элементы в списках, например, имена полей и таблиц, должны быть
разделены запятыми.
3. Имена таблиц и полей заключаются в апострофы или двойные кавычки,
например, "First Name". Если имя не содержит пробелы и другие специальные
символы, то апострофы можно не указывать.
4. Точка с запятой в конце SQL-инструкций необязательна.
5. В SQL-запросе допускается комментарии. Комментарий ограничивается
символами /* и */.
1
1.2. Инструкция SELECT
Язык SQL имеет множество различных инструкций (команд), которые
позволяют выполнить всевозможные действия с БД. Но среди этих команд главной
является инструкция SELECT, которая предназначена для отбора нужных записей.
Инструкция SELECT имеет следующий упрощенный формат:
SELECT {<Список полей> }
FROM <Список таблиц>
[WHERE <Условия отбора> ]
[ORDER BY <Список полей для сортировки> ]
Результатом выполнения этого запроса является соответствующий этому
компоненту набор данных, т.е. часть данных из таблицы.
Обязательные операнды
В инструкцию SELECT обязательно включается список полей и операнд FROM,
остальные операнды могут отсутствовать. В списке операнда FROM перечисляются
имена таблиц, из которых отбираются записи. Список должен содержать как минимум
одну таблицу.
Пример. Отбор записей из таблицы Person.db
SELECT Name, Oklad, Post
FROM Person
В набор данных, формируемый в результате выполнения этого SQL-запроса,
включаются поля Name, Oklad и Post всех записей из таблицы Person.
Вычисляемые поля
Кроме
физических
полей
таблиц,
в
SELECT-запрос
можно
включать
вычисляемые поля. Для получения вычисляемого поля в списке полей указывается не
имя этого поля, а выражение, по которому рассчитывается его значение. Например,
SELECT "-"|| Name, Oklad, Oklad*1.5
FROM
Person
As Зарплата
Здесь через ключевое слово As вводится имя поля. Ввод имени поля не
обязателен, но в то же время информация отображается более наглядно. Результат
работы этого SQL-запроса представлен ниже:
2
1.3. Операнд WHERE инструкции SELECT
В предыдущих примерах в набор данных попадали все записи из указанных
таблиц.
На
практике
набор
данных
обычно
ограничивается
записями,
удовлетворяющими каким-либо определенным условиям отбора, задаваемым с
помощью операнда WHERE.
Критерий отбора записей представляет собой логическое выражение, в котором
можно использовать следующих операторов сравнения.
= - равно;
<= - меньше или равно;
> - больше;
<> - или != - не равно;
< - меньше;
!> - не больше;
>= - больше или равно;
!< - не меньше.
Пример 1. Отбор записей по значениям числового поля:
SELECT Name
FROM Person
WHERE Oklad >= 4000;
Эта инструкция задает получение списка сотрудников, имеющих оклад не менее
4000 рублей.
Пример 2. Отбор записей по значениям символьного поля:
SELECT Name
FROM Person
WHERE Post = 'Водитель';
В результате получим выборку с фамилиями водителей.
3
Функции работы со строками:
UPPER (Str) - преобразование символов строки Str к верхнему регистру;
LOWER (Str) - преобразование символов строки Str к нижнему регистру;
TRIM (Str) - удаление пробелов в начале и в конце строки Str;
SUBSTRING (str FROM n1 TO n2) - выделение из строки Str подстроки, которая
включает в себя символы, начиная с позиции n1 и заканчивая с позицией n2;
CAST (<Expression> AS <Type>) - приведение выражения Expression к типу
Type.
Пример 3. Отбор записей по значениям символьного поля:
SELECT Name
FROM Person
WHERE UPPER( TRIM(Post)) = 'ВОДИТЕЛЬ';
Функция TRIM удаляет из строкового значения начальные и конечные пробелы,
а функция UPPER приводит символы полученной строки к верхнему регистру.
В этом примере слово водитель приводится к верхнему регистру. В результате
слова Водитель и водитель рассматриваются как одно и то же.
Функции декодирования даты и времени:
EXTRACT (<Элемент> FROM <Выражение>) - из выражения, содержащего
значение даты и времени, извлекается значение, соответствующее указанному
элементу. В качестве элемента даты или времени можно указывать значения: YEAR,
MONTH, DAY, HOUR, MINUTE или SECOND.
Составление сложных условий отбора записей
Для составления более сложных условий отбора можно использовать
следующих операторов:
1. LIKE - сравнение по шаблону;
2. IS NULL - проверка на нулевое значение;
3. IN - проверка на вхождение;
4. BETWEEN - проверка на вхождение в диапазон.
Пример 4. Для сравнения строк вместо операций =,
!= и < > можно
использовать операцию LIKE, выполняющую сравнение по частичному совпадению.
Частичное совпадение значений целесообразно проверять, например, в случаях, когда
4
известны только начальная часть фамилии или названия предмета. Вот пример
соответствующего запроса:
SELECT Name
FROM Person
WHERE Name LIKE "Ко";
Здесь мы получаем список фамилий, начинающихся на Ко.
Пример 5. В выражениях операции LIKE допускается использование шаблона, в
котором разрешены все алфавитно-цифровые символы (с учетом регистра).
При этом два символа имеют специальное назначение:
% - замещение любого количества символов, в том числе и нулевого:
_ - замещение одного символа.
С помощью шаблона можно выполнить проверку на частичное совпадение не
только начальных символов строки, но и найти вхождение заданного фрагмента в
любую часть строкового значения. Например,
SELECT Name
FROM Person
WHERE Name LIKE "%" || "ко" || "%";
В приведенном запросе происходит отбор сотрудников, фамилии которых
содержит символы ко. Этому условию удовлетворяют, например, следующие фамилии:
Гречко, комаров, Ванькович и т.д.
Перед операцией LIKE можно использовать описатель NOT, который изменяет
результат выполнения операции на противоположное значение и проверяет значения
выражений на несовпадение.
Пример 6. Для проверки нулевого значения выражения служит операция IS
NULL, которая имеет следующий формат:
<Выражение> IS [NOT] NULL
Так, в запросе:
SELECT *
FROM Person
WHERE Oklad IS NULL;
выполняется отбор всех полей таблицы Person. Будут выбраны сотрудники, у которых
оклад не определен.
5
Пример 7. Проверка на вхождение значения выражения в список выполняется с
помощью операции IN следующего формата:
<Выражение> [NOT] IN (Список значений)
Эту операцию удобно использовать, если выражение может принимать
относительно
небольшое
количество
различных
значений.
Вот
пример
соответствующего запроса:
SELECT Name, Oklad
FROM Person
WHERE LOWER (Post) IN ("менеджер", "водитель");
В результате выполнения этого запроса получим выборку фамилий и окладов
всех менеджеров и водителей.
Пример 8. Операция BETWEEN
выполняет проверку вхождения значения в
диапазон и имеет формат:
<Выражение> [NOT] BETWEEN
<Минимальное значение> AND <Максимальное значение>
При использовании этой операции в набор данных включаются записи, для
которых значение выражения больше или равно минимальному, а также меньше или
равно максимальному значениям.
Рассмотрим запрос:
SELECT *
FROM Person
WHERE BirthDay BETWEEN "21.5.85" AND "27.5.1985"
В результате его выполнения получим набор записей, для которых дата рождения
сотрудника находится в диапазоне с 21 по 27 мая 1985 года. Значение даты
заключается в кавычки, в качестве разделителя используется точка. В некоторых
других реализациях языка SQL
в качестве разделителей допускается использовать
символы - и /. Значение года можно задавать как двумя (21.5.85), так и четырьмя
(27.5.2002) цифрами. В значениях дня и месяца можно опускать незначащий ноль.
Формирование нескольких условий отбора записей
При отборе можно использовать несколько операций, задавая тем самым
сложные критерии отбора записей. При этом между собой условия отбора должны
соединяться с помощью логических операций AND, OR и NOT.
6
Пример 9. Запрос со сложным критерием отбора:
SELECT *
FROM Person
WHERE (Oklad > 5000) AND
(BirtDay BETWEEN "1.01.45" AND "31.12.1988")
Здесь символ "*" означает выбор всех полей таблицы Person. При выполнении
этого запроса происходит отбор записей о сотрудниках, у которых оклад выше 5000
рублей и возраст которых на данный момент с 18 до 60 лет.
1.5. Функции языка SQL для вычисления итоговых величин
Язык SQL, как и другие языки, предоставляет для использования ряд функций
для вычисления итоговых величин.
AVG ( ) - среднее значение;
MAX ( ) - максимальное значение;
MIN ( ) - минимальное значение;
SUM ( ) - сумма;
COUNT ( ) - количество значений;
COUNT (*) - количество ненулевых значений;
Эти функции используются в SELECT-запросе. Ниже приведем использование
некоторых из этих функций.
Пример
Изменим форму предыдущих примеров. Для этого разместите на форме
компонент Query2, DataSource2 и DBGrid2 и измените их свойства. Вид работающего
приложения представлен ниже:
7
Свойство SQL компонента Query2 будем определять в обработчике кнопки
Вычислить:
procedure TForm1.Button1Click(Sender: TObject);
begin
Query2.Close;
Query2.SQL.Clear;
Query2.SQL.Add(‘SELECT SUM(Oklad) As Сумма, ' +
'AVG(Oklad) As Среднее FROM Person');
Query2.Open;
Edit1.Text := Query2.FieldByName('Сумма').AsString;
Edit2.Text := Query2.FieldByName('Среднее').AsString;
end;
Как видно из кода программы, параметрами функций являются название поля
Oklad, а вычисляемое значение возвращается через ключевое слово As название поля.
Так как Query2 связан с сеткой DGrid2, поэтому сумма и среднее значение
представляется в виде таблицы с одной записью.
К полям этой таблицы можно обращаться как к полям обычной таблицы. В
примере использован метод FieldByName для вывода суммы и среднего значения в
текстовых полях.
Группирование записей
Итоговых величин
можно вычислить и вывести по группам. Например,
вычислить сумму окладов и среднее значение оклада для каждого отдела. Для этого
следует использовать операнд Group By.
Ниже приведем SQL-запрос, который выполняет такой запрос для таблицы Person:
SELECT DepID, SUM(Oklad) As Сумма, AVG(Oklad) As Среднее
FROM Person
GROUP BY DepID
Измените SQL-запрос для Query2 в режиме проектирования, а также удалите из
формы текстовых полей и кнопку Вычислить. Тогда на форме появятся следующие
данные:
8
Как видно, итоговые величины выводятся для каждого отдела, что достаточно
удобно.
Совместно с операндом GROUP BY можно использовать операнд HAVING, с
помощью которого задаются дополнительные условия выбора записей. Условие
операнда HAVING похоже на условие WHERE, однако это условие может
использоваться только в сочетании с операндом GROUP BY.
Условие HAVING должен содержать только те поля, которые присутствуют в
списке SELECT. В ней можно использовать итоговые функции, отсутствующие в
списке SELECT. Следующий пример демонстрирует применение условия HAVING:
SELECT DepID, SUM(Oklad) As Сумма, AVG(Oklad) As Среднее
FROM Person
GROUP BY DepID
HAVING SUM(Oklad) >=5000
Результат выполнения этого запроса представлен ниже. Как видно, для второго
отдела итоговые величины не представлены.
9
1.4. Операнд ORDER BY инструкции SELECT
Список полей, по которым выполняется сортировка, указывается в операнде
ORDER BY. Порядок полей в этом операнде определяет порядок сортировки: сначала
записи упорядочиваются по значениям поля, указанного в этом списке первым, затем
записи, имеющие одинаковые значение первого поля, упорядочиваются по второму
полю и т.д.
По умолчанию сортировка происходит в порядке возрастания значений полей.
Для указания обратного порядка сортировки по какому-либо полю нужно указать после
имени этого поля описатель DESC.
Пример 1. Запрос на сортировку записей:
SELECT *
FROM Person
ORDER BY Name
Сортировка записей задана по полю Name.
Пример 2. Сортировка по двум полям:
SELECT Name, Post, Oklad
FROM Person
ORDER BY Post, Oklad DESC
В набор данных входят поля Name, Post и Oklad всех записей. Записи
отсортированы по полям Post и Oklad, при этом по полю Oklad
выполняется в порядке убывания значений.
10
упорядочивание
Пример. Сортировки записей таблицы с применением SQL - запроса
При
разработке
приложения
управление
сортировкой
осуществляется
посредством различных элементов формы, например, кнопок и переключателей.
Рассмотрим следующий пример. Пользователь управляет сортировкой с
помощью двух групп переключателей: в первой задается вид, во второй - направление
сортировки. Сортировка выполняется после нажатия кнопки Отсортировать. На рис.
1. показан вид формы.
На этапе проектирования на форму следует разместить компонентов Query,
DataSource1, DBGrid1 и изменить их свойства, так чтобы связывать этих компонент
взаимно.
Ниже приводится код обработчика события нажатия кнопки Button2.
procedure TForm1.Button1Click(Sender: TObject);
var s: string;
begin
Query1.Close;
Query1.SQL.Clear;
Query1.SQL.Add('SELECT
*
FROM
Case RadioGroup1.ItemIndex of
0: s := 'ORDER BY Name'
1: s := 'ORDER BY Post'
2: s := 'ORDER BY Oklad'
3: s := '';
end;
Case
0:
1:
end;
RadioGroup2.ItemIndex
s := s + '';
s := s + ' DESC';
11
of
Person.db ');
;
;
;
Query1.SQL.Add(s);
Query1.Open;
end;
Запустите приложение, все работает так, как и было запланировано.
Сортировка при щелчке на заголовке поля
В практических приложениях бывает удобно провести сортировку при нажатии
на заголовке поля. Например, при щелчке на заголовке поля Name таблица сортируется
по полю Name, а при щелчке на заголовке поля Oklad таблица сортируется по окладу
служащих. Для этого следует написать обработчик события OnTitleClick компонента
DBGrid. Ниже приведем реализацию этого обработчика, который сортирует таблицу по
различным полям.
procedure TForm1.DBGrid1TitleClick(Column: TColumn);
var Sql_Str: string;
begin
Sql_Str := 'Select * FROM Person Order By ' + Column.FieldName;
Query1.Close;
Query1.SQL.Clear;
Query1.SQL.Add(Sql_Str);
ShowMessage(Query1.Text); //Отладочная строка
Query1.Open;
end;
Как видно, название поля берется из параметра Column с помощью свойства
FieldName.
Отладочная
строка
удобна
тем,
чтобы
убедиться
правильности
сформированного SQL-запроса. Запустите приложение, как видно, все работает как и
запланировано.
§ 2. Фильтры и выделение диапазонов
2.1. Фильтры
Просматривать таблицы, содержащее большое число записей, неудобно. Часто
требуется выделить только группу записей, связанных некоторым условием. Например,
пусть нам требуется вывести список служащих, у которых оклад меньше или равен
2000 рублей (социально не защищенная часть служащих). Для этого таблицу надо
отфильтровать по заданному условию.
12
Такое условие (оно называется запросом) записывается в свойстве Fillter
компоненты Table в виде текстовой строки и содержит логическое выражение, в
котором используются поля текущего набора данных, константы, логические
отношения.
Например, чтобы выявить всех служащих, у которых оклад меньше или равен
2000 рублей, надо записать в свойство Filter следующую строку:
Oklad <= 2000
При запуске программы в списке DBGrid1 отображаются только записи,
удовлетворяющие данному условию. Если строка заносится в свойство Filter не на
этапе проектирования, а во время работы программы (например, по щелчку на кнопке),
то отбор нужных записей выполняется динамически.
Внимание. Чтобы разрешить фильтрацию значений, надо свойство Filtered
компоненты Table установить равным True.
При составлении выражения для фильтра могут быть использованы операции
сравнения: < , > , = , < = , > = , < >. Для составления сложных условий фильтрации
могут быть использованы логические операции AND, OR и NOT. Например,
Oklad >= 2000 AND Oklad <= 10000
Параметры фильтрации задаются с помощью свойства FilterOptions. Это
свойство принадлежит к множественному типу и может принимать комбинации двух
значений:
1. foCaseInsensitive определяет, будет ли учитываться регистр при сравнении
текстовой информации;
2.
foNoPartialCompare
позволяет
выполнить
фильтрацию
по
полному
совпадению значений текстовых полей или считать строки одинаковыми, если одна из
них заканчивается звездочкой. Например, таких строк:
'123*' = '12345'
Пример. Предположим, что на форме Form1 имеется текстовое поле, где
вводится условие фильтрации и две кнопки (рис. 10).
При нажатии кнопки Фильтровать происходит фильтрация записей согласно
введенного в текстовое поле условие фильтрации. При нажатии кнопки Все записи
будут отображаться все записи таблицы. При запуске программы фильтрация
отключается.
13
Рис. 10. Фильтрация записей
Код обработчиков этих событий имеет вид:
procedure TForm1.FormCreate(Sender: TObject);
begin
Query1.FilterOptions := [foCaseInsensitive];
Query1.Filtered := False;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
Query1.Filtered := False; // При нажатии на кнопке Все записи
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
Query1.Filtered := True; // При нажатии на кнопке Фильтровать
Query1.Filter := Edit1.Text;
end;
Запускайте приложение на выполнение и введите в текстовое поле разные
условия фильтрации.
Фильтр и операнд WHERE перекрывают друг друга по функциональности. Но у
них действие разное. Условие WHERE позволяет отобрать набор данных из базы
данных, а фильтр позволяет отобразить часть отобранных данных.
2.2. Использование фильтров для поиска записей
Фильтры удобно использовать для последовательного поиска значений
текстовых полей. Для этого в текстовое поле вводится первые буквы значения поля
(рис. 11).
14
Рис. 11. Использование фильтра для поиска записей
Обработчик события OnChange для текстового поля формирует фильтр и
устанавливает текущий фильтр.
procedure TForm1.Edit1Change(Sender: TObject);
begin
If length(Edit1.Text)<>0 then
begin
Query1.Filter := 'Name=' + ''''+ Edit1.Text +'*' + '''';
Query1.Filtered:=true;
end
else Query1.Filtered := False;
end;
На языке Паскаль для указания символа “апостроф” в строке символов
применяют двойной апостроф. Поэтому строка '''' определяет строку, которая содержит
лишь один символ – апостроф. Этот апостроф необходим для формирования
выражения для фильтра, похожего например, Name=’Иванов’.
Как видно из работы этой программы, по мере ввода первых букв фамилии в
таблице остаются только те записи, которые начинаются от введенных букв.
§ 3. Поиск записи в таблице
Поиск записи, удовлетворяющей определенным условиям, означает переход на
эту запись. Для поиска записей служит функция Locate. Вызов этой функции
рассмотрим на примерах:
1. Пример поиска по одному полю:
Query1.Locate ('Name', 'Иванов', [ ]);
15
Поиск
выполняется по полю Name и ищется первая запись, для которой
значением этого поля является строка 'Иванов'. В квадратных скобках могут быть
указаны параметры поиска. В этом примере все параметры поиска отключены.
Параметр поиска принадлежит к множественному типу и может принимать
комбинации следующих значений:
- loCaseInsensitive - регистр букв не учитывается;
- loPartialKey - допускается частичное совпадение значений.
Если удовлетворяющее условиям поиска записи существуют, то указатель
текущей записи устанавливается на первую из них. Если запись найдена, функция
возвращает значение True, в противном случае - значение False. В этом примере
возвращаемое значение не контролируется.
2. Поиск по нескольким полям.
При поиске по нескольким полям в методе Locate параметр искомые значения
полей является массивом значений типа Variant, в котором содержится несколько
элементов. Для приведения к типу вариантного массива используется функция
VarArrayOf. Значения разделяются запятыми и заключаются в квадратные скобки,
порядок значений должен соответствовать порядку полей, значения которых ищется.
Например,
Query1.Locate ('Name; Oklad', VarArrayOf (['И', 5000]),
[loPartialKey ]);
Поиск выполняется по полям Name и Oklad, ищется первая запись, для которой
значение поля Name начинается с буквы 'И', а значение поля Oklad равен 5000.
Результат поиска не анализируется.
Пример. Если включен параметр loPartialKey, то в процессе поиска
допускаются частичные совпадения значений. Используя это, можно организовать
поиск путем последовательного приближения к требуемой записи (только для
текстовых полей).
Поле для поиска выберем с помощью переключателей, а условие поиска вводим в
текстовое поле Edit1 (рис. 12). При вводе текста в это поле происходит событие OnChange. Код обработчика этого события имеет вид:
16
Рис. 12. Вид приложения для поиска записей
procedure TForm1.Edit1Change(Sender: TObject);
begin
// Если не установлен флажок поиска
if not CheckBox1.Checked then Exit;
// Если условие поиска не введено
if Length(Edit1.text)=0 then Exit;
Case RadioGroup1.ItemIndex of
0: Query1.Locate ('Name', Edit1.Text,
[loCaseInsensitive, loPartialKey]);
1: Query1.Locate('Phone', StrToInt(Edit1.Text),[ ]);
2: Query1.Locate('Oklad', StrToCurr(Edit1.Text), [ ]);
end; // end of Case
end;
procedure TForm1.CheckBox1Click(Sender: TObject);
begin
Edit1Change(Sender);
end;
procedure TForm1.RadioGroup1Click(Sender: TObject);
begin
Edit1Change(Sender);
end;
Как видно, при выполнении этой программы, действие параметра loPartialKey
распространяется только на текстовые поля. При вводе значения текстового поля
найдется значение записи, которое частично совпадает условием поиска. Для полей
других типов запись найдется только при полном совпадении значения.
Также заметим, что действие параметра loCaseInsensitive не распространяется на
русские буквы, что сильно уменьшает эффективности поиска для русскоязычных
текстовых полей.
17
Download