Создание баз данных в Delphi
Урок 1: Настройка BDE
Содержание урока 1:
Обзор 2
Сущность BDE 2
Алиасы 2
Системная информация утилиты настройки BDE 4
Заключение 5
Обзор
На этом уроке мы познакомимся с ядром баз данных компании Борланд - Borland Database Engine (BDE), а также научимся создавать и редактировать алиасы - механизм, облегчающий связь с базами данных. Кроме того, мы изучим, как конфигурировать ODBC драйверы.
Сущность BDE
Мощность и гибкость Delphi при работе с базами данных основана на низкоуровневом ядре - процессоре баз данных Borland Database Engine (BDE). Его интерфейс с прикладными программами называется Integrated Database Application Programming Interface (IDAPI). В принципе, сейчас не различают эти два названия (BDE и IDAPI) и считают их синонимами. BDE позволяет осуществлять доступ к данным как с использованием традиционного record-ориентированного (навигационного) подхода, так и с использованием set-ориентированного подхода, используемого в SQL-серверах баз данных. Кроме BDE, Delphi позволяет осуществлять доступ к базам данных, используя технологию (и, соответственно, драйверы) Open DataBase Connectivity (ODBC) фирмы Microsoft. Но, как показывает практика, производительность систем с использованием BDE гораздо выше, чем оных при использовании ODBC. ODBC драйвера работают через специальный “ODBC socket”, который позволяет встраивать их в BDE.
Все инструментальные средства баз данных Borland - Paradox, dBase, Database Desktop - используют BDE. Все особенности, имеющиеся в Paradox или dBase, “наследуются” BDE, и поэтому этими же особенностями обладает и Delphi.
Алиасы
Таблицы сохраняются в базе данных. Некоторые СУБД сохраняют базу данных в виде нескольких отдельных файлов, представляющих собой таблицы (в основном, все локальные СУБД), в то время как другие состоят из одного файла, который содержит в себе все таблицы и индексы (InterBase). Например, таблицы dBase и Paradox всегда сохраняются в отдельных файлах на диске. Каталог, содержащий dBase .DBF файлы или Paradox .DB файлы, рассматривается как база данных. Другими словами, любой каталог, содержащий файлы в формате Paradox или dBase, рассматривается Delphi как единая база данных. Для переключения на другую базу данных нужно просто переключиться на другой каталог. Как уже было указано выше, InterBase сохраняет все таблицы в одном файле, имеющем расширение .GDB, поэтому этот файл и есть база данных InterBase.
Удобно не просто указывать путь доступа к таблицам базы данных, а использовать для этого некий заменитель - псевдоним, называемый алиасом. Он сохраняется в отдельном конфигурационном файле в произвольном месте на диске и позволяет исключить из программы прямое указание пути доступа к базе данных. Такой подход дает возможность располагать данные в любом месте, не перекомпилируя при этом программу. Кроме пути доступа, в алиасе указываются тип базы данных, языковый драйвер и много другой управляющей информации. Поэтому использование алиасов позволяет легко переходить от локальных баз данных к SQL-серверным базам (естественно, при выполнении требований разделения приложения на клиентскую и серверную части).
Для создания алиаса запустите утилиту конфигурации BDE (программу bdeadmin.exe), находящуюся в каталоге, в котором располагаются динамические библиотеки BDE.
Рис. 1: Главное окно утилиты конфигурации BDE
Главное окно утилиты настройки BDE имеет вид, изображенный на рис.1. Для создания алиаса выберите в меню “Object” пункт “New”. В появившемся диалоговом окне выберите имя драйвера базы данных. Тип алиаса может быть стандартным (STANDARD) для работы с локальными базами в формате dBase или Paradox или соответствовать наименованию SQL-сервера (InterBase, Sybase, Informix, Oracle и т.д.).
Рис. 2: В диалоговом окне добавления нового алиаса можно указать тип базы данных
После создания нового алиаса следует дать ему имя. Это можно сделать с помощью подпункта “Rename” меню “Object”. Однако просто создать алиас не достаточно. Вам нужно указать дополнительную информацию, содержание которой зависит от типа выбранной базы данных. Например, для баз данных Paradox и dBase (STANDARD) требуется указать лишь путь доступа к данным, имя драйвера и флаг ENABLE BCD, который определяет, транслирует ли BDE числа в двоично-десятичном формате (значения двоично-десятичного кода устраняют ошибки округления): TYPE | STANDARD |
DEFAULT DRIVER | PARADOX |
ENABLE BCD | FALSE |
PATH | c:\users\data |
SQL-сервер InterBase и другие типы баз данных требуют задания большого количества параметров, многие из которых можно оставить установленными по умолчанию.
Системная информация утилиты настройки BDE
Итак, мы познакомились с наиболее важной возможностью утилиты настройки BDE - созданием и редактированием алиасов, определяющих параметры доступа к базам данных. Однако, утилита настройки BDE позволяет специфицировать не только алиасы, но и драйверы для доступа к базам данных, а также различную системную информацию, составляющую операционное окружение этих самых алиасов.
Рассмотрим, например, системную информацию драйвера PARADOX:
NET DIR. Параметр содержит расположение каталога сетевого управляющего файла. Он нужен для того, чтобы обратиться к таблице PARADOX на сетевом диске.
VERSION. Номер версии драйвера.
TYPE. Тип драйвера.
LANGDRIVER. Языковой драйвер, определяющий множество допустимых символов.
BLOCK SIZE. Размер блока на диске, используемого для запоминания одной записи.
FILL FACTOR. Содержит процент от блока на текущем диске. Параметр нужен для создания индексных файлов.
LEVEL. Параметр определяет тип формата таблицы, используемой для создания временных таблиц.
STRICTINTEGRTY. Параметр использования ссылочной целостности. Если он равен TRUE, то вы не можете изменить таблицу с ссылочной целостностью, а если FALSE, то можете, но рискуете нарушить целостность данных.
Как уже отмечалось выше, утилита настройки BDE сохраняет всю конфигурационную информацию в файле IDAPI.CFG. Этот файл с предустановленными ссылками на драйверы и некоторыми стандартными алиасами создается при установке Delphi. Кроме того, он создается при установке файлов редистрибуции BDE (т.е. когда Вы переносите BDE и SQL Links на другие компьютеры).
Заключение
Итак, на данном уроке мы постарались понять для, что такое BDE, изучили очень важное для работы с базами данных понятие - алиас, а также научились настраивать его параметры для корректной работы программы на примере драйвера PARADOX.
5
Урок 1: Настройка BDE
Создание баз данных в Delphi
Урок 2: Создание таблиц с помощью Database Desktop
Содержание урока 2:
Обзор 2
Утилита Database Desktop 2
Заключение 9
Обзор
На данном уроке мы изучим, как создавать таблицы базы данных с помощью утилиты Database Desktop, входящей в поставку Delphi. Хотя для создания таблиц можно использовать различные средства (SQL - компонент TQuery и компонент TTable), применение этой утилиты позволяет создавать таблицы в интерактивном режиме и сразу же просмотреть их содержимое - и все это для большого числа форматов. Это особенно удобно для локальных баз данных, в частности Paradox и dBase.
Утилита Database DesktopDatabase Desktop - это утилита, во многом похожая на Paradox, которая поставляется вместе с Delphi для интерактивной работы с таблицами различных форматов локальных баз данных - Paradox и dBase, а также SQL-серверных баз данных InterBase, Oracle, Informix, Sybase (с использованием SQL Links). Исполняемый файл утилиты называется DBD32.EXE. Для запуска Database Desktop просто дважды щелкните по ее иконке.
Рис. 1: Выпадающий список в диалоговом окне Table Type позволяет выбрать тип создаваемой таблицы
После старта Database Desktop выберите команду меню File|New|Table для создания новой таблицы. Перед Вами появится диалоговое окно выбора типа таблицы, как показано на рис.1. Вы можете выбрать любой формат из предложенного, включая различные версии одного и того же формата.
После выбора типа таблицы Database Desktop представит Вам диалоговое окно, специфичное для каждого формата, в котором Вы сможете определить поля таблицы и их тип, как показано на рис.2.
Рис. 2: Database Desktop позволяет задать имена и типы полей в таблице
Имя поля в таблице формата Paradox представляет собой строку, написание которой подчиняется следующим правилам:
Имя должно быть не длиннее 25 символов.
Имя не должно начинаться с пробела, однако может содержать пробелы. Однако, если Вы предполагаете в будущем переносить базу данных в другие форматы, разумнее будет избегать включения пробелов в название поля. Фактически, в целях переносимости лучше ограничиться девятью символами в названии поля, не включая в него пробелы.
Имя не должно содержать квадратные, круглые или фигурные скобки [], () или {}, тире, а также комбинацию символов “тире” и “больше” (->).
Имя не должно быть только символом #, хотя этот символ может присутствовать в имени среди других символов. Хотя Paradox поддерживает точку (.) в названии поля, лучше ее избегать, поскольку точка зарезервирована в Delphi для других целей.
Имя поля в таблице формата dBase представляет собой строку, написание которой подчиняется правилам, отличным от Paradox:
Имя должно быть не длиннее 10 символов.
Пробелы в имени недопустимы.
Таким образом, Вы видите, что имена полей в формате dBase подчиняются гораздо более строгим правилам, нежели таковые в формате Paradox. Однако, мы еще раз хотим подчеркнуть, что если перед Вами когда-либо встанут вопросы совместимости, то лучше сразу закладывать эту совместимость - давать полям имена, подчиняющиеся более строгим правилам.
Укажем еще правила, которым подчиняется написание имен полей в формате InterBase.
Имя должно быть не длиннее 31 символа.
Имя должно начинаться с букв A-Z, a-z.
Имя поля может содержать буквы (A-Z, a-z), цифры, знак $ и символ подчеркивания (_).
Пробелы в имени недопустимы.
Для имен таблиц запрещается использовать зарезервированные слова InterBase.
Следующий (после выбора имени поля) шаг состоит в задании типа поля. Типы полей очень сильно различаются друг от друга, в зависимости от формата таблицы. Для получения списка типов полей перейдите к столбцу “Type”, а затем нажмите пробел или щелкните правой кнопкой мышки. Приведем списки типов полей, характерные для форматов Paradox, dBase и InterBase.
Итак, поля таблиц формата Paradox могут иметь следующий тип (для ввода типа поля можно набрать только подчеркнутые буквы или цифры):
Табл. A: Типы полей формата Paradox
Alpha | строка длиной 1-255 байт, содержащая любые печатаемые символы |
Number | числовое поле длиной 8 байт, значение которого может быть положительным и отрицательным. Диапазон чисел - от 10-308 до 10308 с 15 значащими цифрами |
$ (Money) | числовое поле, значение которого может быть положительным и отрицательным. По умолчанию, является форматированным для отображения десятичной точки и денежного знака |
Short | числовое поле длиной 2 байта, которое может содержать только целые числа в диапазоне от -32768 до 32767 |
Long Integer | числовое поле длиной 4 байта, которое может содержать целые числа в диапазоне от -2147483648 до 2147483648 |
# (BCD) | числовое поле, содержащее данные в формате BCD (Binary Coded Decimal). Скорость вычислений немного меньше, чем в других числовых форматах, однако точность - гораздо выше. Может иметь 0-32 цифр после десятичной точки |
Date | поле даты длиной 4 байта, которое может содержать дату от 1 января 9999 г. до нашей эры - до 31 декабря 9999 г. нашей эры. Корректно обрабатывает високосные года и имеет встроенный механизм проверки правильности даты |
Time | поле времени длиной 4 байта, содержит время в миллисекундах от полуночи и ограничено 24 часами |
@ (Timestamp) | обобщенное поле даты длиной 8 байт - содержит и дату и время |
Memo | поле для хранения символов, суммарная длина которых более 255 байт. Может иметь любую длину. При этом размер, указываемый при создании таблицы, означает количество символов, сохраняемых в таблице (1-240) - остальные символы сохраняются в отдельном файле с расширением .MB |
Formatted Memo | поле, аналогичное Memo, с добавлением возможности задавать шрифт текста. Также может иметь любую длину. При этом размер, указываемый при создании таблицы, означает количество символов, сохраняемых в таблице (0-240) - остальные символы сохраняются в отдельном файле с расширением .MB. Однако, Delphi в стандартной поставке не обладает возможностью работать с полями типа Formatted Memo |
Graphic | поле, содержащее графическую информацию. Может иметь любую длину. Смысл размера - такой же, как и в Formatted Memo. Database Desktop “умеет” создавать поля типа Graphic, однако наполнять их можно только в приложении |
OLE | поле, содержащее OLE-данные (Object Linking and Embedding) - образы, звук, видео, документы - которые для своей обработки вызывают создавшее их приложение. Может иметь любую длину. Смысл размера - такой же, как и в Formatted Memo. Database Desktop “умеет” создавать поля типа OLE, однако наполнять их можно только в приложении. Delphi “напрямую” не умеет работать с OLE-полями, но это легко обходится путем использования потоков |
Logical | поле длиной 1 байт, которое может содержать только два значения - T (true, истина) или F (false, ложь). Допускаются строчные и прописные буквы |
+ (Autoincrement) | поле длиной 4 байта, содержащее нередактируемое (read-only) значение типа long integer. Значение этого поля автоматически увеличивается (начиная с 1) с шагом 1 - это очень удобно для создания уникального идентификатора записи (физический номер записи не может служить ее идентификатором, поскольку в Парадоксе таковой отсутствует. В InterBase также отсутствуют физические номера записей, но отсутствует и поле Autoincrement. Его с успехом заменяет встроенная функция Gen_id, которую удобней всего применять в триггерах) |
Binary | поле, содержащее любую двоичную информацию. Может иметь любую длину. При этом размер, указываемый при создании таблицы, означает количество символов, сохраняемых в таблице (0-240) - остальные символы сохраняются в отдельном файле с расширением .MB. Это полнейший аналог поля BLOb в InterBase |
Bytes | строка цифр длиной 1-255 байт, содержащая любые данные |
Поля таблиц формата dBase могут иметь следующий тип (для ввода типа поля можно набрать только подчеркнутые буквы или цифры):
Табл. B: Типы полей формата dBase
Character (alpha) | строка длиной 1-254 байт, содержащая любые печатаемые символы |
Float (numeric) | числовое поле размером 1-20 байт в формате с плавающей точкой, значение которого может быть положительным и отрицательным. Может содержать очень большие величины, однако следует иметь в виду постоянные ошибки округления при работе с полем такого типа. Число цифр после десятичной точки (параметр Dec в DBD) должно быть по крайней мере на 2 меньше, чем размер всего поля, поскольку в общий размер включаются сама десятичная точка и знак |
Number (BCD) | числовое поле размером 1-20 байт, содержащее данные в формате BCD (Binary Coded Decimal). Скорость вычислений немного меньше, чем в других числовых форматах, однако точность - гораздо выше. Число цифр после десятичной точки (параметр Dec в DBD) также должно быть по крайней мере на 2 меньше, чем размер всего поля, поскольку в общий размер включаются сама десятичная точка и знак |
Date | поле даты длиной 8 байт. По умолчанию, используется формат короткой даты (ShortDateFormat) |
Logical | поле длиной 1 байт, которое может содержать только значения “истина” или “ложь” - T,t,Y,y (true, истина) или F,f,N,n (false, ложь). Допускаются строчные и прописные буквы. Таким образом, в отличие от Парадокса, допускаются буквы “Y” и “N” (сокращение от Yes и No) |
Memo | поле для хранения символов, суммарная длина которых более 255 байт. Может иметь любую длину. Это поле хранится в отдельном файле. Database Desktop не имеет возможности вставлять данные в поле типа Memo |
OLE | поле, содержащее OLE-данные (Object Linking and Embedding) - образы, звук, видео, документы - которые для своей обработки вызывают создавшее их приложение. Может иметь любую длину. Это поле также сохраняется в отдельном файле. Database Desktop “умеет” создавать поля типа OLE, однако наполнять их можно только в приложении. Delphi “напрямую” не умеет работать с OLE-полями, но это легко обходится путем использования потоков |
Binary | поле, содержащее любую двоичную информацию. Может иметь любую длину. Данное поле сохраняется в отдельном файле с расширением .DBT. Это полнейший аналог поля BLOb в InterBase |
Поля таблиц формата InterBase могут иметь следующий тип:
Табл. C: Типы полей формата InterBase
SHORT | числовое поле длиной 2 байта, которое может содержать только целые числа в диапазоне от -32768 до 32767 |
LONG | числовое поле длиной 4 байта, которое может содержать целые числа в диапазоне от -2147483648 до 2147483648 |
FLOAT | числовое поле длиной 4 байта, значение которого может быть положительным и отрицательным. Диапазон чисел - от 3.4*10-38 до 3.4*1038 с 7 значащими цифрами |
DOUBLE | числовое поле длиной 8 байт (длина зависит от платформы), значение которого может быть положительным и отрицательным. Диапазон чисел - от 1.7*10-308 до 1.7*10308 с 15 значащими цифрами |
CHAR | строка символов фиксированной длины (0-32767 байт), содержащая любые печатаемые символы. Число символов зависит от Character Set, установленного в InterBase для данного поля или для всей базы данных (например, для символов в кодировке Unicode число символов будет в два раза меньше длины строки) |
VARCHAR | строка символов переменной длины (0-32767 байт), содержащая любые печатаемые символы. Число символов также зависит от Character Set, установленного в InterBase для данного поля или для всей базы данных |
DATE | поле даты длиной 8 байт, значение которого может быть от 1 января 100 года до 11 декабря 5941 года (время также содержится) |
BLOB | поле, содержащее любую двоичную информацию. Может иметь любую длину. Database Desktop не имеет возможности редактировать поля типа BLOB |
ARRAY | поле, содержащее массивы данных. InterBase позволяет определять массивы, имеющие размерность 16. Поле может иметь любую длину. Однако, Database Desktop не имеет возможности не только редактировать поля типа ARRAY, но и создавать их |
TEXT BLOB | подтип BLOB-поля, содержащее только текстовую информацию. Может иметь любую длину. Database Desktop не имеет возможности редактировать поля типа TEXT BLOB |
Типы полей могут отличаться от приведенных выше. Это зависит от версии драйвера базы данных.
Итак, мы изучили все типы полей, являющиеся “родными” для Delphi.
После этого для таблиц Paradox мы можем определить поля, составляющие первичный ключ, причем все они должны быть в начале записи, а первое поле, входящее в ключ, должно быть первым полем в записи. Для этого достаточно по ней дважды щелкнуть мышкой или нажать любую клавишу.
После создания таблицы, с ней можно связать некоторые свойства, перечень которых зависит от формата таблицы. Так, для таблиц формата Paradox можно задать:
Validity Checks (проверка правильности) - относится к полю записи и определяет минимальное и максимальное значение, а также значение по умолчанию. Кроме того, позволяет задать маску ввода
Table Lookup (таблица для “подсматривания”) - позволяет вводить значение в таблицу, используя уже существующее значение в другой таблице
Secondary Indexes (вторичные индексы) - позволяют доступаться к данным в порядке, отличном от порядка, задаваемого первичным ключом
Referential Integrity (ссылочная целостность) - позволяет задать связи между таблицами и поддерживать эти связи на уровне ядра. Обычно задается после создания всех таблиц в базе данных
Password Security (парольная защита) - позволяет закрыть таблицу паролем
Table Language (язык таблицы) - позволяет задать для таблицы языковый драйвер.
В таблицах dBase не существует первичных ключей. Однако, это обстоятельство можно преодолеть путем определения уникальных (Unique) и поддерживаемых (Maintained) индексов (Indexes). Кроме того, для таблиц dBase можно определить и язык таблицы (Table Language) - языковый драйвер, управляющий сортировкой и отображением символьных данных.
Определения дополнительных свойств таблиц всех форматов доступны через кнопку “Define” (для таблиц InterBase данная кнопка называется “Define Index...” и позволяет определять лишь только индекс, но не первичный ключ) в правой верхней части окна (группа Table Properties). Причем, все эти действия можно проделывать не только при создании таблицы, но и тогда, когда она уже существует. Для этого используется команда Table|Restructure Table (для открытой в данный момент таблицы) или Utilities|Restructure (с возможностью выбора таблицы). Однако, если Вы желаете изменить структуру или добавить новые свойства для таблицы, которая в данный момент уже используется другим приложением, Database Desktop откажет Вам в этом, поскольку данная операция требует монопольного доступа к таблице. Но зато все произведенные в структуре изменения сразу же начинают “работать” - например, если Вы определите ссылочную целостность для пары таблиц, то при попытке вставить в дочернюю таблицу данные, отсутствующие в родительской таблице, в Delphi возникнет исключительное состояние.
В заключение отметим еще часто используемую очень полезную возможность Database Desktop. Создавать таблицу любого формата можно не только “с чистого листа”, но и путем копирования структуры уже существующей таблицы. Для этого достаточно воспользоваться кнопкой “Borrow”, имеющейся в левом нижнем углу окна. Появляющееся диалоговое окно позволит Вам выбрать существующую таблицу и включить/выключить дополнительные опции, совпадающие с уже перечисленными свойствами таблиц. Это наиболее легкий способ создания таблиц.
Заключение
Итак, на данном уроке мы познакомились со штатной утилитой, используемой для интерактивного создания и модификации таблиц различной структуры. И хотя управление таблицами можно осуществлять с помощью различных средств (компонент TTable, компонент TQuery), данная утилита позволяет делать это в интерактивном режиме наиболее простым способом.
9
Урок 2: Создание таблиц с помощью Database Desktop
Создание баз данных в Delphi
Урок 3: Создание таблиц с помощью SQL-запросов
Содержание урока 3:
Обзор 2
Создание таблиц с помощью SQL 2
Заключение 6
Обзор
На данном уроке мы познакомимся еще с одной возможностью создания таблиц - через посылку SQL-запросов. Как Вы, наверное, могли заметить на предыдущем уроке, Database Desktop не обладает всеми возможностями по управлению SQL-серверными базами данных. Поэтому с помощью Database Desktop удобно создавать или локальные базы данных или только простейшие SQL-серверные базы данных, состоящие из небольшого числа таблиц, не очень сильно связанных друг с другом. Если же Вам необходимо создать базу данных, состоящую из большого числа таблиц, имеющих сложные взаимосвязи, можно воспользоваться языком SQL. При этом можно воспользоваться компонентом Query в Delphi, каждый раз посылая по одному SQL-запросу, а можно записать всю последовательность SQL-предложений в один так называемый скрипт и послать его на выполнение. Конечно, для этого нужно хорошо знать язык SQL, но, уверяю Вас, сложного в этом ничего нет! Конкретные реализации языка SQL незначительно отличаются в различных SQL-серверах, однако базовые предложения остаются одинаковыми для всех реализаций.
Создание таблиц с помощью SQLЕсли Вы хотите воспользоваться компонентом TQuery, сначала поместите его на форму. После этого настройте свойство DatabaseName на нужный Вам алиас. После этого можно ввести SQL-предложение в свойство SQL. Для выполнения запроса, изменяющего структуру, вставляющего или обновляющего данные на сервере, нужно вызвать метод ExecSQL компонента TQuery. Для выполнения запроса, получающего данные с сервера (т.е. запроса, в котором основным является оператор SELECT), нужно вызвать метод Open компонента TQuery. Это связано с тем, что BDE при посылке запроса типа SELECT открывает так называемый курсор, с помощью которого осуществляется навигация по выборке данных (подробней об этом см. в уроке, посвященном TQuery).
Приведем упрощенный синтаксис SQL-предложения для создания таблицы на SQL-сервере InterBase (более полный синтаксис можно посмотреть в online-справочнике по SQL, поставляемом с локальным InterBase):
CREATE TABLE table
( [, | ...]);
где
table - имя создаваемой таблицы,
- описание поля,
- описание ограничений и/или ключей (квадратные скобки [] означают необязательность, вертикальная черта | означает “или”).
Описание поля состоит из наименования поля и типа поля (или домена - см. урок 9), а также дополнительных ограничений, накладываемых на поле:
= col {datatype | COMPUTED BY () | domain}
[DEFAULT {literal | NULL | USER}]
[NOT NULL] []
[COLLATE collation]
Здесь
col - имя поля;
datatype - любой правильный тип SQL-сервера (для InterBase такими типами являются SMALLINT, INTEGER, FLOAT, DOUBLE PRECISION, DECIMAL, NUMERIC, DATE, CHAR, VARCHAR, NCHAR, BLOB), символьные типы могут иметь CHARACTER SET - набор символов, определяющий язык страны. Для русского языка следует задать набор символов WIN1251;
COMPUTED BY () - определение вычисляемого на уровне сервера поля, где - правильное SQL-выражение, возвращающее единственное значение;
domain - имя домена (обобщенного типа), определенного в базе данных;
DEFAULT - конструкция, определяющая значение поля по умолчанию;
NOT NULL - конструкция, указывающая на то, что поле не может быть пустым;
COLLATE - предложение, определяющее порядок сортировки для выбранного набора символов (для поля типа BLOB не применяется). Русский набор символов WIN1251 имеет 2 порядка сортировки - WIN1251 и PXW_CYRL. Для правильной сортировки, включающей большие буквы, следует выбрать порядок PXW_CYRL.
Описание ограничений и/или ключей включает в себя предложения CONSTRAINT или предложения, описывающие уникальные поля, первичные, внешние ключи, а также ограничения CHECK (такие конструкции могут определяться как на уровне поля, так и на уровне таблицы в целом, если они затрагивают несколько полей):
= [CONSTRAINT constraint ]
Здесь
= {{PRIMARY KEY | UNIQUE} (col[,col...]) | FOREIGN KEY (col [, col ...]) REFERENCES other_table
| CHECK ()}
=
{ { | ()}
| [NOT] BETWEEN AND
| [NOT] LIKE [ESCAPE ]
| [NOT] IN ( [, ...] |
= {
col [] | | |
| NULL | USER | RDB$DB_KEY } [COLLATE collation]
= num | "string" | charsetname "string"
= {
COUNT (* | [ALL] | DISTINCT )
| SUM ([ALL] | DISTINCT )
| AVG ([ALL] | DISTINCT )
| MAX ([ALL] | DISTINCT )
| MIN ([ALL] | DISTINCT )
| CAST ( AS )
| UPPER ()
| GEN_ID (generator, )
}
= {= | < | > | = | !< | !> | | !=}
= выражение SELECT по одному полю, которое возвращает в точности одно значение.
Приведенного неполного синтаксиса достаточно для большинства задач, решаемых в различных предметных областях. Проще всего синтаксис SQL можно понять из примеров. Поэтому мы приведем несколько примеров создания таблиц с помощью SQL.
Пример A: Простая таблица с конструкцией PRIMARY KEY на уровне поля
CREATE TABLE REGION (
REGION REGION_NAME NOT NULL PRIMARY KEY,
POPULATION INTEGER NOT NULL);
Предполагается, что в базе данных определен домен REGION_NAME, например, следующим образом:
CREATE DOMAIN REGION_NAME
AS VARCHAR(40) CHARACTER SET WIN1251 COLLATE PXW_CYRL;
Пример B: Таблица с предложением UNIQUE как на уровне поля, так и на уровне таблицы
CREATE TABLE GOODS (
MODEL SMALLINT NOT NULL UNIQUE,
NAME CHAR(10) NOT NULL,
ITEMID INTEGER NOT NULL, CONSTRAINT MOD_UNIQUE
UNIQUE (NAME, ITEMID));
Пример C: Таблица с определением первичного ключа, внешнего ключа и конструкции CHECK, а также символьных массивов
CREATE TABLE JOB (
JOB_CODE JOBCODE NOT NULL,
JOB_GRADE JOBGRADE NOT NULL,
JOB_REGION REGION_NAME NOT NULL,
JOB_TITLE VARCHAR(25) CHARACTER SET WIN1251 COLLATE PXW_CYRL NOT NULL,
MIN_SALARY SALARY NOT NULL,
MAX_SALARY SALARY NOT NULL,
JOB_REQ BLOB(400,1) CHARACTER SET WIN1251,
LANGUAGE_REQ VARCHAR(15) [5],
PRIMARY KEY (JOB_CODE, JOB_GRADE, JOB_REGION),
FOREIGN KEY (JOB_REGION) REFERENCES REGION (REGION),
CHECK (MIN_SALARY < MAX_SALARY));
Данный пример создает таблицу, содержащую информацию о работах (профессиях). Типы полей основаны на доменах JOBCODE, JOBGRADE, REGION_NAME и SALARY. Определен массив LANGUAGE_REQ, состоящий из 5 элементов типа VARCHAR(15). Кроме того, введено поле JOB_REQ, имеющее тип BLOB с подтипом 1 (текстовый блоб) и размером сегмента 400. Для таблицы определен первичный ключ, состоящий из трех полей JOB_CODE, JOB_GRADE и JOB_REGION. Далее, определен внешний ключ (JOB_REGION), ссылающийся на поле REGION таблицы REGION. И, наконец, включено предложение CHECK, позволяющее производить проверку соотношения для двух полей и вызывать исключительное состояние при нарушении такого соотношения.
Пример D: Таблица с вычисляемым полем
CREATE TABLE SALARY_HISTORY (
EMP_NO EMPNO NOT NULL,
CHANGE_DATE DATE DEFAULT "NOW" NOT NULL,
UPDATER_ID VARCHAR(20) NOT NULL,
OLD_SALARY SALARY NOT NULL,
PERC_CHANGE DOUBLE PRECISION DEFAULT 0 NOT NULL
CHECK (PERC_CHANGE BETWEEN -50 AND 50),
NEW_SALARY COMPUTED BY
(OLD_SALARY + OLD_SALARY * PERC_CHANGE / 100),
PRIMARY KEY (EMP_NO, CHANGE_DATE, UPDATER_ID),
FOREIGN KEY (EMP_NO) REFERENCES EMPLOYEE (EMP_NO));
Данный пример создает таблицу, где среди других полей имеется вычисляемое (физически не существующее) поле NEW_SALARY, значение которого вычисляется по значениям двух других полей (OLD_SALARY и PERC_CHANGE).
Заключение
Итак, мы рассмотрели, как создавать таблицы с помощью SQL-выражений. Этот процесс, хотя и не столь удобен, как интерактивное средство Database Desktop, однако обладает наиболее гибкими возможностями по настройке Вашей системы и управления ее связями.
6
Урок 3: Создание таблиц с помощью SQL запросов
Создание баз данных в Delphi
Урок 4: ОбъектTTable
Содержание урока 4:Содержание урока 4: 1
Класс TDataSet 2
Открытие и закрытие DataSet 4
Поля 10
Работа с Данными 14
Использование SetKey для поиска в таблице 17
Использование фильтров для ограничения числа записей в DataSet 20
Обновление (Refresh) 22
Закладки (Bookmarks) 23
Создание Связанных Курсоров (Linked cursors) 23
Основные понятия о TDataSource 26
Использование TDataSource для проверки состояния БД: 27
Отслеживание состояния DataSet 31
Обзор
Статья содержит всесторонний обзор основных фактов которые Вы должны знать, прежде чем начать писать программы, работающие с Базами Данных (БД). Прочитав эту статью, Вы должны понять большинство механизмов доступа к данным, которые есть в Delphi.
Более подробно здесь рассказывается о TTable и TDataSource.
Имеются несколько основных компонент(объектов), которые Вы будете использовать постоянно для доступа к БД. Эти объекты могут быть разделены на три группы:
невизуальные: TTable, TQuery, TDataSet, TField
визуальные: TDBGrid, TDBEdit
связующие: TDataSource
Первая группа включает невизуальные классы, которые используются для управления таблицами и запросами. Эта группа сосредотачивается вокруг компонент типа TTable, TQuery, TDataSet и TField. В Палитре Компонент эти объекты расположены на странице Data Access.
Вторая важная группа классов - визуальные, которые показывают данные пользователю, и позволяют ему просматривать и модифицировать их. Эта группа классов включает компоненты типа TDBGrid, TDBEdit, TDBImage и TDBComboBox. В Палитре Компонент эти объекты расположены на странице Data Controls.
Имеется и третий тип, который используется для того, чтобы связать предыдущие два типа объектов. К третьему типу относится только невизуальный компонент TDataSource.
Класс TDataSet
TDataSet класс - один из наиболее важных объектов БД. Чтобы начать работать с ним, Вы должны взглянуть на следующую иерархию:
TDataSet
|
TDBDataSet
|
|-- TTable
|-- TQuery
|-- TStoredProc
TDataSet содержит абстрактные методы там, где должно быть непосредственное управление данными. TDBDataSet знает, как обращаться с паролями и то, что нужно сделать, чтобы присоединить Вас к определенной таблице. TTable знает (т.е. уже все абстрактные методы переписаны), как обращаться с таблицей, ее индексами и т.д.
Как Вы увидите в далее, TQuery имеет определенные методы для обработки SQL запросов.
TDataSet - инструмент, который Вы будете использовать чтобы открыть таблицу, и перемещаться по ней. Конечно, Вы никогда не будете непосредственно создавать объект типа TDataSet. Вместо этого, Вы будете использовать TTable, TQuery или других потомков TDataSet (например, TQBE). Полное понимание работы системы, и точное значение TDataSet, будут становиться все более ясными по мере прочтения этой главы.
На наиболее фундаментальном уровне, Dataset это просто набор записей, как изображено на рис.1
Рис.1: Любой dataset состоит из ряда записей (каждая содержит N полей) и указатель на текущую запись.
В большинстве случаев dataset будет иметь a прямое, один к одному, соответствие с физической таблицей, которая существует на диске. Однако, в других случаях Вы можете исполнять запрос или другое действие, возвращающие dataset, который содержит либо любое подмножество записей одной таблицы, либо объединение (join) между несколькими таблицами. В тексте будут иногда использоваться термины DataSet и TTable как синонимы.
Обычно в программе используются объекты типа TTable или TQuery, поэтому в следующих нескольких главах будет предполагаться существование объекта типа TTable называемого Table1.
Итак, самое время начать исследование TDataSet. Как только Вы познакомитесь с его возможностями, Вы начнете понимать, какие методы использует Delphi для доступа к данным, хранящимся на диске в виде БД. Ключевой момент здесь - не забывать, что почти всякий раз, когда программист на Delphi открывает таблицу, он будет использовать TTable или TQuery, которые являются просто некоторой надстройкой над TDataSet.
Открытие и закрытие DataSet
В этой главе Вы узнаете некоторые факты об открытии и закрытии DataSet.
Если Вы используете TTable для доступа к таблице, то при открытии данной таблицы заполняются некоторые свойства TTable (количество записей RecordCount, описание структуры таблицы и т.д.).
Прежде всего, Вы должны поместить во время дизайна на форму объект TTable и указать, с какой таблицей хотите работать. Для этого нужно заполнить в Инспекторе объектов свойства DatabaseName и TableName. В DatabaseName можно либо указать директорию, в которой лежат таблицы в формате dBase или Paradox (например, C:\DELPHI\DEMOS\DATA), либо выбрать из списка псевдоним базы данных (DBDEMOS). Теперь, если свойство Active установить в True, то при запуске приложения таблица будет открываться автоматически.
Имеются два различных способа открыть таблицу во время выполнения программы. Вы можете написать следующую строку кода:
Table1.Open;
Или, если Вы предпочитаете, то можете установить свойство Active равное True:
Table1.Active := True;
Нет никакого различия между результатом производимым этими двумя операциями. Метод Open, однако, сам заканчивается установкой свойства Active в True, так что может быть даже чуть более эффективно использовать свойство Active напрямую.
Также, как имеются два способа открыть a таблицу, так и есть два способа закрыть ее. Самый простой способ просто вызывать Close:
Table1.Close;
Или, если Вы желаете, Вы можете написать:
Table1.Active := False;
Еще раз повторим, что нет никакой существенной разницы между двумя этими способами. Вы должны только помнить, что Open и Close это методы (процедуры), а Active - свойство.
Навигация (Перемещение по записям)
После открытия таблицы, следующим шагом Вы должны узнать как перемещаться по записям внутри него.
Следующий обширный набор методов и свойства TDataSet обеспечивает все , что Вам нужно для доступа к любой конкретной записи внутри таблицы:
procedure First;
procedure Last;
procedure Next;
procedure Prior;
property BOF: Boolean read FBOF;
property EOF: Boolean read FEOF;
procedure MoveBy(Distance: Integer);
Дадим краткий обзор их функциональных возможностей:
Вызов Table1.First перемещает Вас к первой записи в таблице.
Table1.Last перемещает Вас к последней записи.
Table1.Next перемещает Вас на одну запись вперед.
Table1.Prior перемещает Вас на одну запись Назад.
Вы можете проверять свойства BOF или EOF, чтобы понять, находитесь ли Вы в начале или в конце таблицы.
Процедура MoveBy перемещает Вас на N записей вперед или назад в таблице. Нет никакого функционального различия между запросом Table1.Next и вызовом Table1.MoveBy(1). Аналогично, вызов Table1.Prior имеет тот же самый результат, что и вызов Table1.MoveBy(-1).
Чтобы начать использовать эти навигационные методы, Вы должны поместить TTable, TDataSource и TDBGrid на форму, также, как Вы делали это в предыдущем уроке. Присоедините DBGrid1 к DataSource1, и DataSource1 к Table1. Затем установите свойства таблицы:
в DatabaseName имя подкаталога, где находятся демонстрационные таблицы (или псевдоним DBDEMOS);
в TableName установите имя таблицы CUSTOMER.
Если Вы запустили программу, которая содержит видимый элемент TDBGrid, то увидите, что можно перемещаться по записям таблицы с помощью полос прокрутки (scrollbar) на нижней и правой сторонах DBGrid.
Однако, иногда нужно перемещаться по таблице “программным путем”, без использования возможностей, встроенных в визуальные компоненты. В следующих нескольких абзацах объясняется как можно это сделать.
Поместите две кнопки на форму и назовите их Next и Prior, как показано на рис.2.
Рис.2 : Next и Prior кнопки позволяют Вам перемещаться по БД.
Дважды щелкните на кнопке Next - появится заготовка обработчика события:
procedure TForm1.NextClick(Sender: TObject);
begin
end;
Теперь добавьте одну строчку кода так, чтобы процедура выглядела так:
procedure TForm1.NextClick(Sender: TObject);
begin
Table1.Next;
end;
Повторите те же самые действия с кнопкой Prior, так, чтобы функция связанная с ней выглядела так:
procedure TForm1.PriorClick(Sender: TObject);
begin
Table1.Prior;
end;
Теперь запустите программу, и нажмите на кнопки. Вы увидите, что они легко позволяют Вам перемещаться по записям в таблице.
Теперь добавьте еще две кнопки и назовите их First и Last, как показано на рис.3
Рис.3: Программа со всеми четырьмя кнопками.
Сделайте то же самое для новых кнопок.
procedure TForm1.FirstClick(Sender: TObject);
begin
Table1.First;
end;
procedure TForm1.LastClick(Sender: TObject);
begin
Table1.Last;
end;
Нет ничего более простого чем эти навигационные функции. First перемещает Вас в начало таблицы, Last перемещает Вас в конец таблицы, а Next и Prior перемещают Вас на одну запись вперед или назад.
TDataSet.BOF - read-only Boolean свойство, используется для проверки, находитесь ли Вы в начале таблицы. Свойства BOF возвращает true в трех случаях:
После того, как Вы открыли файл;
После того, как Вы вызывали TDataSet.First;
После того, как вызов TDataSet.Prior не выполняется.
Первые два пункта - очевидны. Когда Вы открываете таблицу, Delphi помещает Вас на первую запись; когда Вы вызываете метод First, Delphi также перемещает Вас в начало таблицы. Третий пункт, однако, требует небольшого пояснения: после того, как Вы вызывали метод Prior много раз, Вы могли добраться до начала таблицы, и следующий вызов Prior будет неудачным - после этого BOF и будет возвращать True.
Следующий код показывает самый общий пример использования Prior, когда Вы попадаете к началу a файла:
while not Table.Bof do begin
DoSomething;
Table1.Prior;
end;
В коде, показанном здесь, гипотетическая функция DoSomething будет вызвана сперва на текущей записи и затем на каждой следующей записи (от текущей и до начала таблицы). Цикл будет продолжаться до тех пор, пока вызов Table1.Prior не сможет больше переместить Вас на предыдущую запись в таблице. В этот момент BOF вернет True и программа выйдет из цикла. (Чтобы оптимизировать вышеприведенный код, установите DataSource1.Enabled в False перед началом цикла, и верните его в True после окончания цикла.)
Все сказанное относительно BOF также применимо и к EOF. Другими словами, код, приведенный ниже показывает простой способ пробежать по всем записям в a dataset:
Table1.First;
while not Table1.EOF do begin
DoSomething;
Table1.Next;
end;
Классическая ошибка в случаях, подобных этому: Вы входите в цикл while или repeat, но забываете вызывать Table1.Next:
Table1.First;
repeat
DoSomething;
until Table1.EOF;
Если Вы случайно написали такой код, то ваша машина зависнет. Также, этот код мог бы вызвать проблемы, если Вы открыли пустую таблицу. Так как здесь используется цикл repeat, DoSomething был бы вызван один раз, даже если бы нечего было обрабатывать. Поэтому, лучше использовать цикл while вместо repeat в ситуациях подобных этой.
EOF возвращает True в следующих трех случаях:
После того, как Вы открыли пустой файл;
После того, как Вы вызывали TDataSet.Last;
После того, как вызов TDataSet.Next не выполняется.
Единственная навигационная процедура, которая еще не упоминалась - MoveBy, которая позволяет Вам переместиться на N записей вперед или назад в таблице. Если Вы хотите переместиться на две записи вперед, то напишите:
MoveBy(2);
И если Вы хотите переместиться на две записи назад, то:
MoveBy(-2);
При использовании этой функции Вы должны всегда помнить, что DataSet - это изменяющиеся объекты, и запись, которая была пятой по счету в предыдущий момент, теперь может быть четвертой или шестой или вообще может быть удалена...
Prior и Next - это простые функции, которые вызывают MoveBy.
ПоляВ большинстве случаев, когда Вы хотите получить доступ из программы к индивидуальные полям записи, Вы можете использовать одно из следующих свойств или методов, каждый из которых принадлежат TDataSet:
property Fields[Index: Integer];
function FieldByName(const FieldName: string): TField;
property FieldCount;
Свойство FieldCount возвращает число полей в текущей структуре записи. Если Вы хотите программным путем прочитать имена полей, то используйте свойство Fields для доступа к ним:
var
S: String;
begin
S := Fields[0].FieldName;
end;
Если Вы работали с записью в которой первое поле называется CustNo, тогда код показанный выше поместит строку “CustNo” в переменную S. Если Вы хотите получить доступ к имени второго поля в вышеупомянутом примере, тогда Вы могли бы написать:
S := Fields[1].FieldName;
Короче говоря, индекс передаваемый в Fields (начинающийся с нуля), и определяет номер поля к которому Вы получите доступ, т.е. первое поле - ноль, второе один, и так далее.
Если Вы хотите прочитать текущее содержание конкретного поля конкретной записи, то Вы можете использовать свойство Fields или метод FieldsByName. Для того, чтобы найти значение первого поля записи, прочитайте первый элемент массива Fields:
S := Fields[0].AsString;
Предположим, что первое поле в записи содержит номер заказчика, тогда код, показанный выше, возвратил бы строку типа “1021”, “1031” или “2058”. Если Вы хотели получить доступ к этот переменный, как к числовой величине, тогда Вы могли бы использовать AsInteger вместо AsString. Аналогично, свойство Fields включают AsBoolean, AsFloat и AsDate.
Если хотите, Вы можете использовать функцию FieldsByName вместо свойства Fields:
S := FieldsByName(‘CustNo’).AsString;
Как показано в примерах выше, и FieldsByName, и Fields возвращают те же самые данные. Два различных синтаксиса используются исключительно для того, чтобы обеспечить программистов гибким и удобным набором инструментов для программного доступа к содержимому DataSet.
Давайте посмотрим на простом примере, как можно использовать доступ к полям таблицы во время выполнения программы. Создайте новый проект, положите на форму объект TTable, два объекта ListBox и две кнопки - “Fields” и “Values” (см рис.4).
Соедините объект TTable с таблицей CUSTOMER, которая поставляется вместе с Delphi (DBDEMOS), не забудьте открыть таблицу (Active = True).
Рис.4: Программа FLDS показывает, как использовать свойство Fields.
Сделайте Double click на кнопке Fields и создайте a метод который выглядит так:
procedure TForm1.FieldsClick(Sender: TObject);
var
i: Integer;
begin
ListBox1.Clear;
for i := 0 to Table1.FieldCount - 1 do
ListBox1.Items.Add(Table1.Fields[i].FieldName);
end;
Обработчик события начинается с очистки первого ListBox1, затем он проходит через все поля, добавляя их имена один за другим в ListBox1. Заметьте, что цикл показанный здесь пробегает от 0 до FieldCount - 1. Если Вы забудете вычесть единицу из FieldCount, то Вы получите ошибку “List Index Out of Bounds”, так как Вы будете пытаться прочесть имя поля которое не существует.
Предположим, что Вы ввели код правильно, и заполнили ListBox1 именами всех полей в текущей структуре записи.
В Delphi существуют и другие средства которые позволяют Вам получить ту же самую информацию, но это самый простой способ доступа к именам полей в Run Time.
Свойство Fields позволяет Вам получить доступ не только именам полей записи, но также и к содержимому полей. В нашем примере, для второй кнопки напишем:
procedure TForm1.ValuesClick(Sender: TObject);
var
i: Integer;
begin
ListBox2.Clear;
for i := 0 to Table1.FieldCount - 1 do
ListBox2.Items.Add(Table1.Fields[i].AsString);
end;
Этот код добавляет содержимое каждого из полей во второй listbox. Обратите внимание, что вновь счетчик изменяется от нуля до FieldCount - 1.
Свойство Fields позволяет Вам выбрать тип результата написав Fields[N].AsString. Этот и несколько связанных методов обеспечивают a простой и гибкий способ доступа к данным, связанными с конкретным полем. Вот список доступных методов который Вы можете найти в описании класса TField:
property AsBoolean
property AsFloat
property AsInteger
property AsString
property AsDateTime
Всякий раз (когда это имеет смысл), Delphi сможет сделать преобразования. Например, Delphi может преобразовывать поле Boolean к Integer или Float, или поле Integer к String. Но не будет преобразовывать String к Integer, хотя и может преобразовывать Float к Integer. BLOB и Memo поля - специальные случаи, и мы их рассмотрим позже. Если Вы хотите работать с полями Date или DateTime, то можете использовать AsString и AsFloat для доступа к ним.
Как было объяснено выше, свойство FieldByName позволяет Вам получить доступ к содержимому определенного поля просто указав имя этого поля:
S := Table1.FieldByName(‘CustNo’).AsString;
Это - удобная технология, которая имеет несколько преимуществ, когда используется соответствующим образом. Например, если Вы не уверены в местонахождении поля, или если Вы думаете, что структура записи, с которой Вы работаете могла измениться, и следовательно, местонахождение поля не определено.
Работа с ДаннымиСледующие методы позволяют Вам изменить данные, связанные с TTable:
procedure Append;
procedure Insert;
procedure Cancel;
procedure Delete;
procedure Edit;
procedure Post;
Все эти методы - часть TDataSet, они унаследованы и используются TTable и TQuery.
Всякий раз, когда Вы хотите изменить данные, Вы должны сначала перевести DataSet в режим редактирования. Как Вы увидите, большинство визуальных компонент делают это автоматически, и когда Вы используете их, то совершенно не будете об этом заботиться. Однако, если Вы хотите изменить TTable программно, Вам придется использовать вышеупомянутые функции.
Имеется a типичная последовательность, которую Вы могли бы использовать при изменении поля текущей записи:
Table1.Edit;
Table1.FieldByName(‘CustName’).AsString := ‘Fred’;
Table1.Post;
Первая строка переводит БД в режим редактирования. Следующая строка присваивает значение ‘Fred’ полю ‘CustName’. Наконец, данные записываются на диск, когда Вы вызываете Post.
При использовании такого подхода, Вы всегда работаете с записями. Сам факт перемещения к следующей записи автоматически сохраняет ваши данные на диск. Например, следующий код будет иметь тот же самый эффект, что и код показанный выше, плюс этому будет перемещать Вас на следующую запись:
Table1.Edit;
Table1.FieldByName(‘CustNo’).AsInteger := 1234;
Table1.Next;
Общее правило, которому нужно следовать - всякий раз, когда Вы сдвигаетесь с текущей записи, введенные Вами данные будут записаны автоматически. Это означает, что вызовы First, Next, Prior и Last всегда выполняют Post, если Вы находились в режиме редактирования. Если Вы работаете с данными на сервере и транзакциями, тогда правила, приведенные здесь, не применяются. Однако, транзакции - это отдельный вопрос с их собственными специальными правилами, Вы увидите это, когда прочитаете о них в следующих уроках.
Тем не менее, даже если Вы не работаете со транзакциями, Вы можете все же отменить результаты вашего редактирования в любое время, до тех пор, пока не вызвали напрямую или косвенно метод Post. Например, если Вы перевели таблицу в режим редактирования, и изменили данные в одном или более полей, Вы можете всегда вернуть запись в исходное состояние вызовом метода Cancel.
Существуют два метода, названные Append и Insert, который Вы можете использовать всякий раз, когда Вы хотите добавить новую запись в DataSet. Очевидно имеет больше смысла использовать Append для DataSets которые не индексированы, но Delphi не будет генерировать exception если Вы используете Append на индексированной таблице. Фактически, всегда можно использовать и Append, и Insert.
Продемонстрируем работу методов на простом примере. Чтобы создать программу, используйте TTable, TDataSource и TdbGrid. Открыть таблицу COUNTRY. Затем разместите две кнопки на форме и назовите их ‘Insert’ и ‘Delete’. Когда Вы все сделаете, то должна получиться программа, показанная на рис.5
Рис.5: Программа может вставлять и удалять запись из таблицы COUNTRY.
Следующим шагом Вы должен связать код с кнопками Insert и Delete:
procedure TForm1.InsertClick(Sender: TObject);
begin
Table1.Insert;
Table1.FieldByName('Name').AsString := 'Russia';
Table1.FieldByName('Capital').AsString := 'Moscow';
Table1.Post;
end;
procedure TForm1.DeleteClick(Sender: TObject);
begin
Table1.Delete;
end;
Процедура показанная здесь сначала переводит таблицу в режим вставки (новая запись с незаполненными полями вставляется в текущую позицию dataset). После вставки пустой записи, следующим этапом нужно назначить значения одному или большему количеству полей. Существует, конечно, несколько различных путей присвоить эти значения. В нашей программе Вы могли бы просто ввести информацию в новую запись через DBGrid. Или Вы могли бы разместить на форме стандартную строку ввода (TEdit) и затем установить каждое поле равным значению, которое пользователь напечатал в этой строке:
Table1.FieldByName(‘Name’).AsString := Edit1.Text;
Можно было бы использовать компоненты, специально предназначенные для работы с данными в DataSet.
Назначение этой главы, однако, состоит в том, чтобы показать, как вводить данные из программы. Поэтому, в примере вводимая информация скомпилирована прямо в код программы:
Table1.FieldByName('Name').AsString := 'Russia';
Один из интересных моментов в этом примере это то, что нажатие кнопки Insert дважды подряд автоматически вызывает exception ‘Key Violation’. Чтобы исправить эту ситуацию, Вы должны либо удалить текущую запись, или изменять поля Name и Capital вновь созданной записи.
Просматривая код показанный выше, Вы увидите, что просто вставка записи и заполнения ее полей не достаточно для того, чтобы изменить физические данные на диске. Если Вы хотите, чтобы информация записалась на диск, Вы должны вызывать Post.
Если после вызова Insert, Вы решаете отказаться от вставки новой записи, то Вы можете вызвать Cancel. Если Вы сделаете это прежде, чем Вы вызовете Post, то все что Вы ввели после вызова Insert будет отменено, и dataset будет находиться в состоянии, которое было до вызова Insert.
Одно дополнительное свойство, которое Вы должны иметь в виду называется CanModify. Если CanModify возвращает False, то TTable находиться в состоянии ReadOnly. В противном случае CanModify возвращает True и Вы можете редактировать или добавлять записи в нее по желанию. CanModify - само по себе ‘read only’ свойство. Если Вы хотите установить DataSet в состояние только на чтение (Read Only), то Вы должны использовать свойство ReadOnly, не CanModify.
Использование SetKey для поиска в таблице
Для того, чтобы найти некоторую величину в таблице, программист на Delphi может использовать две процедуры SetKey и GotoKey. Обе эти процедуры предполагают, что поле по которому Вы ищете индексировано. Delphi поставляется с демонстрационной программой SEARCH, которая показывает, как использовать эти запросы.
Чтобы создать программу SEARCH, поместите TTable, TDataSource, TDBGrid, TButton, TLabel и TEdit на форму, и расположите их как показано на рис.6. Назовите кнопку Search, и затем соедините компоненты БД так, чтобы Вы видели в DBGrid1 таблицу Customer.
Рис.6: Программа SEARCH позволяет Вам ввести номер заказчика и затем найти его по нажатию кнопки.
Вся функциональность программы SEARCH скрыта в единственном методе, который присоединен к кнопке Search. Эта функция считывает строку, введенную в окно редактора, и ищет ее в колонке CustNo, и наконец помещает фокус на найденной записи. В простейшем варианте, код присоединенный к кнопке Search выглядит так:
procedure TSearchDemo.SearchClick(Sender: TObject);
begin
Table1.SetKey;
Table1.FieldByName(’CustNo’).AsString := Edit1.Text;
Table1.GotoKey;
end;
Первый вызов в этой процедуре установит Table1 в режим поиска. Delphi должен знать, что Вы переключились в режим поиска просто потому, что свойство Fields используется по другому в этом режиме. Далее, нужно присвоить свойству Fields значение, которое Вы хотите найти. Для фактического выполнения поиска нужно просто вызывать Table1.GotoKey.
Если Вы ищете не по первичному индексу файла, тогда Вы должны определить имя индекса, который Вы используете в свойстве IndexName. Например, если таблица Customer имеет вторичный индекс по полю City, тогда Вы должны установить свойство IndexName равным имени индекса. Когда Вы будете искать по этому полю, Вы должны написать:
Table1.IndexName := ’CityIndex’;
Table1.Active := True;
Table1.SetKey;
Table1.FieldByName(’City’).AsString := Edit1.Text;
Table1.GotoKey;
Запомните: поиск не будет выполняться, если Вы не назначите правильно индекс (св-во IndexName). Кроме того, Вы должны обратить внимание, что IndexName - это свойство TTable, и не присутствует в других прямых потомках TDataSet или TDBDataSet.
Когда Вы ищете некоторое значение в БД, всегда существует вероятность того, что поиск окажется неудачным. В таком случае Delphi будет автоматически вызывать exception, но если Вы хотите обработать ошибку сами, то могли бы написать примерно такой код:
procedure TSearchDemo.SearchClick(Sender: TObject);
begin
Cust.SetKey;
Cust.FieldByName('CustNo').AsString:= CustNoEdit.Text;
if not Cust.GotoKey then
raise Exception.CreateFmt('Cannot find CustNo %g',
[CustNo]);
end;
В коде, показанном выше, либо неверное присвоение номера, либо неудача поиска автоматически приведут к сообщению об ошибке ‘Cannot find CustNo %g’.
Иногда требуется найти не точно совпадающее значение, а близкое к нему, для этого следует вместо GotoKey пользоваться методом GotoNearest.
Использование фильтров для ограничения числа записей в DataSetПроцедура ApplyRange позволяет Вам установить фильтр, который ограничивает диапазон просматриваемых записей. Например, в БД Customers, поле CustNo имеет диапазон от 1,000 до 10,000. Если Вы хотите видеть только те записи, которые имеют номер заказчика между 2000 и 3000, то Вы должны использовать метод ApplyRange, и еще два связанных с ним метода. Данные методы работают только с индексированным полем.
Вот процедуры, которые Вы будете чаще всего использовать при установке фильтров:
procedure SetRangeStart;
procedure SetRangeEnd;
procedure ApplyRange;
procedure CancelRange;
Кроме того, у TTable есть дополнительные методы для управления фильтрами:
procedure EditRangeStart;
procedure EditRangeEnd;
procedure SetRange;
Для использования этих процедур необходимо:
Сначала вызвать SetRangeStart и использовать свойство Fields для определения начала диапазона.
Затем вызвать SetRangeEnd и вновь использовать свойство Fields для определения конца диапазона.
Первые два шага подготавливают фильтр, и теперь все что Вам необходимо, это вызвать ApplyRange, и новый фильтр вступит в силу.
Когда нужно прекратить действие фильтра - вызовите CancelRange.
Программа RANGE, которая есть среди примеров Delphi, показывает, как использовать эти процедуры. Чтобы создать программу, поместите TTable, TDataSource и TdbGrid на форму. Соедините их так, чтобы Вы видеть таблицу CUSTOMERS из подкаталога DEMOS. Затем поместите два объекта TLabel на форму и назовите их ‘Start Range’ и ‘End Range’. Затем положите на форму два объекта TEdit. Наконец, добавьте кнопки ‘ApplyRange’ и ‘CancelRange’. Когда Вы все выполните, форма имеет вид, как на рис.7
Рис.7: Программа RANGE показывает как ограничивать число записей таблицы для просмотра.
Процедуры SetRangeStart и SetRangeEnd позволяют Вам указать первое и последнее значения в диапазоне записей, которые Вы хотите видеть. Чтобы начать использовать эти процедуры, сначала выполните double-click на кнопке ApplyRange, и создайте процедуру, которая выглядит так:
procedure TForm1.ApplyRangeBtnClick(Sender: TObject);
begin
Table1.SetRangeStart;
if RangeStart.Text '' then
Table1. Fields[0].AsString := RangeStart.Text;
Table1.SetRangeEnd;
if RangeEnd.Text '' then
Table1.Fields[0].AsString := RangeEnd.Text;
Table1.ApplyRange;
end;
Сначала вызывается процедура SetRangeStart, которая переводит таблицу в режим диапазона (range mode). Затем Вы должны определить начало и конец диапазона. Обратите внимание, что Вы используете свойство Fields для определения диапазона:
Table1.Fields[0].AsString := RangeStart.Text;
Такое использование свойства Fields - это специальный случай, так как синтаксис, показанный здесь, обычно используется для установки значения поля. Этот специальный случай имеет место только после того, как Вы перевели таблицу в режим диапазона, вызвав SetRangeStart.
Заключительный шаг в процедуре показанной выше - вызов ApplyRange. Этот вызов фактически приводит ваш запрос в действие. После вызова ApplyRange, TTable больше не в находится в режиме диапазона, и свойства Fields функционирует как обычно.
Обработчик события нажатия кнопки ‘CancelRange’:
procedure TForm1.CancelRangeBtnClick(Sender: TObject);
begin
Table1.CancelRange;
end;
Обновление (Refresh)Как Вы уже знаете, любая таблица, которую Вы открываете всегда “подвержена изменению”. Короче говоря, Вы должны расценить таблицу скорее как меняющуюся, чем как статическую сущность. Даже если Вы - единственное лицо, использующее данную TTable, и даже если Вы не работаете в сети, всегда существует возможность того, что программа с которой Вы работаете, может иметь два различных пути изменения данных в таблице. В результате, Вы должны всегда знать, необходимо ли Вам обновить вид таблицы на экране.
Функция Refresh связана с функцией Open, в том смысле что она считывает данные, или некоторую часть данных, связанных с данной таблицей. Например, когда Вы открываете таблицу, Delphi считывает данные непосредственно из файла БД. Аналогично, когда Вы Регенерируете таблицу, Delphi считывает данные напрямую из таблицы. Поэтому Вы можете использовать эту функцию, чтобы перепрочитать таблицу, если Вы думаете что она могла измениться. Быстрее и эффективнее, вызывать Refresh, чем вызывать Close и затем Open.
Имейте ввиду, однако, что обновление TTable может иногда привести к неожиданным результатам. Например, если a пользователь рассматривает запись, которая уже была удалена, то она исчезнет с экрана в тот момент, когда будет вызван Refresh. Аналогично, если некий другой пользователь редактировал данные, то вызов Refresh приведет к динамическому изменению данных. Конечно маловероятно, что один пользователь будет изменять или удалять запись в то время, как другой просматривает ее, но это возможно.
Закладки (Bookmarks)Часто бывает полезно отметить текущее местоположение в таблице так, чтобы можно было быстро возвратиться к этому месту в дальнейшем. Delphi обеспечивает эту функциональную возможность посредством трех методов, которые используют понятие закладки.
function GetBookmark: TBookmark;
(устанавливает закладку в таблице)
procedure GotoBookmark(Bookmark: TBookmark);
(переходит на закладку)
procedure FreeBookmark(Bookmark: TBookmark);
(освобождает память)
Как Вы можете видеть, вызов GetBookmark возвращает переменную типа TBookmark. TBookmark содержит достаточное количество информации, чтобы Delphi мог найти местоположение к которому относится этот TBookmark. Поэтому Вы можете просто передавать этот TBookmark функции GotoBookmark, и будете немедленно возвращены к местоположению, связанному с этой закладкой.
Обратите внимание, что вызов GetBookmark распределяет память для TBookmark, так что Вы должны вызывать FreeBookmark до окончания вашей программы, и перед каждой попыткой повторного использования Tbookmark (в GetBookMark).
Создание Связанных Курсоров (Linked cursors)Связанные курсоры позволяют программистам определить отношение один ко многим (one-to-many relationship). Например, иногда полезно связать таблицы CUSTOMER и ORDERS так, чтобы каждый раз, когда пользователь выбирает имя заказчика, то он видит список заказов связанных с этим заказчиком. Иначе говоря, когда пользователь выбирает запись о заказчике, то он может просматривать только заказы, сделанные этим заказчиком.
Программа LINKTBL демонстрирует, как создать программу которая использует связанные курсоры. Чтобы создать программу заново, поместите два TTable, два TDataSources и два TDBGrid на форму. Присоедините первый набор таблице CUSTOMER, а второй к таблице ORDERS. Программа в этой стадии имеет вид, показанный на рис.8
Рис.8: Программа LINKTBL показывает, как определить отношения между двумя таблицами.
Следующий шаг должен связать таблицу ORDERS с таблицей CUSTOMER так, чтобы Вы видели только те заказы, которые связанные с текущей записью в таблице заказчиков. В первой таблице заказчик однозначно идентифицируется своим номером - поле CustNo. Во второй таблице принадлежность заказа определяется также номером заказчика в поле CustNo. Следовательно, таблицы нужно связывать по полю CustNo в обоих таблицах (поля могут иметь различное название, но должны быть совместимы по типу). Для этого, Вы должны сделать три шага, каждый из которых требует некоторого пояснения:
Установить свойство Table2.MasterSource = DataSource1
Установить свойство Table2.MasterField = CustNo
Установить свойство Table2.IndexName = CustNo
Если Вы теперь запустите программу, то увидите, что обе таблицы связаны вместе, и всякий раз, когда Вы перемещаетесь на новую запись в таблице CUSTOMER, Вы будете видеть только те записи в таблице ORDERS, которые принадлежат этому заказчику.
Свойство MasterSource в Table2 определяет DataSource от которого Table2 может получить информацию. То есть, оно позволяет таблице ORDERS знать, какая запись в настоящее время является текущей в таблице CUSTOMERS.
Но тогда возникает вопрос: Какая еще информация нужна Table2 для того, чтобы должным образом отфильтровать содержимое таблицы ORDERS? Ответ состоит из двух частей:
Требуется имя поля по которому связанны две таблицы.
Требуется индекс по этому полю в таблице ORDERS (в таблице ‘многих записей’), которая будет связываться с таблицей CUSTOMER(таблице в которой выбирается ‘одна запись’).
Чтобы правильно воспользоваться информацией описанной здесь, Вы должны сначала проверить, что таблица ORDERS имеет нужные индексы. Если этот индекс первичный, тогда не нужно дополнительно указывать его в поле IndexName, и поэтому Вы можете оставить это поле незаполненным в таблице TTable2 (ORDERS). Однако, если таблица связана с другой через вторичный индекс, то Вы должны явно определять этот индекс в поле IndexName связанной таблицы.
В примере показанном здесь таблица ORDERS не имеет первичного индекса по полю CustNo, так что Вы должны явно задать в свойстве IndexName индекс CustNo.
Недостаточно, однако, просто yпомянуть имя индекса, который Вы хотите использовать. Некоторые индексы могут содержать несколько полей, так что Вы должны явно задать имя поля, по которому Вы хотите связать две таблицы. Вы должны ввести имя ‘CustNo’ в свойство Table2.MasterFields. Если Вы хотите связать две таблицы больше чем по одному полю, Вы должны внести в список все поля, помещая символ ‘|’ между каждым:
Table1.MasterFields := ‘CustNo | SaleData | ShipDate’;
В данном конкретном случае, выражение, показанное здесь, не имеет смысла, так как хотя поля SaleData и ShipDate также индексированы, но не дублируются в таблице CUSTOMER. Поэтому Вы должны ввести только поле CustNo в свойстве MasterFields. Вы можете определить это непосредственно в редакторе свойств, или написать код подобно показанному выше. Кроме того, поле (или поля) связи можно получить, вызвав редактор связей - в Инспекторе Объектов дважды щелкните на свойство MasterFields (рис.10)
Рис.10: Редактор связей для построения связанных курсоров.
Важно подчеркнуть, что данная глава охватила только один из нескольких путей, которым Вы можете создать связанные курсоры в Delphi. В главе о запросах будет описан второй метод, который будет обращен к тем кто знаком с SQL.
Основные понятия о TDataSourceКласс TDataSource используется в качестве проводника между TTable или TQuery и компонентами, визуализирующими данные, типа TDBGrid, TDBEdit и TDBComboBox (data-aware components). В большинстве случаев, все, что нужно сделать с DataSource - это указать в свойстве DataSet соответствующий TTable или TQuery. Затем, у data-aware компонента в свойстве DataSource указывается TDataSource, который используется в настоящее время.
TDataSource также имеет свойство Enabled, и оно может быть полезно всякий раз, когда Вы хотите временно отсоединить, например, DBGrid от таблицы или запроса. Эти требуется, например, если нужно программно пройти через все записи в таблице. Ведь, если таблица связана с визуальными компонентами (DBGrid, DBEdit и т.п.), то каждый раз, когда Вы вызываете метод TTable.Next, визуальные компоненты будут перерисовываться. Даже если само сканирование в таблице двух или трех тысяч записей не займет много времени, то может потребоваться значительно больше времени, чтобы столько же раз перерисовать визуальные компоненты. В случаях подобных этому, лучше всего установить поле DataSource.Eabled в False. Это позволит Вам просканировать записи без перерисовки визуальных компонент. Это единственная операция может увеличить скорость в некоторых случаях на несколько тысяч процентов.
Свойство TDataSource.AutoEdit указывает, переходит ли DataSet автоматически в режим редактирования при вводе текста в data-aware объекте.
Использование TDataSource для проверки состояния БД:TDataSource имеет три ключевых события, связанных с состоянием БД
OnDataChange
OnStateChange
OnUpdateData
OnDataChange происходит всякий раз, когда Вы переходите на новую запись, или состояние DataSet сменилось с dsInactive на другое, или начато редактирование. Другими словами, если Вы вызываете Next, Previous, Insert, или любой другой запрос, который должен привести к изменению данных, связанных с текущей записью, то произойдет событие OnDataChange. Если в программе нужно определить момент, когда происходит переход на другую запись, то это можно сделать в обработчике события OnDataChange:
procedure TForm1.DataSource1DataChange(Sender: TObject; Field: TField);
begin
if DataSource1.DataSet.State = dsBrowse then begin
DoSomething;
end;
end;
Событие OnStateChange событие происходит всякий раз, когда изменяется текущее состояние DataSet. DataSet всегда знает, в каком состоянии он находится. Если Вы вызываете Edit, Append или Insert, то TTable знает, что он теперь находится в режиме редактирования (dsEdit или dsInsert). Аналогично, после того, как Вы делаете Post, то TTable знает что данные больше не редактируется, и переключается обратно в режим просмотра (dsBrowse).
Dataset имеет шесть различных возможных состояний, каждое из которых включено в следующем перечисляемом типе:
TDataSetState = (dsInactive, dsBrowse, dsEdit, dsInsert,
dsSetKey, dsCalcFields);
В течение обычного сеанса работы, БД часто меняет свое состояние между Browse, Edit, Insert и другими режимами. Если Вы хотите отслеживать эти изменения, то Вы можете реагировать на них написав примерно такой код:
procedure TForm1.DataSource1StateChange(Sender: TObject);
var
S: String;
begin
case Table1.State of
dsInactive: S := 'Inactive';
dsBrowse: S := 'Browse';
dsEdit: S := 'Edit';
dsInsert: S := 'Insert';
dsSetKey: S := 'SetKey';
dsCalcFields: S := 'CalcFields';
end;
Label1.Caption := S;
end;
OnUpdateData событие происходит перед тем, как данные в текущей записи будут обновлены. Например, OnUpdateEvent будет происходить между вызовом Post и фактическим обновлением информации на диске.
События, генерируемые TDataSource могут быть очень полезны. Иллюстрацией этого служит следующий пример. Эта программа работает с таблицей COUNTRY, и включает TTable, TDataSource, пять TEdit, шесть TLlabel, восемь кнопок и панель. Действительное расположение элементов показано на рис.11. Обратите внимание, что шестой TLabel расположен на панели внизу главной формы.
Рис.11: Программа STATE показывает, как отслеживать текущее состояние таблицы.
Для всех кнопок напишите обработчики, вроде:
procedure TForm1.FirstClick(Sender: TObject);
begin
Table1.First;
end;
В данной программе есть одна маленькая хитрость, которую Вы должны понять, если хотите узнать, как работает программа. Так как есть пять отдельных редакторов TEdit на главной форме, то хотелось бы иметь некоторый способ обращаться к ним быстро и легко. Один простой способ состоит в том, чтобы объявить массив редакторов:
Edits: array[1..5] of TEdit;
Чтобы заполнить массив, Вы можете в событии OnCreate главной формы написать:
procedure TForm1.FormCreate(Sender: TObject);
var
i: Integer;
begin
for i := 1 to 5 do
Edits[i] := TEdit(FindComponent('Edit' + IntToStr(i)));
Table1.Open;
end;
Код показанный здесь предполагает, что первый редактор, который Вы будете использовать назовем Edit1, второй Edit2, и т.д. Существование этого массива позволяет очень просто использовать событие OnDataChange, чтобы синхронизировать содержание объектов TEdit с содержимом текущей записи в DataSet:
procedure TForm1.DataSource1DataChange(Sender: TObject;
Field: TField);
var
i: Integer;
begin
for i := 1 to 5 do
Edits[i].Text := Table1.Fields[i - 1].AsString;
end;
Всякий раз, когда вызывается Table1.Next, или любой другой из навигационных методов, то будет вызвана процедура показанная выше. Это обеспечивает то, что все редакторы всегда содержат данные из текущей записи.
Всякий раз, когда вызывается Post, нужно выполнить противоположное действие, то есть взять информацию из редакторов и поместить ее в текущую запись. Выполнить это действие, проще всего в обработчике события TDataSource.OnUpdateData, которое происходит всякий раз, когда вызывается Post:
procedure TForm1.DataSource1UpdateData(Sender: TObject);
var
i: Integer;
begin
for i := 1 to 5 do
Table1.Fields[i - 1].AsString := Edits[i].Text;
end;
Программа будет автоматически переключатся в режим редактирования каждый раз, когда Вы вводите что-либо в одном из редакторов. Это делается в обработчике события OnKeyDown (укажите этот обработчик ко всем редакторам):
procedure TForm1.Edit1KeyDown(Sender: TObject;
var Key: Word; Shift: TShiftState);
begin
if DataSource1.State dsEdit then
Table1.Edit;
end;
Этот код показывает, как Вы можете использовать св-во State DataSource, чтобы определить текущий режим DataSet.
Обновление метки в статусной панели происходит при изменении состояния таблицы:
procedure TForm1.DataSource1StateChange(Sender: TObject);
var
s : String;
begin
case DataSource1.State of
dsInactive : s:='Inactive';
dsBrowse : s:='Browse';
dsEdit : s:='Edit';
dsInsert : s:='Insert';
dsSetKey : s:='SetKey';
dsCalcFields : s:='CalcFields';
end;
Label6.Caption:=s;
end;
Данная программа является демонстрационной и ту же задачу можно решить гораздо проще, если использовать объекты TDBEdit.
Отслеживание состояния DataSet
В предыдущей части Вы узнали, как использовать TDataSource, чтобы узнать текущее состоянии TDataSet. Использование DataSource - это простой путь выполнения данной задачи. Однако, если Вы хотите отслеживать эти события без использования DataSource, то можете написать свои обработчики событий TTable и TQuery:
property OnOpen
property OnClose
property BeforeInsert
property AfterInsert
property BeforeEdit
property AfterEdit
property BeforePost
property AfterPost
property OnCancel
property OnDelete
property OnNewRecord
Большинство этих свойств очевидны. Событие BeforePost функционально подобно событию TDataSource.OnUpdateData, которое объяснено выше. Другими словами, программа STATE работала бы точно также, если бы Вы отвечали не на DataSource1.OnUpdateData а на Table1.BeforePost. Конечно, в первом случае Вы должен иметь TDataSource на форме, в то время, как во втором этого не требуется.
32
Урок 4: Объект TTableСоздание баз данных в Delphi
Урок 5: Компонент
TTable. Создание
таблиц
с помощью
компонента
TTable
Содержание урока 5:
Создание таблиц с помощью компонента TTable 2
Заключение 6
Обзор
На этом небольшом уроке мы завершим изучение возможностей создания таблиц. Как Вы помните, мы уже освоили два способа создания таблиц - с помощью утилиты Database Desktop, входящей в поставку Delphi и с помощью SQL-запросов, которые можно использовать как в WISQL (Windows Interactive SQL - клиентская часть Local InterBase), так и в компоненте TQuery. Теперь мы рассмотрим, как можно создавать локальные таблицы в режиме выполнения с помощью компонента TTable.
Создание таблиц с помощью компонента TTableДля создания таблиц компонент TTable имеет метод CreateTable. Этот метод создает новую пустую таблицу заданной структуры. Данный метод (процедура) может создавать только локальные таблицы формата dBase или Paradox.
Компонент TTable можно поместить на форму в режиме проектирования или создать динамически во время выполнения. В последнем случае перед использованием его необходимо создать, например, с помощью следующей конструкции:
var
Table1: TTable;
...
Table1:=TTable.Create(nil);
...
Перед вызовом метода CreateTable необходимо установить значения свойств
TableType - тип таблицы
DatabaseName - база данных
TableName - имя таблицы
FieldDefs - массив описаний полей
IndexDefs - массив описаний индексов.
Свойство TableType имеет тип TTableType и определяет тип таблицы в базе данных. Если это свойство установлено в ttDefault, тип таблицы определяется по расширению файла, содержащего эту таблицу:
Расширение .DB или без расширения: таблица Paradox
Расширение .DBF : таблица dBASE
Расширение .TXT : таблица ASCII (текстовый файл).
Если значение свойства TableType не равно ttDefault, создаваемая таблица всегда будет иметь установленный тип, вне зависимости от расширения:
ttASCII: текстовый файл
ttDBase: таблица dBASE
ttParadox: таблица Paradox.
Свойство DatabaseName определяет базу данных, в которой находится таблица. Это свойство может содержать:
BDE алиас
директорий для локальных БД
директорий и имя файла базы данных для Local InterBase
локальный алиас, определенный через компонент TDatabase.
Свойство TableName определяет имя таблицы базы данных.
Свойство FieldDefs (имеющее тип TFieldDefs) для существующей таблицы содержит информацию обо всех полях таблицы. Эта информация доступна только в режиме выполнения и хранится в виде массива экземпляров класса TFieldDef, хранящих данные о физических полях таблицы (т.о. вычисляемые на уровне клиента поля не имеют своего объекта TFieldDef). Число полей определяется свойством Count, а доступ к элементам массива осуществляется через свойство Items:
property Items[Index: Integer]: TFieldDef;
При создании таблицы, перед вызовом метода CreateTable, нужно сформировать эти элементы. Для этого у класса TFieldDefs имеется метод Add:
procedure Add(const Name: string; DataType: TFieldType; Size: Word; Required: Boolean);
Параметр Name, имеющий тип string, определяет имя поля. Параметр DataType (тип TFieldType) обозначает тип поля. Он может иметь одно из следующих значений, смысл которых ясен из их наименования:
TFieldType = (ftUnknown, ftString, ftSmallint, ftInteger, ftWord, ftBoolean, ftFloat, ftCurrency, ftBCD, ftDate, ftTime, ftDateTime, ftBytes, ftVarBytes, ftBlob, ftMemo,
ftGraphic);
Параметр Size (тип word) представляет собой размер поля. Этот параметр имеет смысл только для полей типа ftString, ftBytes, ftVarBytes, ftBlob, ftMemo, ftGraphic, размер которых может сильно варьироваться. Поля остальных типов всегда имеют строго фиксированный размер, так что данный параметр для них не принимается во внимание. Четвертый параметр - Required - определяет, может ли поле иметь пустое значение при записи в базу данных. Если значение этого параметра - true, то поле является “требуемым”, т.е. не может иметь пустого значения. В противном случае поле не является “требуемым” и, следовательно, допускает запись значения NULL. Отметим, что в документации по Delphi и online-справочнике допущена ошибка - там отсутствует упоминание о четвертом параметре для метода Add.
Если Вы желаете индексировать таблицу по одному или нескольким полям, используйте метод Add для свойства IndexDefs, которое, как можно догадаться, также является объектом, т.е. экземпляром класса TIndexDefs. Свойство IndexDefs для существующей таблицы содержит информацию обо всех индексах таблицы. Эта информация доступна только в режиме выполнения и хранится в виде массива экземпляров класса TIndexDef, хранящих данные об индексах таблицы. Число индексов определяется свойством Count, а доступ к элементам массива осуществляется через свойство Items:
property Items[Index: Integer]: TIndexDef;
Метод Add класса TIndexDefs имеет следующий вид:
procedure Add(const
Name, Fields: string;
Options: TIndexOptions);
Параметр Name, имеющий тип string, определяет имя индекса. Параметр Fields (также имеющий тип string) обозначает имя поля, которое должно быть индексировано, т.е. имя индексируемого поля. Составной индекс, использующий несколько полей, может быть задан списком имен полей, разделенных точкой с запятой “;”, например: ‘Field1;Field2;Field4’. Последний параметр - Options - определяет тип индекса. Он может иметь набор значений, описываемых типом TIndexOptions:
TIndexOptions = set of (ixPrimary, ixUnique, ixDescending,
ixCaseInsensitive, ixExpression);
Поясним эти значения. ixPrimary обозначает первичный ключ, ixUnique - уникальный индекс, ixDescending - индекс, отсортированный по уменьшению значений (для строк - в порядке, обратном алфавитному), ixCaseInsensitive - индекс, “нечувствительный” к регистру букв, ixExpression - индекс по выражению. Отметим, что упоминание о последнем значении также отсутствует в документации и online-справочнике. Опция ixExpression позволяет для таблиц формата dBase создавать индекс по выражению. Для этого достаточно в параметре Fields указать желаемое выражение, например: 'Field1*Field2+Field3'. Вообще говоря, не все опции индексов применимы ко всем форматам таблиц. Ниже мы приведем список допустимых значений для таблиц dBase и Paradox:
Опции индексов dBASE Paradox
---------------------------------------
ixPrimary
ixUnique
ixDescending
ixCaseInsensitive
ixExpression
Необходимо придерживаться указанного порядка применения опций индексов во избежание некорректной работы. Следует отметить, что для формата Paradox опция ixUnique может использоваться только вместе с опцией ixPrimary (см. пример на диске - Рис. 1).
Итак, после заполнения всех указанных выше свойств и вызова методов Add для FieldDefs и IndexDefs необходимо вызвать метод класса TTable - CreateTable:
with Table1 do
begin
DatabaseName:='dbdemos';
TableName:='mytest';
TableType:=ttParadox;
{Создать поля}
with FieldDefs do
begin
Add('Surname', ftString, 30, true);
Add('Name', ftString, 25, true);
Add('Patronymic', ftString, 25, true);
Add('Age', ftInteger, 0, false);
Add('Weight', ftFloat, 0, false);
end;
{Сгенерировать индексы}
with IndexDefs do
begin
Add('I_Name',
'Surname;Name;Patronymic',
[ixPrimary, ixUnique]);
Add('I_Age', 'Age', [ixCaseInsensitive]);
end;
CreateTable;
end;
Рис. 1: Программа CREATABL демонстрирует технику создания таблиц во время выполнения
Индексы можно сгенерировать и не только при создании таблицы. Для того чтобы сгенерировать индексы для существующей таблицы, нужно вызвать метод AddIndex класса TTable, набор параметров которого полностью повторяет набор параметров для метода Add класса TIndexDefs:
procedure
AddIndex(const Name, Fields: string;
Options:
TIndexOptions);
При этом для метода AddIndex справедливы все замечания по поводу записи полей и опций индексов, сделанные выше.
Заключение
Итак, мы познакомились с еще одним способом создания таблиц - способом, использующим метод CreateTable класса TTable. Использование данного способа придаст Вашему приложению максимальную гибкость, и Вы сможете строить локальные таблицы “на лету”. Сопутствующим методом является метод AddIndex класса TTable, позволяющий создавать индексы для уже существующей таблицы. Подчеркнем еще раз, что данный способ применим только для локальных таблиц.
6
Урок 5: Создание таблиц с помощью компонента TTable
Создание баз данных в Delphi
Урок 6: Объект TQuery
Содержание Урока 6:Содержание Урока 6: 1
Краткий Обзор 2
Основные понятия о TQuery 2
Свойство SQL 3
TQuery и Параметры 6
Передача параметров через TDataSource 10
Выполнение соединения нескольких таблиц. 12
Open или ExecSQL? 14
Специальные свойства TQuery 15
Краткий Обзор
В этой главе Вы узнаете некоторые основные понятия о запросах (queries) и транзакциях. Это достаточно широкие понятия, поэтому обсуждение разбито на следующие основные части:
Объект TQuery.
Использование SQL с локальным и удаленным серверами (Select, Update, Delete и Insert).
Использование SQL для создания объединения (joins), связанных курсоров (linked cursors) и программ, которые ведут поиск заданных записей.
Сокращение SQL означает Structured Query Language - Язык Структурированных Запросов, и обычно произноситься либо как "Sequel" либо " Ess Qu El”. Однако, как бы Вы его ни произносили, SQL - это мощный язык БД, который легко доступен из Delphi, но который отличается от родного языка Delphi. Delphi может использовать утверждения SQL для просмотра таблиц, выполнять объединение таблиц, создавать отношения один-ко-многим, или исполнить почти любое действие, которое могут сделать ваши основные инструменты БД. Delphi поставляется с Local SQL, так что Вы можете выполнять запросы SQL при работе с локальными таблицами, без доступа к SQL серверу.
Delphi обеспечивает поддержку “pass through SQL”, это означает то, что Вы можете составлять предложения SQL и посылать их непосредственно серверам Oracle, Sybase, Inrterbase и другим. “Pass through SQL” - это мощный механизм по двум причинам:
Большинство серверов могут обрабатывать SQL запросы очень быстро, а это означает, что используя SQL для удаленных данных, Вы получите ответ очень быстро.
Есть возможность составлять SQL запросы, которые заставят сервер исполнить специализированные задачи, недоступные через родной язык Delphi.
Перед чтением этой статьи Вы должны иметь, по крайней мере, элементарное понятие о серверах и различиях между локальными и удаленными (remote) данными.
Основные понятия о TQueryПредыдущий Урок был, в основном, посвящен объекту TTable, который служит для доступа к данным. При использовании TTable, возможен доступ ко всему набору записей из одной таблицы. В отличие от TTable, TQuery позволяет произвольным образом (в рамках SQL) выбрать набор данных для работы с ним. Во многом, методика работы с объектом TQuery похожа на методику работы с TTable, однако есть свои особенности.
Вы может создать SQL запрос используя компонент TQuery следующим способом:
Назначите Псевдоним (Alias) DatabaseName.
Используйте свойство SQL чтобы ввести SQL запрос типа
“Select * from Country”.
Установите свойство Active в True
Если обращение идет к локальным данным, то вместо псевдонима можно указать полный путь к каталогу, где находятся таблицы.
Две основных вещи, которые Вы должны понять прежде, чем перейти дальше:
Этот урок не является учебником для начинающих по SQL, а, скорее, описанием объекта TQuery и основных задач, которые Вы можете решить с его помощью. Если Вы не знаете ничто об SQL, Вы все же сможете воспользоваться этой статьей, и, в конце концов, приобретете некоторое понимание основ SQL. Однако, для полного изучения языка, Вы должны обратиться к любой из большого количества книг и документов, доступных по этому предмету.
Delphi использует pass through SQL, поэтому для разных SQL серверов синтаксис может быть несколько разным. Версия SQL для локальных таблиц (Local SQL) очень сильно урезан, по сравнению со стандартом. Чтобы узнать о его возможностях, Вы должны прочитать не только эту статью, но также файл LOCALSQL.HLP.
Вы увидите, что объект TQuery один из наиболее полезных и гибких компонентов, доступных в Delphi. С ним Вы сможете воспользоваться всей мощью, предоставляемой лидерами среди промышленных SQL серверов, вроде InrterBase, Oracle или Sybase.
Свойство SQLСвойство SQL - вероятно, самая важная часть TQuery. Доступ к этому свойству происходит либо через Инспектор Объектов во время конструирования проекта (design time), или программно во время выполнения программы (run time).
Интересней, конечно, получить доступ к свойству SQL во время выполнения, чтобы динамически изменять запрос. Например, если требуется выполнить три SQL запроса, то не надо размещать три компонента TQuery на форме. Вместо этого можно разместить один и просто изменять свойство SQL три раза. Наиболее эффективный, простой и мощный способ - сделать это через параметризованные запросы, которые будут объяснены в следующей части. Однако, сначала исследуем основные особенности свойства SQL, а потом рассмотрим более сложные темы, типа запросов с параметрами.
Свойство SQL имеет тип TStrings, который означает что это ряд строк, сохраняемых в списке. Список действует также, как и массив, но, фактически, это специальный класс с собственными уникальными возможностями. В следующих нескольких абзацах будут рассмотрены наиболее часто используемые свойства.
При программном использовании TQuery, рекомендуется сначала закрыть текущий запрос и очистить список строк в свойстве SQL:
Query1.Close;
Query1.SQL.Clear;
Обратите внимание, что всегда можно “безопасно” вызвать Close. Даже в том случае, если запрос уже закрыт, исключительная ситуация генерироваться не будет.
Следующий шаг - добавление новых строк в запрос:
Query1.SQL.Add(‘Select * from Country’);
Query1.SQL.Add(‘where Name = ’’Argentina’’’);
Метод Add используется для добавления одной или нескольких строк к запросу SQL. Общий объем ограничен только количеством памяти на вашей машине.
Чтобы Delphi отработал запрос и возвратил курсор, содержащий результат в виде таблицы, можно вызвать метод:
Query1.Open;
Демонстрационная программа THREESQL показывает этот процесс (см Рис.1)
Рис.1: Программа THREESQL показывает, как сделать несколько запросов с помощью единственного объекта TQuery.
Программа THREESQL использует особенность локального SQL, который позволяет использовать шаблоны поиска без учета регистра (case insensitive). Например, следующий SQL запрос:
Select * form Country where Name like ’C%’
возвращает DataSet, содержащий все записи, где поле Name начинается с буквы ‘C’. Следующий запрос позволит увидеть все страны, в названии которых встречается буква ‘C’:
Select * from Country where Name like ‘%C%’;
Вот запрос, которое находит все страны, название которых заканчивается на ‘ia’:
Select * from Country where Name like ‘%ia’;
Одна из полезных особенностей свойства SQL - это способность читать файлы, содержащие текст запроса непосредственно с диска. Эта особенность показана в программе THREESQL.
Вот как это работает. В директории с примерами к данному уроку есть файл с расширением SQL. Он содержат текст SQL запроса. Программа THREESQL имеет кнопку с названием Load, которая позволяет Вам выбрать один из этих файлов и выполнять SQL запрос, сохраненный в этом файле.
Кнопка Load имеет следующий метод для события OnClick:
procedure TForm1.LoadClick(Sender: TObject);
begin
if OpenDialog1.Execute then
with Query1 do begin
Close;
SQL.LoadFromFile(OpenDialog1.FileName);
Open;
end;
end;
Метод LoadClick сначала загружает компоненту OpenDialog и позволяет пользователю выбрать файл с расширением SQL. Если файл выбран, текущий запрос закрывается, выбраный файл загружается с диска в св-во SQL, запрос выполняется и результат показывается пользователю.
TQuery и ПараметрыDelphi позволяет составить “гибкую” форму запроса, называемую параметризованным запросом. Такие запросы позволяют подставить значение переменной вместо отдельных слов в выражениях “where” или “insert”. Эта переменная может быть изменена практически в любое время. (Если используется локальный SQL, то можно сделать замену почти любого слова в утверждении SQL, но при этом та же самая возможность не поддерживается большинством серверов.)
Перед тем, как начать использовать параметризованные запросы, рассмотрим снова одно из простых вышеупомянутых предложений SQL:
Select * from Country where Name like ’C%’
Можно превратить это утверждение в параметризованный запрос заменив правую часть переменной NameStr:
select * from County where Name like :NameStr
В этом предложении SQL, NameStr не является предопределенной константой и может изменяться либо во время дизайна, либо во время выполнения. SQL parser (программа, которая разбирает текст запроса) понимает, что он имеет дело с параметром, а не константой потому, что параметру предшествует двоеточие ":NameStr". Это двоеточие сообщает Delphi о необходимости заменить переменную NameStr некоторой величиной, которая будет известна позже.
Обратите внимание, слово NameStr было выбрано абсолютно случайно. Использовать можно любое допустимое имя переменной, точно также, как выбирается идентификатор переменной в программе.
Есть два пути присвоить значение переменной в параметризованном запросе SQL. Один способ состоит в том, чтобы использовать свойство Params объекта TQuery. Второй - использовать свойство DataSource для получения информации из другого DataSet. Вот ключевые свойства для достижения этих целей:
property Params[Index: Word];
function ParamByName(const Value: string);
property DataSource;
Если подставлять значение параметра в параметризованный запрос через свойство Params, то обычно нужно сделать четыре шага:
Закрыть TQuery
Подготовить объект TQuery, вызвав метод Prepare
Присвоить необходимые значения свойству Params
Открыть TQuery
Второй шаг выполняется в том случае, если данный текст запроса выполняется впервые, в дальнейшем его можно опустить.
Вот фрагмент кода, показывающий как это может быть выполнено практически:
Query1.Close;
Query1.Prepare;
Query1.Params[0].AsString := ‘Argentina’;
Query1.Open;
Этот код может показаться немного таинственным. Чтобы понять его, требуется внимательный построчный анализ. Проще всего начать с третьей строки, так как свойство Params является “сердцем” этого процесса.
Params - это индексированное свойство, которое имеет синтаксис как у свойства Fields для TDataSet. Например, можно получить доступ к первой переменной в SQL запросе, адресуя нулевой элемент в массиве Params:
Params[0].AsString := ‘”Argentina”’;
Если параметризованный SQL запрос выглядит так:
select * from Country where Name = :NameStr
то конечный результат (т.е. то, что выполнится на самом деле) - это следующее предложение SQL:
select * from Country where Name = “Argentina”
Все, что произошло, это переменной :NameStr было присвоено значение "Аргентина" через свойство Params. Таким образом, Вы закончили построение простого утверждения SQL.
Если в запросе содержится более одного параметра, то доступаться к ним можно изменяя индекс у свойства Params
Params[1].AsString := ‘SomeValue’;
либо используя доступ по имени параметра
ParamByName(‘NameStr’).AsString:=’”Argentina”’;
Итак, параметризованные SQL запросы используют переменные, которые всегда начинаются с двоеточия, определяя места, куда будут переданы значения параметров.
Прежде, чем использовать переменную Params, сначала можно вызвать Prepare. Этот вызов заставляет Delphi разобрать ваш SQL запрос и подготовить свойство Params так, чтобы оно "было готово принять” соответствующее количество переменных. Можно присвоить значение переменной Params без предварительного вызова Prepare, но это будет работать несколько медленнее.
После того, как Вы вызывали Prepare, и после того, как присвоили необходимые значения переменной Params, Вы должны вызвать Open, чтобы закончить привязку переменных и получить желаемый DataSet. В нашем случае, DataSet должен включать записи где в поле “Name” стоит “Argentina”.
Рассмотрим работу с параметрами на примере (программа PARAMS.DPR). Для создания программы, разместите на форме компоненты TQuery, TDataSource, TDBGrid и TTabSet. Соедините компоненты и установите в свойстве TQuery.DatabaseName псевдоним DBDEMOS. См. рис.2
Рис.2 : Программа PARAMS во время дизайна.
В обработчике события для формы OnCreate напишем код, заполняющий закладки для TTabSet, кроме того, здесь подготавливается запрос:
procedure TForm1.FormCreate(Sender: TObject);
var
i : Byte;
begin
Query1.Prepare;
for i:=0 to 25 do
TabSet1.Tabs.Add(Chr(Byte('A')+i));
end;
Текст SQL запроса в компоненте Query1:
select * from employee where LastName like :LastNameStr
Запрос выбирает записи из таблицы EMPLOYEE, в которых поле LastName похоже (like) на значение параметра :LastNameStr. Параметр будет передаваться в момент переключения закладок:
procedure TForm1.TabSet1Change(Sender: TObject;
NewTab: Integer;
var AllowChange: Boolean);
begin
with Query1 do begin
Close;
Params[0].AsString:=
'"'+TabSet1.Tabs.Strings[NewTab]+'%"';
Open;
end;
end;
Рис.3: Программа PARAMS во время выполнения.
Передача параметров через TDataSource
В предыдущем Уроке Вы видели способ создания отношения однин-ко-многим между двумя таблицами. Теперь речь пойдет о выполнении того же самого действия с использованием объекта TQuery. Этот способ более гибок в том отношении, что он не требует индексации по полям связи.
Объект TQuery имеет свойство DataSource, которое может использоваться для того, чтобы создать связь с другим DataSet. Не имеет значения, является ли другой DataSet объектом TTable, TQuery, или некоторый другим потомком TDataSet. Все что нужно для установления соединения - это удостовериться, что у того DataSet есть связанный с ним DataSource.
Предположим, что Вы хотите создать связь между таблицами ORDERS и CUSTOMERS так, что каждый раз, когда Вы просматриваете конкретную запись о заказчике, будут видны только заказы, связанные с ним.
Рассмотрите следующий параметризованный запрос:
select * from Orders where CustNo = :CustNo
В этом запросе :CustNo - связывающая переменная, которой должно быть присвоено значение из некоторого источника. Delphi позволяет использовать поле TQuery.DataSource чтобы указать другой DataSet, который предоставит эту информацию автоматически. Другими словами, вместо того, чтобы использовать свойство Params и “вручную” присваивать значения переменной, эти значения переменной могут быть просто взяты автоматически из другой таблицы. Кроме того, Delphi всегда сначала пытается выполнить параметризованный запрос используя свойство DataSource, и только потом (если не было найдено какое-то значение параметра) будет пытаться получить значение переменной из свойства Params. При получении данных из DataSource считается, что после двоеточия стоит имя поля из DataSource. При изменении текущей записи в главном DataSet запрос будет автоматически пересчитываться.
Давайте переделаем пример из прошлого урока (LINKTBL - связывание двух таблиц). Создайте новый проект, положите на форму один набор TTable, TDataSource и TDBGrid. Привяжите его к таблице CUSTOMER. Положите на форму второй набор - TQuery, TDataSource и TDBGrid и свяжите объекты между собой. (см рис.4).
В свойстве SQL наберите текст запроса:
select * from Orders where CustNo = :CustNo
В свойстве DatabaseName для Query1 укажите DBDEMOS.
В свойстве DataSource для Query1 укажите DataSource1.
Поставьте Active = True и запустите программу.
Рис.4: Программа LINKQRY - связанные курсоры с помощью SQL
Выполнение соединения нескольких таблиц.
Вы видели что таблицы CUSTOMERS и ORDERS связаны в отношении один-ко-многим, основанному на поле CustNo. Таблицы ORDERS и ITEMS также связаны отношении один-ко-многим, только через поле OrderNo.
Более конкретно, каждый заказ который существует в таблице ORDERS будет иметь несколько записей в таблице ITEMS, связанных с этим заказом. Записи из таблицы ITEMS определяют тип и количество изделий, связанных с этим заказом.
Пример.
Некто Иванов Ф.П. 1 мая 1995г. заказал следующее:
Гайка 4х-угольная - 50 штук
Вентиль - 1 штука
А некто Сидорчук Ю.Г. 8 декабря 1994г. заказал:
М/схема КР580 ИК80 - 10 штук
Транзистор КТ315 - 15 штук
Моток провода - 1 штука
В ситуации подобной этой, иногда проще всего "соединить" данные из таблиц ORDERS и ITEMS так, чтобы результирующий DataSet содержал информацию из обеих таблиц:
Иванов Ф.П. 1 мая 1995г Гайка 4х-угольная 50 штук
Иванов Ф.П. 1 мая 1995г Вентиль 1 штука
Сидорчук Ю.Г. 8 декабря 1994г М/схема КР580 ИК80 10 штук
Сидорчук Ю.Г. 8 декабря 1994г Транзистор КТ315 15 штук
Сидорчук Ю.Г. 8 декабря 1994г Моток провода 1 штука
Слияние этих двух таблиц называется "соединение" и это одно из фундаментальных действий, которые Вы можете выполнить на наборе двух или больше таблиц.
Взяв таблицы ORDERS и ITEMS из подкаталога DEMOS\DATA, их можно соединить их таким путем, что поля CustNo, OrderNo и SaleDate из таблицы ORDERS будут “слиты” с полями PartNo и Qty из таблицы ITEMS и сформируют новый DataSet, содержащий все пять полей. Grid содержащий результирующий DataSet показан на рис.5
Рис.5: Соединение таблиц ORDERS и ITEMS может быть сделано так, что формируется новый DataSet содержащий поля из каждой таблицы.
Имеется существенное различие между связанными курсорами и соединенными таблицами. Однако они имеют две общие черты:
И те, и другие используют две или более таблиц
Каждый таблица связана с другой по одному или более одинаковых полей.
Соединение таблиц ORDERS и ITEMS может быть выполнено единственным SQL запросом, который выглядит так:
select
O.CustNo, O.OrderNo, O.SaleDate, I.PartNo, I.Qty
from Orders O, Items I
where O.OrderNo = I.OrderNo
Этот запрос состоит из четырех различных частей:
Выражение Select определяет, что Вы хотите получить - курсор, содержащий некоторую форму DataSet.
Затем идет список полей которые Вы хотите включить в dataset. Этот список включает поля CustNo, OrderNo, SaleDate, PartNo и Qty. Первые три поля из таблицы ORDERS, а два других - из таблицы ITEMS.
Выражение from объявляет, что Вы работаете с двумя таблицами, одна называется ORDERS, а другая ITEMS. Для краткости, в запросе используется особенность SQL, которая позволяет Вам ссылаться на таблицу ORDERS буквой O, а на таблицу ITEMS буквой I.
Выражение where жизненно важно потому, что оно определяет поля связи для двух таблиц. Некоторые серверы могут вернуть DataSet, даже если Вы не включите выражение where в запрос, но почти всегда результирующий набор записей будет не тем, что Вы хотели видеть. Чтобы получить нужный результат, убедитесь что Вы включили выражение where.
Open или ExecSQL?После того, как составлен SQL запрос, есть два различных способа выполнить его. Если Вы хотите получить курсор, то нужно вызывать Open. Если выражение SQL не подразумевает возвращение курсора, то нужно вызывать ExecSQL. Например, если происходит вставка, удаление или обновление данных (т.е. SQL запросы INSERT, DELETE, UPDATE), то нужно вызывать ExecSQL. Тоже самое можно сказать по-другому: Open вызывается при запросе типа SELECT, а ExecSQL - во всех остальных случаях.
Вот типичный SQL запрос, который используется для удаления записи из таблицы:
delete from Country where Name = ‘Argentina’;
Этот запрос удалил бы любую запись из таблицы COUNTRY, которая имеет значение "Argentina" в поле Имя.
Не трудно заметить, что это тот случай, когда удобно использовать параметризованный запрос. Например, неплохо было бы менять имя страны, которую требуется удалить:
delete from Country where Name = :CountryName
В этом случае переменная :CountryName может быть изменена во время выполнения:
Query2.Prepare;
Query2.Params[0] := ‘Argentina’;
Query2.ExecSQL;
Код сначала вызывает Prepare, чтобы сообщить Delphi что он должен разобрать SQL запрос и подготовить свойство Params. Следующим шагом присваивается значение свойству Params и затем выполняется подготовленный SQL запрос. Обратите внимание, что он выполняется через ExecSQL, а не Open.
Программа INSQUERY из примеров Delphi демонстрирует эту технику (проект C:\DELPHI\DEMOS\DB\INSQUERY.DPR)
Специальные свойства TQueryЕсть несколько свойств, принадлежащих TQuery, которые еще не упоминались:
property UniDirectional: Boolean;
property Handle: HDBICur;
property StmtHandle: HDBIStmt;
property DBHandle: HDBIDB;
Свойство UniDirectional используется для того, чтобы оптимизировать доступ к таблице. Если Вы установите UniDirectional в True, то Вы можете перемещаться по таблице более быстро, но Вы сможете двигаться только вперед.
Свойство StmtHandle связано со свойством Handle TDataSet. То есть, оно включено исключительно для того, что Вы могли делать вызовы Borland Database Engine напрямую. При нормальных обстоятельствах, нет никакой необходимости использовать это свойство, так как компоненты Delphi могут удовлетворить потребностями большинства программистов. Однако, если Вы знакомы с Borland Database Engine, и если Вы знаете что существуют некоторые возможности не поддерживаемые в VCL, то Вы можете использовать TQuery.StmtHandle, или TQuery. Handle, чтобы сделать вызов напрямую в engine.
Следующий фрагмент кода показывает два запроса к BDE:
var
Name: array[0..100] of Char;
Records: Integer;
begin
dbiGetNetUserName(Name);
dbiGetRecordCount(Query1.Handle, Records);
end;
16
Урок 6: Объект TQueryСоздание баз данных в Delphi
Урок 7: Редактор DataSet, Вычисляемые поля Содержание Урока 7:
Урок 7: Редактор DataSet, Вычисляемые поля 1
Содержание Урока 7: 1
Обзор 2
Редактор DataSet 2
Вычисляемые Поля 5
Управление TDBGrid во время выполнения 9
ОбзорВ этой статье вы узнаете о Редакторе DataSet и о способах управления компонентом TDBGrid во время выполнения программы. Здесь же будут рассмотрены вычисляемые поля - весьма ценная особенность Редактора DataSet.
Примеры, которые вы увидите в этой статье, продемонстрируют основные способы, которыми пользуются большинство программистов для показа таблиц БД пользователям. Для понимания большей части материала требуется общее знание среды и языка Delphi.
Редактор DataSetРедактор DataSet может быть вызван с помощью объектов TTable или TQuery. Чтобы начать работать с ним, положите объект TQuery на форму, установите псевдоним DBDEMOS, введите SQL запрос "select * from customer" и активизируйте его (установив св-во Active в True).
Откройте комбобокс “Object Selector” вверху Инспектора Объектов - в настоящее время там имеется два компонента: TForm и TQuery.
Нажмите правую кнопку мыши на объекте TQuery и в контекстном меню выберите пункт “Fields Editor”. Нажмите кнопку Add - появиться диалог Add Fields, как показано на рис.1
Рис.1: Диалог Add Fields Редактора DataSet.
По-умолчанию, все поля в диалоге выбраны. Нажмите на кнопку OK, чтобы выбрать все поля, и закройте редактор. Снова загляните в “Object Selector”, теперь здесь появилось несколько новых объектов, (см. рис.2)
Рис.2: Object Selector показывает в списке все объекты созданные в Редакторе DataSet. Вы можете также найти этот список в определении класса TForm1.
Эти новые объекты будут использоваться для визуального представления таблицы CUSTOMER пользователю.
Вот полный список объектов, которые только что созданы:
Query1CustNo: TFloatField;
Query1Company: TStringField;
Query1Addr1: TStringField;
Query1Addr2: TStringField;
Query1City: TStringField;
Query1State: TStringField;
Query1Zip: TStringField;
Query1Country: TStringField;
Query1Phone: TStringField;
Query1FAX: TStringField;
Query1TaxRate: TFloatField;
Query1Contact: TStringField;
Query1LastInvoiceDate: TDateTimeField;
Я вырезал и вставил этот список из определения класса TForm1, которое можно найти в окне Редактора исходного текста. Происхождение имен показанных здесь, должно быть достаточно очевидно. Часть "Query1" берется по-умолчанию от имени объекта TQuery, а вторая половина от имени поля в таблице Customer. Если бы мы сейчас переименовали объект Query1 в Customer, то получили бы такие имена:
CustomerCustNo
CustomerCompany
Это соглашение может быть очень полезно, когда Вы работаете с несколькими таблицами, и сразу хотите знать, на поле какой таблицы ссылается данная переменная.
Любой объект, созданный в редакторе DataSet является наследником класса TField. Точный тип потомка зависит от типа данных в конкретном поле. Например, поле CustNo имеет тип TFloatField, а поле Query1City имеет тип TStringField. Это два типа полей, которые Вы будете встречать наиболее часто. Другие типы включают тип TDateTimeField, который представлен полем Query1LastInvoiceDate, и TIntegerField, который не встречается в этой таблице.
Чтобы понять, что можно делать с потомками TField, откройте Browser, выключите просмотр полей Private и Protected, и просмотрите свойства и методы Public и Published соответствующих классов.
Наиболее важное свойство называется Value. Вы можете получить доступ к нему так:
procedure TForm1.Button1Click(Sender: TObject);
var
d: Double;
S: string;
begin
d := Query1CustNo.Value;
S := Query1Company.Value;
d:=d+1;
S := 'Zoo';
Query1CustNo.Value := d;
Query1Company.Value := S;
end;
В коде, показанном здесь, сначала присваиваются значения переменным d и S. Следующие две строки изменяют эти значения, а последний две присваивают новые значения объектам. Не имеет большого смысла писать код, подобный этому, в программе, но этот код служит лишь для того, чтобы продемонстрировать синтаксис, используемый с потомками TField.
Свойство Value всегда соответствует типу поля, к которому оно относится. Например у TStringFields - string, TCurrencyFields - double. Однако, если вы отображаете поле типа TCurrencyField с помощью компонент, “чувствительных к данным” (data-aware: TDBEdit, TDBGrid etc.), то оно будет представлена строкой типа: "$5.00".
Это могло бы заставить вас думать, что у Delphi внезапно отключился строгий контроль типов. Ведь TCurrencyField.Value объявлена как Double, и если Вы пробуете присвоить ему строку, Вы получите ошибку “type mismatch” (несоответствие типа). Вышеупомянутый пример демонстрирует на самом деле свойства объектов визуализации данных, а не ослабление проверки типов. (Однако, есть возможность получить значение поля уже преобразованное к другому типу. Для этого у TField и его потомков имеется набор методов типа AsString или AsFloat. Конечно, преобразование происходит только тогда, когда имеет смысл.)
Если нужно получить имена полей в текущем DataSet, то для этого используется свойство FieldName одним из двух способов, показанных ниже:
S := Query1.Fields[0].FieldName;
S := Query1CustNo.FieldName;
Если вы хотите получить имя объекта, связанного с полем, то вы должны использовать свойство Name:
S := Query1.Fields[0].Name;
S := Query1CustNo.Name;
Для таблицы CUSTOMER, первый пример вернет строку "CustNo", а любая из строк второго примера строку "Query1CustNo".
Вычисляемые ПоляСоздание вычисляемых полей - одно из наиболее ценных свойств Редактора DataSet. Вы можете использовать эти поля для различных целей, но два случая выделяются особо:
выполнение вычислений по двум или более полям в DataSet, и отображение результата вычислений в третьем поле.
имитация соединения двух таблиц с возможностью редактировать результат соединения.
Программа CALC_SUM.DPR из примеров к данному уроку иллюстрирует первый случай использования вычисляемых полей.
Эта программа связывает три таблицы в отношении один ко многим. В частности, ORDERS и ITEMS связаны по полю OrderNo, а ITEMS и PARTS связаны по полю PartNo. (В таблице ORDERS хранятся все заказы; в таблице ITEMS - предметы, указанные в заказах; PARTS - справочник предметов). В программе можно перемещаться по таблице ORDERS и видеть связанный с текущим заказом список включенных в него предметов. Программа CALC_SUM достаточно сложная, но хорошо иллюстрирует мощность вычисляемых полей.
Последовательность создания проекта CALC_SUM:
Создайте новый проект (File|New Project) и удалите из него форму (в Менеджере Проекта View|Project Manager)
Выберите эксперта форм БД из меню Help.
На первом экране, выберите "Create a master/detail form" и "Create a form using TQuery Objects".
Нажмите кнопку Next и выберите таблицу ORDERS.DB из псевдонима БД DBDEMOS.
Нажмите Next и выберите поля OrderNo, CustNo, SaleDate, ShipDate и ItemsTotal из таблицы ORDERS.DB.
Нажмите Next и выберите "Horizontal" из расстановки компонентов dbEdit на форме.
Нажмите Next и выберите таблицу ITEMS.DB.
В двух следующих экранах выберите все поля из таблицы и поместите их в grid.
Нажмите Next и выберите поле OrderNo из Master и Detail ListBoxes, и Нажмите кнопку Add.
Нажмите Next и сгенерируйте форму.
Требуется много слов для того, чтобы описать процесс показанный выше, но, фактически, выполнение команд в Эксперте форм БД легко и интуитивно.
Выделите первый из двух объектов TQuery и установят свойство Active в True. Для Query2 в свойстве SQL напишите текст запроса:
select * from Items I, Parts P
where (I.OrderNo =:OrderNo) and
(I.PartNo=P.PartNo)
Активизируйте объект Query2 (Active установите в True) и вызовите редактор DataSet (Fields Editor) для него. Вызовите диалог Add Fields и добавьте поля OrderNo, PartNo, Qty и ListPrice.
Нажмите Define и ведите слово Total в поле FieldName. Установите Field Type в CurrencyField. Проверьте что Calculated CheckBox отмечен. Нажмите Ok и закройте редактор DataSet.
Простой процесс описанный в предыдущем абзаце, показывает как создать вычисляемое поле. Если посмотреть в DBGrid, то можно видеть, что там теперь есть еще одно пустое поле. Для того, чтобы поместить значение в это поле, откройте в Инспекторе Объектов страницу событий для объекта Query2 и сделайте двойной щелчок на OnCalcFields. Заполните созданный метод так:
procedure TForm2.Query2CalcFields(DataSet: TDataSet);
begin
Query2NewTotalInvoice.Value := 23.0;
end;
После запуска программы поле Total будет содержит строку $23.00.
Это показывает, насколько просто создать вычисляемое поле, которое показывает правильно сформатированные данные. На самом деле это поле должно показывать нечто другое - произведение полей Qty (количество) и ListPrice (цена). Для этого вышеприведенный код для события OnCalcFields нужно изменить следующим образом:
procedure TForm1.Query2CalcFields(DataSet: TDataset);
begin
Query2Total.Value:=Query2Qty.Value*Query2ListPrice.Value;
end;
Если теперь запустить программу, то поле Total будет содержать требуемое значение.
В обработчике события OnCalcFields можно выполнять и более сложные вычисления (это будет показано позже), однако следует помнить, что это вызывает соответствующее замедление скорости работы программы.
Теперь давайте добавим вычисляемое поле для первой таблицы (Query1, ORDERS), которое будет отображать сумму значений из поля Total второй таблицы (Query2) для данного заказа. Вызовите редактор DataSet для объекта Query1 и добавьте вычисляемое поле NewItemsTotal типа CurrencyField. В обработчике события OnCalcFields для Query1 нужно подсчитать сумму и присвоить ее полю NewItemsTotal:
procedure TForm1.Query1CalcFields(DataSet: TDataset);
var
R : Double;
begin
R:=0;
with Query2 do begin
DisableControls;
Close;
Open;
repeat
R:=R+Query2Total.Value;
Next;
until EOF;
First;
EnableControls;
end;
Query1NewItemsTotal.Value:=R;
end;
В данном примере сумма подсчитывается с помощью простого перебора записей, это не самый оптимальный вариант - можно, например, для подсчета суммы использовать дополнительный объект типа TQuery. Метод DisableControls вызывается для того, чтобы отменить перерисовку DBGrid при сканировании таблицы. Запрос Query2 переоткрывается для уверенности в том, что его текущий набор записей соответствует текущему заказу.
Поместите на форму еще один элемент DBEdit и привяжите его к Query1, полю NewItemsTotal. Запустите программу, ее примерный вид показан на рис.3
Рис.3: Программа CALC_SUM
Как видно из программы, наличие поля ItemsTotal в таблице ORDERS для данного примера необязательно и его можно было бы удалить (однако, оно необходимо в других случаях).
Управление TDBGrid во время выполнения
Объект DBGrid может быть полностью реконфигурирован во время выполнения программы. Вы можете прятать и показывать колонки, изменять порядок показа колонок и их ширину.
Вы можете использовать свойство Options объекта DBGrid, чтобы изменить ее представление. Свойство Options может принимать следующие возможные значения:
dgEditing | Установлен по-умолчанию в true, позволяет пользователю редактировать grid. Вы можете также установить свойство ReadOnly grid в True или False. |
dgAlwaysShowEditor | Всегда показывать редактор. |
dgTitles | Показывать названия колонок. |
dgIndicator | Показывать небольшие иконки слева. |
dgColumnResize | Может ли пользователь менять размер колонки. |
dgColLines | Показывать линии между колонками. |
dgRowLines | Показывать линии между строками. |
dgTabs | Может ли пользователь использовать tab и shift-tab для переключения между колонками. |
dgRowSelect | Выделять всю запись целиком. |
dgAlwaysShowSelection | Всегда показывать выбранные записи. |
dgConfirmDelete | Подтверждать удаление. |
dgCancelOnExit | Отмена изменений при выходе из DBGrid. |
dgMultiSelect | Одновременно может быть выделена больше чем одна запись. |
Как объявлено в этой структуре:
DBGridOption = (dgEditing, dgAlwaysShowEditor, dgTitles, dgIndicator, dgColumnResize, dgColLines, dgRowLines, dgTabs, dgRowSelect,dgAlwaysShowSelection, dgConfirmDelete, dgCancelOnExit, dgMultiSelect);
Например Вы можете установить опции в Runtime написав такой код:
DBGrid1.Options := [dgTitles, dgIndicator];
Если Вы хотите включать и выключать опции, это можно сделать с помощью логических операций. Например, следующий код будет добавлять dgTitles к текущему набору параметров:
DBGrid1.Options := DBGrid1.Options + [dgTitles];
Пусть есть переменная ShowTitles типа Boolean, тогда следующий код позволяют включать и выключать параметр одной кнопкой:
procedure TForm1.Button3Click(Sender: TObject);
begin
if ShowTitles then
DBGrid1.Options := DBGrid1.Options + [dgTitles]
else
DBGrid1.Options := DBGrid1.Options - [dgTitles];
ShowTitles := not ShowTitles;
end;
Если Вы хотите скрыть поле в run-time, то можете установить свойство visible в false:
Query1.FieldByName(‘CustNo’).Visible := False;
Query1CustNo.Visible := False;
Обе строки кода выполняют идентичную задачу. Чтобы показать поле снова, установите видимый в true:
Query1.FieldByName(‘CustNo’).Visible := True;
Query1CustNo.Visible := True;
Если Вы хотите изменить положение колонки в Runtime, можете просто изменить индекс, (первое поле в записи имеет индекс нуль):
Query1.FieldByName(‘CustNo’).Index := 1;
Query1CustNo.Index := 2;
По-умолчанию, поле CustNo в таблице Customer является первым. Код в первой строке перемещает это поле во вторую позицию, а следующая строка перемещает его в третью позицию. Помните, что нумерация полей начинается с нуля, так присвоение свойству Index 1 делает поле вторым в записи. Первое поле имеет Index 0.
Когда Вы изменяете индекс поля, индексы других полей в записи изменяются автоматически.
Если Вы хотите изменить ширину колонки в Runtime, только измените свойство DisplayWidth соответствующего TField.
Query1.FieldByName(‘CustNo’).DisplayWidth := 12;
Query1CustNo.DisplayWidth := 12;
Величина 12 относится к числу символов, которые могут быть показаны в видимом элементе.
Программа DBGR_RT показывает как работать с DBGrid в Runtime. Программа достаточно проста, кроме двух небольших частей, которые описаны ниже. Первая часть показывает, как создать check box в Runtime, а вторая показывает, как изменить порядок пунктов в listbox в Runtime.
При создании формы (событие OnCreate) ListBox заполняется именами полей, далее создается массив объектов CheckBox, соответствующий полям в таблице. Сперва все CheckBox’ы выбраны и все поля в таблице видимы. Программа узнает через TTable1 имена полей и присваивает их свойству Caption соответствующего CheckBox. Кроме того, обработчику события OnClick всех CheckBox’ов присваивается процедура ChBClick, которая и включает/выключает поля в DBGrid.
procedure TForm1.FormCreate(Sender: TObject);
var
i : Word;
R : Array[0..49] of TCheckBox;
begin
{Fill ListBox}
ListBox1.Clear;
for i:=0 to Table1.FieldCount-1 do
ListBox1.Items.Add(Table1.Fields[i].FieldName);
{Make CheckBoxes}
for i:=0 to Table1.FieldCount-1 do begin
R[I] := TCheckBox.Create(Self);
R[I].Parent := ScrollBox1;
R[I].Caption := Table1.Fields[i].FieldName;
R[I].Left := 10;
R[I].Top := I * CheckBox1.Height + 5;
R[I].Width := 200;
R[I].Checked := True;
R[I].OnClick := ChBClick;
end;
end;
Большая часть кода в этом примере выполняет относительно простые задачи, типа назначения имен и положений check boxes. Вот две ключевых строки:
R[I] := TCheckBox.Create(Self);
R[I].Parent := ScrollBox1;
Первая строки создает CheckBox с заданным Owner (Владельцем). Вторая строки назначает Parent (Родителя) для CheckBox. Чтобы понять различия между Родителем и Владельцем, посмотрите соответствующие свойства в online-help.
Программа содержит ListBox, который показывает текущий порядок полей в DataSet. Для изменения порядка полей в DataSet (а, следовательно, в DBGrid) используются две кнопки. При нажатии на одну из кнопок, выбранное в ListBox’е поле перемещается на одну позицию вверх или вниз. Синхронно с этим меняется и порядок полей в DBGrid. Код, показанный ниже, изменяет Index поля для Table1, изменяя, таким образом, позицию поля в DBGrid. Эти изменения касаются только визуального представления DataSet. Физически данные на диске не изменяются.
procedure TForm1.downButtonClick(Sender: TObject);
var
i : Integer;
begin
with ListBox1 do
if
(ItemIndex '1-1-94' получить
список сотрудников,
принятых
на работу после
1
января 1994 года
FIRST_NAME LAST_NAME HIRE_DATE
=============== ==================== ===========
Pierre Osborne 3-JAN-1994
John Montgomery 30-MAR-1994
Mark Guckenheimer 2-MAY-1994
Значения дат можно сравнивать друг с другом, сравнивать с относительными датами, вычитать одну дату из другой.
SELECT first_name, last_name, hire_date
FROM employee
WHERE
'today' - hire_date > 365 * 7 + 1
получить
список служащих,
проработавших
на предприятии
к
настоящему
времени
более
7 лет
FIRST_NAME LAST_NAME HIRE_DATE
=============== ==================== ===========
Robert Nelson 28-DEC-1988
Bruce Young 28-DEC-1988
Агрегатные функции
К агрегирующим функциям относятся функции вычисления суммы (SUM), максимального (SUM) и минимального (MIN) значений столбцов, арифметического среднего (AVG), а также количества строк, удовлетворяющих заданному условию (COUNT).
SELECT count(*), sum (budget), avg (budget),
min (budget), max (budget)
FROM department
WHERE
head_dept = 100 вычислить:
количество
отделов,
являющихся
подразделениями
отдела 100 (Маркетинг
и продажи),
их
суммарный,
средний, мини- мальный
и максимальный
бюджеты
COUNT SUM AVG MIN MAX
====== =========== ========== ========== ===========
5 3800000.00 760000.00 500000.00 1500000.00
Предложение FROM команды SELECTВ предложении FROM перечисляются все объекты (один или несколько), из которых производится выборка данных (рис.2). Каждая таблица или представление, о которых упоминается в запросе, должны быть перечислены в предложении FROM.
Ограничения на число выводимых строкЧисло возвращаемых в результате запроса строк может быть ограничено путем использования предложения WHERE, содержащего условия отбора (предикат, рис.2). Условие отбора для отдельных строк может принимать значения true, false или unnown. При этом запрос возвращает в качестве результата только те строки (записи), для которых предикат имеет значение true.
Типы предикатов, используемых в предложении WHERE:
сравнение с использованием реляционных операторов
= равно
не равно
!= не равно
> больше
< меньше
>= больше или равно
Похожие работы
... данных. ЗАКЛЮЧЕНИЕ Данный курсовой проект является актуальным и отвечает предъявленым к нему требованиям. Была разработана и написана, на языке программирования высокого уровня Borland Delphi 5.0, с использованием баз данных, программа, позволяющая определить лучшую группу колледжа по среднему баллу аттестационной и экзаменационной сессии. Список использованной литературы 1. Delphi 5.0, учебный ...
... , и на практике 5NF не используется. Заметим, что зависимость соединения является обобщением как многозначной зависимости, так и функциональной зависимости. 4. РАЗРАБОТКА БАЗЫ ДАННЫХ 4.1 Предметная область базы данных База данных предназначена для хранения информации об электронных источниках литературы в виде файлов, упакованных в архивы. Файлы архивов физически располагаются на сервере ...
... на номер страны ID_Language Short Данное поле служит для связывания c таблицей “Language”, ссылка на номер языка 4 Разработка программы 4.1 Программная реализация В результате работы над курсовым проектом была разрабоатна программа на языке Object Pascal, реализующая базу данных флагов мира. Программа состоит из 7 модулей и файла проекта. 4.2 Анализ программного кода Функции и ...
... , подсистему поддержки времени исполнения, которая интерпретирует программы манипуляции данными, создающие пользовательский интерфейс с СУБД, а также сервисные программы (внешние утилиты), обеспечивающие ряд дополнительных возможностей по обслуживанию информационной системы. 3 Программное обеспечение Для разработки курсового проекта выбрана среда объектно-ориентированного программирования ...
0 комментариев