Базы данных с нуля (статья)

Материал из DRKB

Базы данных с нуля (статья)[править | править код]

Итак, решил я открыть серию статей для помощи в овладении основными навыками программирования баз данных (БД, или DB - database). Я буду рассчитывать, что читатель имеет понятие о программировании в Дельфи, но абсолютно не знаком с базами данных. Первые несколько постингов будут о базах данных вообще, поэтому прошу не ругать меня - это не офтопик, очень быстро мы доберёмся до Дельфи.


Что такое базы данных?[править | править код]

Базы данных существуют следующих видов:

1) Древовидные - простейший пример - Windows Registry, файловая система FAT и XML - здесь информация хранится в древовидной структуре и доступ осуществляется через "путь", т.е. указание всех узлов от корневого до нужного. Например: "c:\Docs\Pictures\Me.jpg". Недостатки этого способа хранения данных является очень медленный поиск, если не известен путь и очень плохая устойчивость к повреждениям структуры. Преимущество - возможность хранить в классифицированном виде очень разнородную информацию и очень быстрый поиск при знании ключа. Эти базы данных мы разбирать здесь не будем.

2) Сетевые базы данных - простейший пример - интернет. Т.е. существуют узлы, обособленные друг от друга, содержащие определённую информацию. Каждый узел представляет какое-то количество ссылок на другие узлы, по которым и ведётся поиск. Недостатки - очень сложный и долгий поиск, возможна неполное предоставление информации или невозможность найти нужную информацию. Преимущества - очень легко добавить любую, разнородную информацию, самая высокая стабильность из всех систем. Эти базы данных мы разбирать здесь не будем.

3) Объектные базы данных - новое веяние. Их мы разбирать здесь не будем, но интересующиеся найдут интересной дискуссию о них в нашем разделе по базам данных.

4) Реляционные базы данных - именно с ними мы и будем работать. В дальнейшем если говорится "база данных", то подразумевается "Реляционная база данных". "Реляционный" - Relation - обозначает взаимосвязанный. С этими связями мы будем разбираться потом, а пока можно для простоты считать, что реляционная база данных - это набор двумерных простых таблиц. Недостатки реляционных баз данных - хранение только однородной информации, сложности в добавлении новых структур и взаимоотношений, информация хранящаяся в такой БД должна быть в нужной степени абстрагирована. Преимущества - прежде всего очень высокая скорость поиска - по этому параметру у реляционных баз данных конкурентов нет, высокая стабильность, обилие софта для их поддержки и разработки, удобность для очень широкого круга задач.


Реляционные базы данных?[править | править код]

Реляционная база данных упрощённо является набором таблиц. Таблица же есть основной строительный кирпичик базы данных. Рассмотрим структуру таблицы. Для начала представьте себе таблицу, например в Word...

Что в ней есть?

Во-первых есть строки и колонки (row and column). В базах данных, в отличие от Word есть строгие ограничения на их содержимое, да и терминология немного другая:

1) Колонка называется тоже column, но чаще употребляется понятие "поле" (field). Колонка всегда имеет имя (по которому ее можно найти) и обязана хранить данные только одного какого-либо типа - например целые числа, строки, дату/время и т.п. Создавая таблицу вы обязаны указать какой тип имеет каждое поле, другими словами, вы заранее должны определится, какого сорта данные будут хранится в колонке. Например, вот примерно так может выглядеть структура таблицы для хранения данных об участнике этого форума:

  • Ник - строка (25 символов)
  • ФИО - строка (250 символов)
  • Дата регистрации - Дата/время
  • Количество постингов - Целое
  • Показывать email - True/False

Обратите внимание что для строк я указал конкретную длину, а для остальных ничего не указывал. Зачем? Ответ прост - каждая строка должна занимать строго одинаковое место (об исключениях потом), и это сделано для быстроты поиска. Действительно, если бы каждая строка имела разную длину, то чтобы найти например 1000 строку, надо было бы перечитать все 999 предыдущих строк, но если известно, что каждая строка занимает например 1Кб, то чтобы прочитать 1000 строку достаточно прочитать 1 Кб с 999Кб... Другая сторона этого - например мне надо сравнить даты в приведенной выше таблице - сделать это просто - я точно знаю что первую дату можно прочитать с 276 байта, и так же точно я знаю точные координаты каждой даты. Именно в этом и лежит одна из сторон высокой скорости работы баз данных (другие способы ускорения работы рассмотрим позже).

