Опубликован: 07.05.2010 | Уровень: специалист | Доступ: платный
Лекция 9:

Приемы создания и модификации таблиц программно

Аннотация: На этой лекции вы познакомитесь с тремя способами создания таблиц программно. Вы создадите как простую, так и сложную индексированную таблицу, научитесь создавать таблицы через SQL-запрос.

На прошлых лекциях мы изучили немало способов создания и обработки таблиц. В большинстве случаев, база данных и таблицы в ней проектируются и создаются заранее, например, такими утилитами, как Database Desktop, или СУБД MS Access. Однако иногда приходится создавать таблицы программно, то есть, не во время проектирования приложения, а во время его работы. Предположим, каждый пользователь программы должен иметь свою собственную таблицу в базе данных, или даже свою собственную БД. Сделать это заранее программист не может, ведь неизвестно, сколько пользователей на одном ПК будут работать с программой, и какие у них будут имена.

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

BDE. Простая таблица.

Наиболее простой способ создания таблицы без индексов предлагает механизм доступа к данным 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 такого свойства не имеет. Это свойство указывает на тип используемой или создаваемой таблицы. Свойство может иметь следующие значения:

Таблица 9.1 . Значения свойства TableType компонента TTable
Значение Описание
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.

BDE. Таблица с ключом и индексами.

В задачу данного раздела входит создание таблицы 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:

Таблица 9.2. Параметры типов индекса
Тип Описание
ixPrimary Первичный индекс (ключ). Не применяется с таблицами типа dBase.
ixUnique Уникальный индекс. Значения этого поля не могут повторяться.
ixDescending Индекс в убывающем (обратном) порядке.
ixExpression Ключевой индекс для таблиц dBase.
ixCaseInsensitive Индекс, нечувствительный к регистру букв.
ixNonMaintained Этот тип используется редко. Он подразумевает, что при редактировании пользователем значения индексируемого поля, индексный файл автоматически не обновляется.

Как видно из примера, свойству Options можно присвоить не один параметр, а список параметров:

Options := [ixCaseInsensitive, ixDescending];

Далее все просто: указав необходимые поля и индексы, методом CreateTable формируются физические файлы таблицы. Сама таблица имеет расширение *.db, файл с полем Memo - *.mb, остальные файлы содержат созданные индексы.

Для сортировки данных используем индексы. У нас их два -' NameIndxASC ' (в возрастающем порядке) и ' NameIndxDESC ' (в убывающем порядке). Чтобы сортировать данные, например, в убывающем порядке, нужно указать имя соответствующего индекса в свойстве IndexName компонента-таблицы:

TMy.IndexName := 'NameIndxDESC';

Если же мы хотим снять сортировку, то достаточно просто присвоить этому свойству пустую строку:

TMy.IndexName := '';

Описываемый выше пример взят из справочника Delphi и приведен с небольшими доработками. Пример описывает практически все аспекты создания таблицы; по аналогии вы сможете создавать таблицы любой сложности.

ADO. Создание простой таблицы посредством запроса SQL

Создание таблицы выполняется 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. Увы, но этих типов очень немного. Рассмотрим их:

Таблица 9.3 . Типы ANSI
Тип Описание
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, который находится в той же папке, что и приложение.

Евгений Медведев
Евгений Медведев

В лекции №2 вставляю модуль данных. При попытке заменить name на  fDM выдает ошибку: "The project already contains a form or module named fDM!". Что делать? 

Анна Зеленина
Анна Зеленина

При вводе типов успешно сохраняется только 1я строчка. При попытке ввести второй тип вылезает сообщение об ошибке "project mymenu.exe raised exception class EOleException with message 'Microsoft Драйвер ODBC Paradox В операции должен использоваться обновляемый запрос'. 

Денис Попов
Денис Попов
Россия, Оренбург, Оренбургский государственный университет, 2015
Рустам Кадыров
Рустам Кадыров
Россия, Тирлян, Тирлянская школа №5, 2003