На прошлых лекциях мы изучили немало способов создания и обработки таблиц. В большинстве случаев, база данных и таблицы в ней проектируются и создаются заранее, например, такими утилитами, как Database Desktop, или СУБД MS Access. Однако иногда приходится создавать таблицы программно, то есть, не во время проектирования приложения, а во время его работы. Предположим, каждый пользователь программы должен иметь свою собственную таблицу в базе данных, или даже свою собственную БД. Сделать это заранее программист не может, ведь неизвестно, сколько пользователей на одном ПК будут работать с программой, и какие у них будут имена.
К сожалению, в большинстве учебной литературы информация о программном создании таблиц или вовсе отсутствует, или очень скудна - описывается только какой то один способ создания только одного типа таблиц. А ведь программисту, в зависимости от обстоятельств, может понадобиться создание разного типа таблиц, разными способами. В данной лекции мы попробуем восполнить этот пробел и разберем три способа программного создания как простых, так и индексированных таблиц.
Наиболее простой способ создания таблицы без индексов предлагает механизм доступа к данным BDE. Плюсы данного способа в простоте выполнения, в возможности создания таблиц как текстового типа, так и dBase, Paradox или FoxPro.
Суть данного способа заключается в предварительном создании объектов-полей в редакторе полей компонента TTable. Это также означает, что еще на этапе проектирования можно настроить формат объектов-полей по своему усмотрению. Рассмотрим этот способ на примере. Создайте новое приложение. Форму как всегда назовите fMain, сохраните модуль под именем Main, а проект в целом назовите как угодно. На форму установите простую панель, очистите ее свойство Caption, а свойству Align присвойте значение alTop. В левой части панели установите рядом две простые кнопки, а в правой - компонент TDBNavigator с вкладки Data Controls Палитры компонентов. Ниже панели установите сетку TDBGrid, в ее свойстве Align выберите значение alClient. У кнопок измените свойство Caption: на первой кнопке напишите "Создать таблицу", на второй - "Открыть таблицу".
Также нам потребуется еще четыре не визуальных компонента. Прямо на сетку, или в любое другое место установите компонент TTable с вкладки BDE, компонент TDataSource с вкладки Data Access, и компоненты TSaveDialog и TOpenDialog с вкладки Dialogs.
Подготовим диалоговые компоненты. Выделите их и присвойте свойству Filter обоих компонентов строку
Таблицы dBase|*.dbf
Таким образом, мы указали, что диалоги будут работать только с таблицами типа dBase. Кроме того, у обоих диалогов измените свойство DefaultExt, указав там:
dbf
Это свойство указывает расширение файла по умолчанию, если пользователь не назначит расширения сам.
В свойстве DataSet компонента DataSource1 выберите таблицу Table1. В свойстве DataSource сетки DBGrid1 и навигатора DBNavigator1 выберите имеющийся DataSource1. Теперь при открытии таблицы она будет отображаться в сетке, а навигатор позволит управлять ей.
Теперь сложнее - настраиваем компонент Table1. Табличный компонент TTable имеет одно важное свойство TableType, с которым раньше нам не приходилось сталкиваться; компонент TADOTable такого свойства не имеет. Это свойство указывает на тип используемой или создаваемой таблицы. Свойство может иметь следующие значения:
Значение | Описание |
---|---|
ttASCI | Таблица содержится в формате обычного текстового файла. Строки и поля разделяются специальными символами - разделителями. Имя файла таблицы имеет расширение *.TXT |
ttDBase | Таблица содержится в формате dBase, файл по умолчанию имеет расширение *.DBF |
ttDefault | Компонент определяет тип таблицы по расширению имени файла таблицы. При создании таблицы, если не указано расширение имени файла, принимается тип Paradox. |
ttFoxPro | Таблица содержится в формате FoxPro, файл по умолчанию также имеет расширение *.DBF |
ttParadox | Таблица содержится в формате Paradox, файл по умолчанию имеет расширение *.DB |
Если выбран тип таблицы (не ttDefault ), то будет использован этот тип вне зависимости от расширения указанного имени файла таблицы.
В свойстве TableType компонента Table1 выберите значение ttDBase, то есть, таблица будет работать только с типом dBase. Далее дважды щелкните по компоненту, открыв редактор полей. Нам нужно будет добавить запланированные ранее поля. Щелкните по редактору правой кнопкой, выберите команду New Field (Новое поле). В поле Name впишите имя поля, например, FCeloe. В поле Type выберите тип поля Integer. В поле Size нужно указывать размер поля, но это справедливо только для текстовых полей и полей типов Memo или BLOB. Убедитесь, что переключатель Field Type установлен на Data, это создаст пустое поле указанного типа. Нажав кнопку "ОК" добавьте объект-поле в редактор полей.
Таким же образом создайте еще несколько разнотипных полей. Каждому полю присвойте уникальное имя (ведь в таблице не может быть двух полей с одинаковым именем!). Важно, чтобы вы добавляли только те типы полей, которые поддерживаются выбранным типом таблиц, в нашем случае это dBase. При добавлении типа Memo укажите размер от 1 до 255, например, 50. В этом случае в файле таблицы *.dbf будет сохранен текст поля в 50 символов. Текст, который не уместится в этот размер, будет сохранен в файле Memo с таким же именем, но с расширением *.dbt.
Делать табличный компонент активным на этапе проектирования не нужно. Итак, не имея базы данных, не имея физической таблицы, мы заранее установили тип таблицы и нужные нам поля. Как вы, наверное, догадываетесь, мы также имеем возможность сразу настроить нужные нам форматы для каждого поля, изменяя такие его свойства, как DisplayFormat, EditMask, DisplayLabel и др.
Далее нам осталось непосредственно создать и открыть таблицу. Дважды щелкните по кнопке "Создать таблицу", сгенерировав для нее событие. В процедуру этого события впишите код:
//если пользователь не выбрал таблицу, выходим: if not SaveDialog1.Execute then Exit; //закроем таблицу, если вдруг уже есть открытая: Table1.Close; //вначале устанавливаем адрес базы данных: Table1.DatabaseName := ExtractFilePath(SaveDialog1.FileName); //теперь устанавливаем имя таблицы: Table1.TableName := SaveDialog1.FileName; //физически создаем таблицу: Table1.CreateTable; //и открываем ее: Table1.Open; //запишем имя открытой таблицы: fMain.Caption := 'Таблица - '+ Table1.TableName;
Комментарии к каждой строке достаточно подробны, чтобы вы самостоятельно разобрались с кодом. Метод CreateTable() компонента-таблицы создает файл таблицы, и дополнительные файлы ( Memo, индексные), если они нужны. В свойстве DatabaseName табличного компонента вы можете установить любой необходимый вам адрес, мы использовали папку, выбранную диалогом SaveDialog.
Для кнопки "Открыть таблицу" код будет почти таким же:
//если пользователь не выбрал таблицу, выходим: if not OpenDialog1.Execute then Exit; //закроем таблицу, если вдруг уже есть открытая: Table1.Close; //вначале устанавливаем адрес базы данных: Table1.DatabaseName := ExtractFilePath(OpenDialog1.FileName); //теперь устанавливаем имя таблицы: Table1.TableName := OpenDialog1.FileName; //открываем таблицу: Table1.Open; //запишем имя открытой таблицы: fMain.Caption := 'Таблица - '+ Table1.TableName;
Откомпилировав программу и поработав с ней, вы обнаружите, что можете создавать и открывать сколь угодно много таблиц программно. При этом на каждую таблицу создается по два файла (если вы используете поле Memo ). Попробуйте таким же образом создать таблицу типа Paradox.
В задачу данного раздела входит создание таблицы Paradox с различными типами полей, с первичным ключом и индексами по текстовому полю как в возрастающем, так и в убывающем порядке. Редактор полей компонента TTable при этом вызывать не нужно, добавлять поля мы тоже будем программно. В целях экономии места проектирование формы приложения не описывается - это несложная задача. Вы можете создать главную форму такой же, как в предыдущем примере, только кнопка там будет одна. При нажатии на эту кнопку мы должны открыть таблицу, если она существует, или создать и открыть новую таблицу. Располагаться таблица должна в той же папке, откуда запущено приложение. Файл с таблицей Paradox назовем Proba.db, файлы с Memo и индексные файлы сгенерируются автоматически, также с именем Proba, но с разными расширениями.
На форму добавьте компонент TTable с вкладки BDE, свойству Name которого присвойте значение TMy (вместо Table1 ), а свойству TableType значение ttParadox. Если у вас в приложении есть сетка DBGrid и (или) навигатор DBNavigator, то добавьте также компонент DataSource, который необходимо подключить к таблице TMy, а сетку и навигатор - подключить к DataSource. Здесь следует иметь в виду одну деталь: описание методов создания полей и индексов хранится в модуле DBTables, который подключается к вашей форме сразу, как вы установите компонент TTable. Если же вы используете модуль данных, и устанавливаете табличный компонент там, то и создавать таблицу нужно тоже в этом модуле, а в главной форме лишь вызывать процедуру создания таблицы. Но в нашем простом примере модуля данных нет, модуль DBTables указан в разделе Uses главной формы, и никаких проблем возникнуть не должно.
Код нажатия на кнопку выглядит так:
{Если таблицы нет - создаем и открываем ее, если есть- просто открываем} procedure TfMain.Button1Click(Sender: TObject); begin //если таблица есть - открываем ее и выходим: if FileExists(ExtractFilePath(Application.ExeName) + 'Proba.db') then begin TMy.DatabaseName := ExtractFilePath(Application.ExeName); TMy.TableName := 'Proba.db'; TMy.Open; Exit; end; //if {Если дошли до этого кода, значит таблицы еще нет. Указываем данные таблицы:} TMy.DatabaseName := ExtractFilePath(Application.ExeName); TMy.TableType := ttParadox; TMy.TableName := 'Proba'; {Создаем поля:} with TMy.FieldDefs do begin //вначале очистим: Clear; //добавляем поле-счетчик типа автоинкремент: with AddFieldDef do begin Name := 'Key'; DataType := ftAutoInc; Required := True; end; //with //добавляем текстовое поле: with AddFieldDef do begin Name := 'Name'; DataType := ftString; Size := 30; end; //with //добавляем поле дата: with AddFieldDef do begin Name := 'Date'; DataType := ftDate; end; //with //добавляем логическое поле: with AddFieldDef do begin Name := 'MyLog'; DataType := ftBoolean; end; //with //добавляем целое поле: with AddFieldDef do begin Name := 'MyInt'; DataType := ftInteger; end; //with //добавляем вещественное поле: with AddFieldDef do begin Name := 'MyReal'; DataType := ftFloat; end; //with //добавляем денежное поле: with AddFieldDef do begin Name := 'MyCurr'; DataType := ftCurrency; end; //with //добавляем поле Memo: with AddFieldDef do begin Name := 'MyMemo'; DataType := ftMemo; Size := 20; end; //with end; //with {Создаем ключ и индексы:} with TMy.IndexDefs do begin Clear; //делаем первичный ключ: with AddIndexDef do begin Name := ''; Fields := 'Key'; Options := [ixPrimary]; end; //делаем индекс в возрастающем порядке: with AddIndexDef do begin Name := 'NameIndxASC'; Fields := 'Name'; Options := [ixCaseInsensitive]; end; //делаем индекс в убывающем порядке: with AddIndexDef do begin Name := 'NameIndxDESC'; Fields := 'Name'; Options := [ixCaseInsensitive, ixDescending]; end; end; //with //создаем таблицу: TMy.CreateTable; //и открываем ее: TMy.Open; end;
Разберем приведенный код. Первый блок выполняет проверку на наличие таблицы. Таблица ищется в папке, откуда была запущена программа. Если таблица найдена, то компоненту TMy присваиваются свойства DatabaseName (папка, где располагается таблица) и TableName (имя таблицы). В нашем случае таблица называется Proba.db, но вы можете усложнить программу, используя диалог OpenDialog, как в прошлом примере. В этом случае пользователь сможет выбрать не только имя таблицы, но и ее расположение. Далее таблица открывается, а оператор Exit досрочно завершает выполнение процедуры.
Если выполнение процедуры продолжается, значит, таблица не была найдена. В этом случае мы заполняем свойства компонента-таблицы DatabaseName, TableType и TableName необходимыми значениями.
Далее начинаем добавлять поля. Чтобы уменьшить код, мы используем оператор with. Напомню, что этот оператор создает блок кода, который относится к указанному в with объекту. Так, вместо
with TMy.FieldDefs do begin Clear;
можно было бы написать
TMy.FieldDefs.Clear;
В случае одиночного оператора это допустимо, но в случае множественных команд ссылаться в каждой строчке на объект будет утомительно. Свойство FieldDefs таблицы содержит описание полей этого набора данных. Таким образом, мы начинаем с того, что очищаем это описание.
Далее у нас идет метод AddFieldDef, предназначенный для добавления поля в описание. Опять же, чтобы не ссылаться каждый раз на этот метод, мы используем вложенный оператор with для каждого добавляемого поля.
В простейшем случае в блоке добавления нового поля требуется указать только два свойства объекта-поля: Name (имя поля) и DataType (тип поля). С именем все понятно, а что касается типа поля, то он определяется свойством DataType класса TField. Чтобы получить подробную справку по возможным типам полей, установите курсор в редакторе кода на слове DataType и нажмите <Ctrl+F1>, чтобы вызвать контекстную справку. В списке тем выберите ту тему, которая относится к классу TField, а в открывшейся справке щелкните по ссылке TFieldType (относится к Delphi 7, хотя возможно, имеется и в предыдущих версиях). Откроется страница с подробным описанием типов полей. При использовании этого метода следует сверяться, имеется ли выбранный тип поля в таблицах используемого формата.
Помимо этих двух свойств, при необходимости могут использоваться и другие:
Required - Логическое свойство. Если равно True, то значения поля должны быть уникальными (не могут повторяться). В нашем примере такое свойство имеется у поля, которое мы будем использовать как первичный ключ.
Size - Указывает размер поля. Используется в основном, со строковыми и Memo - полями.
После того, как в список полей были добавлены все необходимые поля, начинаем создание первичного ключа и индексов. Если за список полей отвечает свойство FieldDefs таблицы, то за список индексов отвечает свойство IndexDefs, а за добавление нового индекса - метод AddIndexDef. По аналогии с полями, используем оператор with для уменьшения кода. Для каждого индекса требуется указать по три свойства: Name (имя индекса), Fields (имя поля, по которому строится индекс) и Options (параметры индекса). Параметры индекса указаны в таблице 9.2:
Как видно из примера, свойству Options можно присвоить не один параметр, а список параметров:
Options := [ixCaseInsensitive, ixDescending];
Далее все просто: указав необходимые поля и индексы, методом CreateTable формируются физические файлы таблицы. Сама таблица имеет расширение *.db, файл с полем Memo - *.mb, остальные файлы содержат созданные индексы.
Для сортировки данных используем индексы. У нас их два -' NameIndxASC ' (в возрастающем порядке) и ' NameIndxDESC ' (в убывающем порядке). Чтобы сортировать данные, например, в убывающем порядке, нужно указать имя соответствующего индекса в свойстве IndexName компонента-таблицы:
TMy.IndexName := 'NameIndxDESC';
Если же мы хотим снять сортировку, то достаточно просто присвоить этому свойству пустую строку:
TMy.IndexName := '';
Описываемый выше пример взят из справочника Delphi и приведен с небольшими доработками. Пример описывает практически все аспекты создания таблицы; по аналогии вы сможете создавать таблицы любой сложности.
Создание таблицы выполняется SQL -запросом CREATE TABLE. Тут есть одно но: дело в том, что существует два типа SQL -запросов. Запрос, который возвращает набор данных и начинается оператором SELECT, выполняется простым открытием компонента-запроса. При этом выполняется запрос, который содержится в свойстве SQL компонента.
С модифицирующими командами дело обстоит иначе. Команда CREATE TABLE принадлежит к той части SQL, которая называется DDL (Data Definition Language) - Язык Определения Данных. Этот язык предназначен для изменения структуры базы данных. Команды INSERT, DELETE, UPDATE относятся к DML (Data Manipulation Language) - Язык Обработки Данных, предназначенный для модификации данных. Эти команды объединяет то, что они не возвращают результирующий набор данных. Чтобы выполнить эти команды, нужно присвоить соответствующий SQL -запрос свойству SQL, а затем вызвать метод ExecSQL.
Синтаксис создания таблицы несложный:
CREATE TABLE <TableName> (<ColumnName> <DataType> [<Size>], …)
Здесь, TableName - имя таблицы; ColumnName - имя столбца; DataType - тип данных и Size - размер, который указывается для некоторых типов данных, например, строки. Описания столбцов таблицы разделяются запятыми. В различных СУБД синтаксис и типы данных SQL могут отличаться. Поэтому запрос, прекрасно работающий в одной СУБД, может вызвать ошибку в другой. Чтобы избежать ошибок, рекомендуется везде использовать типы ANSI, являющиеся стандартом SQL. Увы, но этих типов очень немного. Рассмотрим их:
Тип | Описание |
---|---|
CHAR (CHARACTER) TEXT | Строковые типы данных. Обычно имеют размер до 255 символов. Требуют указания размера. |
INT (INTEGER) | Целое число. Размер не указывается. |
SMALLINT | Короткое целое. Размер не указывается. |
FLOAT REAL | Вещественные числа. Размер не указывается. |
Как видите, многих типов просто нет. Вместо логического типа, вероятно, придется использовать строковый тип с размером в один символ; при этом 'Y' или '1' будут означать истину, а 'N' или '0' - ложь. Программисту придется самостоятельно делать проверку на это значение. Нет типа Memo. Нет автоинкрементного типа. Однако стандартные типы непременно будут корректно работать в любой СУБД.
Ниже приведен пример создания и открытия простой таблицы. В приложении должен иметься компонент ADOQuery, а если есть сетка и навигатор, то и DBSource. Для подключения к нужному провайдеру данных желательно использовать компонент TADOConnection. В его свойство ConnectionString нужно прописать строку подключения, например:
Provider=MSDASQL.1;Persist Security Info=False;Data Source=Файлы dBASE
Эту строку можно ввести программно, или создать подключение при проектировании (я так и сделал). Поставщик данных в примере оставлен по умолчанию - Microsoft OLE DB Provider for ODBC Drivers, а в качестве источника данных (вкладка "Подключение" редактора связей TADOConnection ) используются файлы dBase. Не следует забывать и про свойство LoginPrompt, которое следует переводить в False, чтобы программа не запрашивала имя пользователя и пароль при каждом подключении. А также нужно сразу открыть TADOConnection, установив его свойство Connected в True. В свойстве Connection компонента TADOQuery следует выбрать ADOConnection1.
Пример реализован, как событие нажатия на кнопку:
procedure TfMain.Button1Click(Sender: TObject); var s: String; begin {Создаем текст запроса} s := 'CREATE TABLE MyTab(Key1 INT, Name CHAR(20), '+ ' MyFloat FLOAT, MyDate DATE)'; {Создаем таблицу} ADOQuery1.SQL.Clear; ADOQuery1.SQL.Add(s); ADOQuery1.ExecSQL; {Открываем таблицу} ADOQuery1.SQL.Clear; ADOQuery1.SQL.Add('SELECT * FROM MyTab'); ADOQuery1.Open;
Как видите, создается четыре поля - целый тип, строковый размером 20 символов, вещественный и тип Дата. Последний тип не входит в стандартное описание ANSI -типов, тем не менее, работает в большинстве СУБД. Можете также поэкспериментировать и с типом BOOLEAN (Логический).
Итак, в переменную s мы вносим строку записи SQL -запроса. Затем очищаем свойство SQL, на случай, если там уже имелся запрос. Далее этот запрос мы заносим в свойство SQL, и методом ExecSQL выполняем его. С открытием таблицы мы уже неоднократно сталкивались. В результате выполнения кода создается и открывается файл MyTab.dbf, который находится в той же папке, что и приложение.