2) Строка - в базах данных имеет специфическое название - запись (Record) - к Дельфийскому типу Record этот термин не имеет никакого отношения. Хотя большинство БД дают возможность перейти и прочитать например 10 запись, надо с самого начала попытаться никогда этим не пользоваться. Почему? Да просто потому что БД практически всегда подразумевают совместный доступ нескольких пользователей к одним и тем же данным, и если Вы хотите перейти на 10 запись, а другой пользователь в это время удалит запись номер 5, то вы перейдёте вовсе не на ту запись что ожидалось. А как же быть? У вас есть 2 способа - либо вы находите нужную запись по значению поля - например для нашей таблицы это будет выглядеть примерно так:

"Найти пользователя [Вася] в первой колонке"

В результате вы получите доступ ко всем полям записи для "Васи". Либо вы берёте все записи и перебираете их в цикле пока не найдёте нужный - это гораздо худший способ, так как работает на 2-3 порядка медленнее и имеет другие неприятные последствия (об этом позже), но он возможен и иногда применяется.


Первая программа с базами данных?[править | править код]

После небольшого теоретизирования спустимся с небес на землю, откроем Дельфи и напишем простейшую программу для баз данных. Напишем, это громко сказано, потому что писать ничего не придётся, только компоненты потыкаем.

Открываем новый проект. Открываем форму. Кладём на форму компонент TTable (с закладки "Data Access" или "BDE" - у кого какая версия Дельфей). Оп! Не ожидали - вроде бы и таблица, а компонент не визуальный! Итак компонент TTable - это пока основной компонент для нашей базы - всё обращение к таблице идёт только через него. Теперь давай-те его подсоединим к базе данных.

К Дельфи прилагается учебная база данных, её мы и будем пользовать. Найдите свойство DatabaseName и из выпадающего списка выберите "DBDEMOS" - это и есть учебная база данных. Теперь берём свойство TableName и в выпадающем списке обнаруживаем список имён всех таблиц в базе данных "DBDEMOS", выбираем например "biolife.db" - это таблица так называется (а в данном случае и название файла)

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

Но все визуальные компоненты могут подсоединится к TTable только через вспомогательный компонент TDataSource - находящийся на той же закладке. Ставим и его на форму. Находим свойство DataSet у этого компонента и в выпадающем списке указываем на Table1. Теперь визуальные компоненты будут "видеть" инфу в таблице через TDataSource.

Переходим на другую закладку компонентов - "Data Controls" и ставим компонент TDBGrid. В его свойстве DataSource указываем на DataSource1. Что видим? Пока ничего! Таблица то не открыта - кликаем на Table1 и устанавливаем свойство Active в True. Работает!

Можно программу откомпилировать и поиграться со своим первым приложением для баз данных. Неправда ли очень просто!


Визуальные компоненты для DB[править | править код]

Итак, мы не написав ни строчки кода получили простейшее приложение, работающее с базой данных. С помощью него мы можем просматривать и редактировать содержимое таблицы. Давайте теперь сделаем эту процедуру немного удобнее, на закладке "Data Control" есть компонент TDBNavigator. Положим его на форму и в инспекторе объектов поставим его свойство DataSource указывающим на тот же DataSource1, что и для DBGrid - собственно, теперь оба визуальных контрола (DBGrid и DBNavigator) привязаны к одному и тому же DataSource и через него к одной и той же таблице. DBNavigator имеет несколько кнопок (вы можете настроить какие именно вы хотите видеть) дающие лёгкий контроль над следующими операциями:

(перечисление в порядке расположения кнопок)

  1. Переход на первую запись
  2. Переход на предыдущую запись
  3. Переход на следующую запись
  4. Переход на последнюю запись
  5. Добавить запись
  6. Удалить запись
  7. Редактировать запись
  8. Сохранить изменения
  9. Отменить изменения
  10. Перечитать таблицу

Обратите внимание, что запись (строка) таблицы есть как бы неделимый квант информации - т.е. отменяются действия произведенные для всей записи целиком, добавляется или удаляется тоже строка целиком.

Разберём ещё несколько визуальных компонентов, чтобы покончить с визуализацией данных и перейти собственно к программированию.

На закладке Data Controls есть ещё несколько важных компонентов, давайте поставим их на нашу форму: DBLabel, DBEdit, DBMemo и DBImage. Все их так же как и DBGrid соединим с DataSource1. Однако здесь мы обнаружим что этого недостаточно, эти компоненты работают с отдельной ячейкой в таблице, поэтому мы должны указать ещё поле (столбец) который они будут показывать.

Давайте сделаем следующее - для каждого из этих компонентов укажем свойство DataField, например следующим образом:

  • DBLabel - ассоциируем с полем Category
  • DBEdit - ассоциируем с полем Common_name
  • DBMemo - ассоциируем с полем Notes
  • DBImage - ассоциируем с полем Graphic

Можно откомпилировать программу и немного поиграться с ней. Итак что мы можем здесь увидеть? Что в каждый данный момент времени из всей таблицы у нас есть запись которая активная (текущая) - в DBGrid она показывается треугольничком слева. Именно с этой единственной записью мы и можем оперировать - удалять, добавлять, редактировать, именно её содержимое отображается в DBLabel, DBEdit, DBMemo, DBImage и именно она может быть изменена при помощи этих компонентов. Описанная только что структура позволяет работать только с одной записью в определённый момент времени, если вы переходите на другую запись то все изменения должны быть либо запомнены либо отменены! По умолчанию они запоминаются без всяких запросов, в чём вы можете убедиться меняя значения и переходя на другую запись.


Работа с таблицами в коде[править | править код]

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

Итак, начинаем разбирать способы работы с базами данных в коде. Прежде всего заметим, что работать мы будем только с компонентом Table1. Сразу предупреждаю

КАТЕГОРИЧЕСКИ НЕ СЛЕДУЕТ ПЫТАТЬСЯ ИЗ КОДА МЕНЯТЬ ЗНАЧЕНИЯ В ВИЗУАЛЬНЫХ КОМПОНЕНТАХ

Не следует пытаться менять или читать значения из DBGrid, DBEdit и т.д. Эти компоненты существуют только для работы оператора "вручную". Для доступа к данным из кода надо использовать только невизуальные компоненты типа TTable (в дальнейшем мы разберём и другие компоненты для работы с данными - но в любом случае это будут не визуальные компоненты).

Представив себе обычную таблицу, понятно что для доступа к данным надо определить столбец(поле) и строку(запись) в которой эти данные находятся. Давайте разбирать по очереди.

1) Определить поле задача очень простая. Способов здесь 2:

по имени:

Table1.FieldByName('Category')

или по номеру столбца

Table1.Fields[1]

Оба выражения являются объектом наследованным от типа TField

Я не буду разбирать этот объект подробно, только приведу пример как можно пользоваться этим объектом для доступа к содержимому ячейки таблицы. Содержимое может быть разных типов, поэтому можно использовать следующие методы в зависимости от типа данных:

Table1.FieldByName('Category').AsString
Table1.FieldByName('Category').AsInteger
Table1.FieldByName('Category').AsBoolean
Table1.FieldByName('Category').AsDateTime
Table1.FieldByName('Category').AsFloat


Например, поставьте на форму кнопку, и на onClick напишите следующий код:

ShowMessage(Table1.FieldByName('Category').AsString);


При нажатии на кнопку вы увидите содержимое столбца 'Category' для текущей записи. Аналогично для обращения по номеру:

ShowMessage(Table1.Fields[1].AsString);

Обратите внимание, что на этапе компиляции компилятор абсолютно не знает о реальном типе данных в поле таблицы. Это выяснится только при попытке выполнить строку. Что будет если типы не совпадают? Если тип можно конвертировать - то ничего страшного, например если у вас поле с целым числом 123, то обращение к полю через AsString выдаст результат - строку '123'. Но если типы не совместимы, то будет сгенерирована ошибка, например такая строка почти наверняка в нашем приложении приведёт к ошибке:

var i: Integer;
begin
  i := Table1.FieldByName('Category').AsInteger;
  ShowMessage(IntToStr(i));
end;


Потому что реальные данные не могут быть приведены к целому типу.

Теперь давайте разбираться как нам добраться до нужной строки, другими словами, до нужной записи. Как я уже говорил мы можем работать только с одной "активной" записью, поэтому задача сводится к установке нужной записи "активной" (К знатокам баз данных - я упорно и намеренно обхожу стороной понятие "курсор" и попытаюсь провести повествование без его упоминания, с целью упрощения понимания материала и не хочу углубляться в материал, без которого можно на первых порах обойтись). Итак, прежде всего Table компонент имеет 4 метода которые помогут нам пройти через все строки таблицы:

  • Table1.First - переход на первую запись
  • Table1.Last - переход на последнюю запись
  • Table1.Prior - переход на предыдущую запись
  • Table1.Next - переход на следующую запись


А так же 2 полезных свойства:

  • Table1.RecordCount - возвращает количество записей (строк) в таблице
  • Table1.Eof - возвращает TRUE если достигнута последняя запись, в остальных случаях FALSE


Давайте на нашу форму положим компонент Memo (на сей раз обычное, а не dbMemo).

Вот этот простейший код позволит пройти таблицу от начала до конца и считать значения одного поля для всех записей в Memo:

Table1.First; // переход на первую запись
while not Table1.Eof do // делать цикл пока таблица не закончится
begin
  Memo1.Lines.Add(Table1.FieldByName('Category').AsString); // заносим в Мемо значение поля для текущей записи
  Table1.Next; // переходим на следующую запись
end;


Или это же самое можно сделать например так:

Table1.First;
for i := 0 to Table1.RecordCount-1 do
begin
  Memo1.Lines.Add(Table1.FieldByName('Category').AsString); // заносим в Мемо значение поля для текущей записи
  Table1.Next; // переходим на следующую запись
end;


Второй способ гораздо хуже. Он имеет следующие недостатки:

1) Не для всех баз данных метод Table1.RecordCount возвращает правильное значение. Иногда Table1.RecordCount устанавливается только после перехода на последнюю запись (это не глюк, это вполне объяснимое поведение, например если у вас есть хорошая табличка размером в несколько гигабайт на другом компьютере, то Table вовсе не скачивает всю эту таблицу локально, а достаёт данные только по мере необходимости). К счастью этот недостаток легко устраним, надо код преобразовать до следующего вида:

Table1.Last;
Table1.First;
for i := 0 to Table1.RecordCount-1 do
begin
  Memo1.Lines.Add(Table1.FieldByName('Category').AsString); // заносим в Мемо значение поля для текущей записи
  Table1.Next; // переходим на следующую запись
end;


Несмотря на кажущуюся бессмысленность это работает.

2) А вот другой аспект - всегда надо помнить о многопользовательском доступе к таблицам - что случится, если во время прохода по записям другой пользователь добавит или удалит запись?


Поиск нужных данных[править | править код]

Теперь разберём более эффективные способы нахождения нужной записи в таблице.

А если нам надо перейти к вполне конкретной строке (записи)? Можно конечно организовать такой цикл и найти нужную запись, но это громоздко, неудобно, и главное очень медленно! Для этого можно использовать метод таблицы Locate.

Например в нашей задаче нам надо найти запись где в поле Category значение "Cod". Этого можно добиться примерно следующим кодом:

Table1.Locate('Category', 'Cod', [loCaseInsensitive]);

Можно "повесить" этот код на кнопку и убедится, что после выполнения этого кода активная запись стала именно та которая нам и нужна. Итак что же за параметры мы передаём этому методу? Первый параметр - это имя поля, второй параметр - это значение поля, третий опции поиска (см. справку Дельфи). А что будет если такого значения нет? Например:

Table1.Locate('Category', 'Cod123', [loCaseInsensitive]);

Ничего не будет, правда метод Locate - это функция и она возвращает значение Boolean в зависимости от того, найдена запись или нет. Преобразовав код как

if Table1.Locate('Category', 'Cod123', [loCaseInsensitive]) then
  ShowMessage('Record is located seccessful!')
else
  ShowMessage('Record is not found!');


можно убедится, что теперь мы знаем найдена запись или нет. Можно искать и по части значения, например

Table1.Locate('Category', 'Co', [])

не сможет найти запись, а

Table1.Locate('Category', 'Co', [loPartialKey])

вполне правильно найдёт запись с значением 'Cod'.

А если нам надо найти значение по двум полям? В этой таблице искать так бесполезно, так как все поля разные. Переключим таблицу на другую. Для этого удалим с формы все визуальные компоненты кроме DBGrid и DBNavigator (так как у новой таблицы будет совсем другой список полей). В коде напишем что-то типа:

Table1.Active := False; // закрыли таблицу
Table1.TableName := 'items.db'; // ассоциируем с новой таблицей на диске
Table1.Active := True; // открыли таблицу


Откомпилируем код, убедимся, что теперь мы видим совсем другую таблицу.

Теперь давайте найдём такую запись, где ItemNo = 1 и Discount = 50, для этого нам надо применить Locate следующим образом:

Table1.Locate('ItemNo;Discount', VarArrayOf([1, 50]), []);

Теперь несколько примечаний:

  1. Для Дельфи 6/7 - добавьте "Uses Variants;"
  2. Первый параметр - это список имён полей через ";" (точка с запятой) без пробелов
  3. Второй параметр - это массив вариант - значений полей. Почему вариант? Потому что поля могут быть разных типов и в этом массиве вполне можно задать значения разных типов: VarArrayOf([1, 'Вася', True, 3.14])


Доступ к базам данных[править | править код]

Теперь, после работы руками, попробуем разобрать несколько менее призёмлённых вещей. Для начала следует упомянуть что Вы наверное уже прочувствовали что такое таблица. Теперь немного остановимся на базе данных. Базу данных можно очень упрощённо представить как несколько разных таблиц. Они могут быть связаны между собой, а могут и нет. Как физически хранятся таблицы? В трёх видах:

  1. Каждая таблица это отдельный файл. Так работают наиболее древние базы данных, например Парадокс (который мы пока используем в примерах), Dbase, FoxPro. Все файлы таблиц лежат в отдельном каталоге на диске. Этот каталог и называется базой данных.
  2. Все таблицы хранятся в одном файле - например MS Access - именно этот файл и называется базой данных
  3. Таблицы хранятся на специальном сервере - например MS SQL Server, Oracle. В данном случае нас вообще не интересует как сервер хранит эти таблицы - для нас прямой доступ к ним закрыт, мы можем лишь послать запрос на сервер и получить ответ.

Несмотря на значительную разницу в организации, работа с разными базами данных очень сходная (во всяком случае до углубления в дебри). В целом, Вам нет смысла копаться в реальных форматах файлов, нет смысла искать что в файле biolife.db означает 10-й байт. Может показаться что всю работу над этим файлом делает компонент TTable в нашем примере. Но это не так! Я наверное удивлю многих если скажу, что компонент TTable реально является только интерфейсом, для лёгкого доступа к данным из Дельфи. Оказывается, что всю работу над таблицей делает специальный драйвер базы данных (или его ещё называют провайдер). Итак упрощённая схема общения с таблицей из программы выглядит примерно следующим образом(для нашего примера):

База Данных <-> Драйвер Базы Данных <-> TTable <-> наш код или др. компоненты

Итак драйвер БД «знает» тонкости и детали строения файла таблицы, или знает конкретные форматы запроса к серверу на «входе», а на выходе имеет некий универсальный «интерфейс» (Я имею ввиду широкое понятие слова «интерфейс», вне контекста с COM) к которому и подключается TTable. Естественно что каждая база данных, и даже каждая версия базы данных имеет свой уникальный формат, свои уникальные особенности, поэтому драйвер для каждой разновидности баз данных тоже уникальный и обычно создаётся производителем баз данных. Интерфейс на «выходе» тоже должен быть стандартизованным - тогда работа с разными базами данных будет значительно облегчена, конечно до истиной переносимости кода далеко (хотя для простейших программ можно легко перенести код для работы с другой базой данных) - сказываются очень большие различия в архитектуре баз данных, которые просто невозможно свести 100% к одинаковому интерфейсу, но в любом случае знакомство с одной базой данных позволяет с лёгкостью разобраться с другой... Как всегда существует несколько стандартов таких «выходных интерфейсов». Наиболее широко распространены следующие «стандарты» или системы доступа к базам данных:

  • BDE - Borland Database Engine (или по-старому IDAPI). Мы как раз работали в наших примерах именно через эту систему. Эта система является «родной» для Дельфи и отличается весьма высокой производительностью при работе с локальными базами данных. С серверными базами данных её производительность гораздо скромнее. Она же является «родной» для Парадокса, что обусловливает очень высокую производительность и удобство работы связки Delphi-BDE-Paradox (конечно для небольших систем с малым количеством пользователей). BDE имеет в своём составе драйвера практически ко всем более или менее известным базам данных в среде Windows. Позже мы подробнее остановимся на настройке BDE.
  • ODBC - продукт был создан Microsoft как конкурент BDE. На большинстве баз данных он показывает меньшую производительность чем BDE, из Дельфи с ним работать не так удобно, но он так же имеет в своём составе драйвера практически ко всем более или менее известным базам данных в среде Windows. Его настройки можно найти в «Панели Управления» Windows. Есть бесплатная библиотека компонентов для работы с ODBC с исходными кодами, её можно взять с моего сайта: http://www.delphist.com. Для программиста на Дельфи представляет очень ограниченный интерес - большинство возможностей реализовано в BDE, причём BDE со многими базами работает быстрее и Дельфи имеет собственные компоненты для BDE.
  • DAO - это очень старая система для доступа к MS Access и MS Excel (она так же поддерживает ещё несколько баз данных), отличается высокой производительностью и богатым набором функций для работы именно с MS Access и MS Excel. Вообще не поддерживает работу с серверными базами данных. DAO можно использовать для работы с MS Access и MS Excel когда критична производительность приложений и/или требуется всё богатство возможностей доступа к MS Access и MS Excel. Есть бесплатная библиотека компонентов для работы с DAO с исходными кодами, её можно взять с моего сайта: http://www.delphist.com.
  • ADO (ActiveX Data Object) - новая система от MS ориентированная прежде всего на работу с серверными базами данных. Довольно удобна в использовании, Дельфи начиная с 4й версии в модификации Enterprise/Professional имеет линейку собственных компонентов для работы через ADO. Позднее мы рассмотрим работу с ADO компонентами.

Кроме перечисленных есть ещё по крайней мере десяток других широко известных систем доступа к базам данных, и огромное количество "отдельно стоящих" драйверов для конкретной базы данных.


Приёмы работы с BDE[править | править код]

Те примеры с которыми мы работали использовали именно BDE. Давайте рассмотрим вопросы, напрямую связанные с BDE:

  1. Где физически хранится моя база данных
  2. Как создать базу данных
  3. Как создать таблицу

Итак, где физически хранится моя база данных и собственно куда мы обращались в наших примерах? Если вы помните, в наших примерах мы свойство DatabaseName для Table установили в "DBDemos". Что же это такое "DBDemos"? - это название базы данных, или в терминологии BDE - Alias (перевод на русский язык "Псевдоним"). Alias - это некая "структура" BDE, которая указывает на физическое расположение файлов базы данных, а так же хранит некоторые свойства (параметры) доступа к базе данных. Эти параметры можно посмотреть, настроить, а также добавить или удалить Alias используя программу "BDE Administrator" которую можно найти в Control Panel (панель управления Windows). Запустите BDE Administrator и найдите в левом дереве DBDemos. Теперь на правой части можно увидеть его свойства, например там вы найдёте путь к базе данных. С помощью BDE можно удалить Alias или добавить новый.


Создание базы данных[править | править код]

Итак, Как создать базу данных?

Пока мы рассматриваем Paradox осмелюсь заметить, что база данных ничто иное как папка (каталог) на диске. Надеюсь что читателю не составит труда создать пустой каталог, например "c:\MyDBExample". Теперь создадим Alias на этот каталог:

  1. Открываем BDE Administrator, нажимаем menu -> Object -> New (или Ctrl-N)
  2. В выскочившем окне выбираем Standard, жмём Ok
  3. На левой панели переименовываем название Alias во что-нибудь, например "MyDB"
  4. На правой панели, в разделе PATH указываем c:\MyDBExample
  5. Сохраняем изменения


Закрываем BDE Administrator. Загружаем Дельфи с нашим примером, убеждаемся, что в свойстве DatabaseName у таблицы появился среди вариантов выбора и наш Alias - "MyDB".

А как создать базу данных программно? Забегаю вперёд, новички могут со спокойной совестью эту информацию пропустить.

С созданием базы данных программно большие проблемы! Задача состоит из 2х этапов:

  1. Создание самой базы данных
  2. Создание Alias

Второй вопрос весьма прост - используются методы системного объекта Session:

Session.AddAlias
Session.SaveConfigFile


Ответ же на первый не однозначен и вызвано это тем, что это целиком зависит от базы данных. Так как Dbase и Paradox базы данных это просто каталоги, то тут проблем нет, создаём каталог (ForceDirectories например) и дело с концом, для MS Access и MS Excel уже придётся использовать DAO низкоуровневые функции, серверные базы данных обычно имеют системные Stored Procedures для создания базы данных.


Создание таблицы[править | править код]

Как создать таблицу ?

Вариантов несколько:

  1. Сделать это с помощью менеджера соответствующей базы данных, например таблицу для Paradox создать в Paradox'e
  2. С помощью Database Desktop - утилита , поставляемая с BDE и большинством борландовских продуктов. Я не думаю что вы встретите проблемы при создании таблицы - там всё очень просто.
  3. Из программы. Здесь 2 варианта, вариант первый, использовать метод CreateTable у таблицы, вот пример из справки по Дельфи как Борланд предлагает это делать:
with Table1 do
begin
  Active := False;
  DatabaseName := 'DBDEMOS';
  TableType := ttParadox;
  TableName := 'CustInfo';
  { Don't overwrite an existing table }
  if not Table1.Exists then begin
    { The Table component must not be active }
    { First, describe the type of table and give }
    { it a name }
    { Next, describe the fields in the table }
    with FieldDefs do
    begin
      Clear;
      with AddFieldDef do
      begin
        Name := 'Field1';
        DataType := ftInteger;
        Required := True;
      end;
      with AddFieldDef do
      begin
        Name := 'Field2';
        DataType := ftString;
        Size := 30;
      end;
    end;
    { Next, describe any indexes }
    with IndexDefs do
    begin
      Clear;
      { The 1st index has no name because it is
      { a Paradox primary key }
      with AddIndexDef do
      begin
        Name := '';
        Fields := 'Field1';
        Options := [ixPrimary];
      end;
      with AddIndexDef do
      begin
        Name := 'Fld2Indx';
        Fields := 'Field2';
        Options := [ixCaseInsensitive];
      end;
    end;
    { Call the CreateTable method to create the table }
    CreateTable;
  end;
end;


Есть другой метод, который нравится мне гораздо больше, он проще в реализации и работает стабильнее - выполнить Query типа "Create Table". Этот способ я разберу позже, когда мы будем рассматривать работу с запросами.


Изменение данных в таблице[править | править код]

Теперь последний штрих о работе с таблицами. Мы разобрали способы обратиться к нужному столбцу и нужной записи, их поиск и чтение значений. А как записать новое значение? Для этого есть методы Edit, Append, Insert, Post и Cancel.

1) Надо изменить текущую запись.

Table1.Edit(); // переводим таблицу в режим редактирования
Table1.FieldByName('Category').AsString := 'New value'; // изменяем поле
Table1.Post(); // сохраняем изменения


2) Надо добавить новую запись.

Table1.Append(); // переводим таблицу в режим добавления новой записи
Table1.FieldByName('Category').AsString := 'New value'; // присваиваем значение полей
Table1.Post(); // сохраняем изменения


Итого, как видим изменения данных производятся точно так же как и чтение, но перед изменением КАЖДОЙ записи таблица должна быть переведена в режим редактирования, а после изменения КАЖДОЙ записи изменения должны быть сохранены.

В режим редактирования таблицу переводят следующие методы:

  • Edit - редактирование текущей записи
  • Append - добавление записи в конец таблицы
  • Insert - вставка записи перед текущей

Для выхода из режима редактирования служат методы:

  • Post - запомнить изменения и выйти из режима редактирования
  • Cancel - отменить сделанные изменения и выйти из режима редактирования


Понятие о запросе (Query)[править | править код]

Итак, это пожалуй почти всё, что мы можем "выжать" из компонента TTable. Почувствовали мощь и удобство работы с базами данных? - Ой, вряд ли! Скорее всего ощущение заумности, убогости и неудобства в сочетании с крайней медлительностью операций... Действительно доступ к базе данных через TTable является самым прямым и самым неудобным, какие же основные недостатки? Наверное уже многие задались вопросами:

1) А если мне надо 1000 записей изменить, так мне надо в цикле крутить приведенный код? Так это ж как долго!

2) Ну хорошо, у меня есть табличка на сотню тысяч записей, и мне нужно из неё выбрать только, например, 1000 записей касательно "Иванова", так мне придётся всю таблицу в цикле крутить пока не найду то что мне нужно?

Все эти проблемы решены, и решены великолепно - просто, очень эффективно и с минимальными затратами.

Рассмотрим вкратце общий алгоритм доступа к информации который был рассмотрен:

  1. Передать данные из таблицы в программу
  2. Программа сама занимается поиском данных

А что если изменить схему:

  1. Отдать команду драйверу на поиск нужной информации
  2. Драйвер найдёт инфу
  3. Найденная информация будет возвращена программе.


На первый взгляд разница только в том, что работу, которую делает программный код в первом случае, мы пробуем свалить на драйвера базы данных. Ну и зачем? Оказывается есть зачем! В первой схеме - есть несколько моментов на которые я бы хотел обратить внимание:

1) В любом случае манипулирование базой производится драйвером, следовательно код драйвера в любом случае работает. Драйвер написан под конкретную базу данных, и "знает" каким образом манипулировать с базой самым быстрым способом, а программный код неизбежно подвергается "транслированию", многочисленным преобразованиям данных для обеспечения совместимости.

2) А если база данных находится на другом компьютере? Тогда программа вынуждена "вытащить" все данные на локальный компьютер перед манипулированием данными, что есть совсем не быстрая операция. Кстати пока мы тащим гигабайтную таблицу к себе, ваш сосед Вася уже успел там что-то поменять, и что теперь? Тащим заново, или посылаем Васю подальше, и не даём ему работать с базой пока сами не закончим?

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

И как же это реализовано? Это реализовано с помощью языка запросов: SQL. Подробнее этот язык рассматривается в разделе "Базы данных", мы же не заостряя внимание на самих запросах, рассмотрим простейшие случаи как этот язык можно применить внутри программы на Дельфи и какие выгоды можем мы получить используя запросы.

Итак, из нашего тестового проекта удаляем компонент Table1. Вместо него ставим компонент запроса TQuery. Устанавливаем Alias (DatabaseName) и связываем его с DataSource таким же образом как и таблицу.

Пока вы найдёте 2 радикальных отличия от TTable:

  1. Нет свойства TableName
  2. Открытие (Active := True) приводит к ошибке

Query должна содержать текст запроса. Для этого есть свойство SQL, являющееся самым обыкновенным TStringList. Откроем это свойство и напишем примерно следующее:

SELECT * FROM biolife

Теперь можно попытаться открыть запрос (установить Active в True) и вы получите точно тоже что и в первом примере - грид заполненный данными из таблицы Biolife.db. Мало того, всё что мы проделывали с таблицей - позиционирование строки, чтение и запись полей и т.п. вы можете с успехом сделать и с Query - причём синтаксис тот же самый! Пока мы только сделали замену компоненту TTable и не более того.

Я здесь не буду подробно останавливаться на синтаксисе SQL запроса, материал по этой теме вы найдёте здесь:

http://forum.vingrad.ru/index.php?act=ST&f=12&t=5638

Однако простейшие приёмы работы я покажу. В нескольких дальнейших примерах мы будем менять свойство "SQL". Перед каждым изменением этого свойства Вы должны закрывать Query (в дизайне устанавливать Active в False). В run-time изменения SQL должны выглядеть примерно так:

Query1.Active := False;
Query1.SQL.Text := 'SELECT * FROM biolife';
Query1.Active := True;


Запросы на выбор данных[править | править код]

Теперь покажу на примере как можно использовать TQuery для наиболее простых, но очень частых и нужных операций:

1) Выбор только тех строк (записей) которые отвечают условию (например тех где в поле category записано 'Snapper')

SELECT * FROM biolife
WHERE category='Snapper'


2) Выбор только нужных столбцов (например нам нужны только столбцы Category и common_name )

SELECT category, common_name FROM biolife


3) Выбор записей отсортированных в определённом порядке (например в алфавитном порядке поля Category)

SELECT * FROM biolife
ORDER BY category


4) Запрос может комбинировать в себе всё перечисленное

SELECT category, common_name FROM biolife
WHERE category='Snapper'
ORDER BY common_name


Попробуйте задать каждый из этих запросов и посмотреть как программа будет реагировать на него. На самом деле запросы предоставляют гораздо большие возможности - например:

  • суммарные и статистические функции (вычислить сумму всех значений поля),
  • вычисляемые поля (например добавить столбец который отражает не реальное поле в таблице, а сумму 2-х других полей),
  • объединение нескольких таблиц в одном запросе (2 таблицы с похожей структурой представляются как одна таблица),
  • запросы на несколько таблиц (например вам надо выбрать всех из одной таблицы которые не встречаются в другой таблице, или для Иванова взять его номер телефона из одной таблицы, а его заказы из другой и т.п.).

Всё это вы найдёте здесь: http://forum.vingrad.ru/index.php?act=ST&f=12&t=5638 , а в этой статье я только показываю как с этим можно работать из Дельфи


Запросы на изменение данных[править | править код]

А как менять значения в базе данных? Тоже при помощи TQuery это делать проще и, что важнее, значительно быстрее. Например, меняем в поле category все "Cod" на "Kod". В Query.SQL пишем текст:

UPDATE biolife
SET category='Kod'
WHERE category='Cod'


Ставим на форму кнопку, в обработчике нажатия (onClick) пишем код:

Query1.ExecSQL;

Важные примечания: 1) Обратите внимание, что в данном случае мы не открываем запрос делая Active := True и не используем эквивалентный метод Open, а используем метод ExecSQL. Если открытие запроса с оператором Select приводит к возвращению данных в программу (так называемый курсор данных), то все остальные типы запросов никаких данных в программу не возвращают - они выполняют операцию над базой, но не возвращают курсор. Такой запрос НЕЛЬЗЯ соединить с визуальными компонентами, его открытие хоть и будет выполнять операцию, но будет приводить к исключительной ситуации.

2) Перед изменением текста запроса, хоть в дизайне, хоть в run-time запрос должен быть закрыт.

Аналогичным способом можно пользоваться другими операторами SQL:

  • DELETE - для удаления нескольких/всех строк
  • INSERT - для вставки одной или нескольких строк
  • CREATE TABLE - для создания таблицы
  • ALTER TABLE - для изменения структуры таблицы
  • DROP TABLE - для удаления таблицы

и другими. Смотрите руководства по SQL по использованию этих операторов.



Author: Vit (www.delphist.com, www.drkb.ru, www.unihighlighter.com, www.nevzorov.org)
Source: Взято с Vingrad.ru http://forum.vingrad.ru
ID: 02460