понедельник, 28 июля 2008 г.

Компонент TUpdateSQL

Этот компонент довольно-таки небольшой. Поэтому мы с ним быстро разделаемся.

Предназначен он для отложенного внесения изменений в таблицы. Потому и подключается он к компонентам TTable, TQuery и TStoredProc через свойство UpdateObject. При этом свойство CachedUpdates необходимо установить равным true.

Его можно примениять к любому из поддерживающих его компонентов. Но чаще всего он встречается в двух случаях:

  • когда необходимо "оживить" TQuery со сложным SQL-запросом
  • при создании клиент-серверных приложений

При создании клиент-серверных приложений не следует надолго занимать данные. Например, пока пользователь редактирует даные, которые подключены у вас в программе черех обычный TTable без кеширования изменений, данные заняты и никто не может получить их. Ничего не мешает пользователю уйти на обед оставив таблицу в режиме редактирования, заблокировав таким образом работу остальных пользователей в этой таблицей. А иногда и со связанными с ней. Использование TUpdateSQL позволяет изменить стратегию работы с данными - данные все время доступны, а блокируются только на момент внесения изменений - то есть на доли секунды. Есть еще один важный момент связанный с понятием транзакций, но об этом в другой раз.

С "оживлением" TQuery ситуация такая - когда вы создаете запрос, в зависимости от того, что вы там напишете, TQuery может позволить или не позволить установить свойство RequestLive в true (см. урок о TQuery). Если позволит - мы получаем компонент, который позволяет вносить изменения в данные, но блокирует их на время внесения изменений, аналогично TTable. А если не позволит, то мы получаем компонент, который не позволяет вносить изменения. А если нам надо, чтобы позволял - воспользуемся TUpdateSQL.

Для изучения его свойств воспользуемся нашим проектом, созданным еще несколько уроков назад. В последнем уроке мы применили TQuery. В этом уроке мы присоединим его к TUpdateSQL. Присоединение к TTable будет аналогичным.

Во-первых установим сам компонент на форму. Его можно найти на странице компонентов DataAccess для Delphi 5 и ниже или на странице BDE для Delphi 6. Во-вторых свойству UpdateObject компонента TQuery необходимо присвоить имя нашего компонента TUpdateSQL. В третьих свойство CachedUpdates компонента TQuery должно быть равным true.

После этого, если ваш компонент TQuery полностью настроен (а у нас он давно настроен), достаточно двойного щелчка мыши на TUpdateSQL, чтобы открылось диалоговое окно его настройки, в котором почти все уже настроено. Почти, но не все. Нас интересуют колонки Key Fields, Update Fields переключатель Quote Field Names и кнопка Generate SQL. Если нажать кнопку Select Primary Keys. То в колонке Key Fields выделенным останется только ключевое поле. Вообще в этой колонке указываются поля, по значениям которых TUpdateSQL отличает одну строку от другой. К том случае, если у вас есть первичный индекс, достаточно только полей, входящих в него. Но только в том случае, если он есть.

Если первичный индекс есть, то нажатием кнопки Select Primary Keys в колонке Key Fields мы оставляем только нужные ключевые поля. При этом в колонке Update Fields следует оставить выделенными только те поля, которые не выделены в первой колонке. Особое внимание следует обратить на то, чтобы не были выделены поля, которые заполняются автоматически. Например, если оставить выделенным поле, тип которого AutoIncrement, Delphi вообще выдаст ошибку при попытке обновить данные. В нашем случае следует убрать выделение с поля Species No.

Если же первичного индекса нет, или ваш запрос выбирает данные с нескольких таблиц, то можно в качестве ключевых указать все поля, как и было по умолчанию.

Далее, если у нас есть поля, в именах которых встречаются пробелы, знаки подчеркивания, и другие "запрещенные" символы, следует установить галочку для Quote Field Names. Если таких полей нет - не обязательно. Впрочем, если эту галочку устанавливать всегда, тоже не ошибетесь. Ну все, теперь можно нажимать на Generate SQL и смотреть что получилось. На закладке SQL теперь сформированы запросы, которые будут выполняться при изменениии (modify), вставке (insert) и удалении (delete) данных. Если вы знакомы с SQL, вам будет понятно, что здесь происходит, а если нет - не огорчайтесь, со следующего урока мы начинаем его изучение. Впрочем, компонент TUpdateSQL все делает сам, так что у вас все будет работать даже если вы и не знакомы с SQL.

Если вам понадобится внести изменения в запрос в TQuery, следует повторить для связанного TUpdateSQL описанные выше действия начиная с указания ключевых полей.

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

Вот и все, если вы все делали правильно, можете компилировать программу, все будет работать.:)

Подробно о компонентах. TQuery.

В прошлом уроке мы рассмотрели копонент TTable. TQuery во многом похож на него, но во многом и различается. Причем различий больше. Так и опишем, сначала общее, затем различия.

Оба компонента имеют TDataSet среди своих предков. Этим и определяются общие черты. Так все что касается большинства свойств, всех описаных в предыдущем уроке событий, методов доступа к данным здесь также применимо.

Например, возьмем программe, которую в 4 уроке мы создали с помошью одной только мышки. Установим на форму компонент TQuery. Свойство Database установим равным DBDemos. Свойство SQL равным Select * From biolife. Если вы не знакомы с языком запросов SQL, пока пишите как видите, мы рассмотрим его подробно в ближайших уроках. Все, свойство Active в true, с этим компонентом закончили. Теперь достаточно в DataSource свойству DataSet присвоить имя нашего TQuery (Query1, если вы его не переименовали) чтобы наша программа заработала через TQuery вместо TTable. Все очень просто.

Но, конечно не для таких простых случаев предназначается компонент TQuery. Точнее не только для таких простых случаев. Он нам пригодится там, где не хватает возможностей TTable. А именно:

  1. Когда необходимо получить данные из нескольких таблиц так, как будто это одна таблица.
  2. Когда необходимо прямо во время работы программы менять источники данных, переключаться на другие таблицы.
  3. Удобно пользоваться им в тех случаях, когда отфильтровывается множество данных. При этом он фильтрует знаительно быстрее, чем TTable с использованием обработчика события OnFilterRecord.
  4. При создании клиент-серверных приложений, с ним удобнее правильно организовавать блокировки данных.
  5. Он позволяет выполнять сложные запросы, что гораздо больше чем просто выборка строк из таблицы.
  6. Он позволяет производить не только выборку данных, но и модификацию, создание и удаление таблиц, раздачу прав доступа - вся мощь SQL в ваших руках.
  7. Наконец самое главное, запрос выполняется на сервере. Сервер получает запрос, выбирает данные в соответствии с ним, и отправляет их вам. Вы врядли почувствуете разницу при создании простых небольших приложений, но она будет весьма значительна при попытке получить три строки по сети из базы в 100 000 записей. Особенно если сеть не слишком быстрая. Впрочем и при создании небольших локальных приложений разница существует.

Как и в TTable, свойства, которые видны в Object Inspector лишь вершина айсберга. Большая часть полезностей доступна программным путем. И здесь опять множество отличий. Например, перечислим способы получить данные из поля:

  1. A := Query1Field1.Value;
  2. A := Query1.FieldByName('Field1').Value;
  3. A := Query1.FieldByName('Field1').AsString; // AsInteger, AsFloat и т.д.
  4. A := Query1.FieldValues['Field1'];
  5. A := Query1.['Field1'];
  6. A := Query1.Fields.Fields[1].Value;
  7. A := Query1.Fields.Fields[1].AsString; // AsInteger, AsFloat и т.д.

Здесь есть тонкость, заключающаяся в том, что запросы часто меняются программно. Это накладывает некоторые ограничения на использование этих способов доступа к данным. Например первым пользоваться надо понимая, что если вы программно измените запрос, который выполняет выборку, то полей с такими именами в результируещем наборе может и не оказаться, или у них окажутся другие типы. В этом случае программа создаст исключительную ситуацию. При использовании способов 2-5 надо понимать, что если ваш запрос получает данные с нескольких таблиц, то в них могут оказаться поля с одинаковыми именами. А при использовании последних двух способов - что поля могут поменять порядок. Впрочем вы не подумайте, я не пугаю, я только указываю на потенциальные ошибки и призываю быть внимательными.

Так как же пользоваться этими способами? Как обычно, учитывая несколько простых правил.

  • Если вы выбрали первый способ - необходимо чтобы в вашем измененном запросе сохранились все те поля, которые и были до изменений. То есть скорее всего вы ограничитесь изменением условий выборки.
  • При выборке из нескольких таблиц, если появляются, например, 2 поля с одинаковыми именами (Field1), Delphi даст им имена Field1 и Field1_1. Т.е. поле, которое содержится в таблице, упомянутой первой сохранит свое имя, а второе - изменит.
  • Обращаться к полю по имени, на мой взгляд, не совсем удобно. Не наглядно, и через некоторое время можно забыть, какое поле подразумевалось. Такой подход имеет смысл при переборе всех полей в цикле, или когда пользователь сам выбирает таблицу, и вы не знаете имен полей, с которыми придется работать. При изменении запроса важно сохранить не только все поля, как в предыдущем случае, но и их последовательность.

Все стандартные методы перемещения по выборке (First, Last, Next, Prior) действуют и здесь. Аналогично и с поиском - Locate, Lookup, FindKey, FindNearest и пр. А вот при изменении опять отличия. Дело в том, что изменять можно данные не любого запроса. Для того, чтобы запрос позволял вносить изменения в выбранные данные, он должен удовлетворять следующим условиям:

  • Запрос должен представлять собой именно выборку, т.е. оператор select. У вас ничего не выйдет, если запрос представляет собой вызов хранимой процедуры или что-нибудь типа Create Table.
  • Запрос должен выбирать данные всего из одной таблицы.
  • Выходные данные не сортируются, т.е. в запросе нет Group by и Order by
  • В запросе не используются агрегатные функции, т.е. функции, вычисляющие значения для групп записей (sum, count, min, max, avg и др.)
  • Данные не кешируются, т.е. значение свойства CashedUpdate равно false.
  • Необходимо установить свойство RequestLive в true.
  • Следует перед попыткой изменить данные проверить свойство CanModify. Если оно равно false - у вас ничего не выйдет.
  • Могут быть и другие ограничения, все зависит от используемой вами СУБД и ваших прав доступа.

Если наш запрос - изменяемый, то прекрасно работают методы Edit, Insert, Delete, Post, Cancel - как в TTable. А если не изменяемый - выйти из положения нам поможет компонент TUpdateSQL, который мы рассмотрим на следующем уроке. Пока вам достаточно просто знать, что выход есть.

С событиями тоже все похоже. Более того, они здесь те же самые и вызываются в тех же случаях. Поэтому можете смело смотреть описание событий TTable в предыдущем уроке.

Это все были похожести. Теперь отличия.

Главным отличием компонента TQuery, как уже было сказано, является возможность на прямо во время выполнения программы изменять как источник данных, так и условия выборки. При этом может меняться как весь запрос, так и отдельные его параметры. Применяются оба способа, в зависимости от того, что вам надо.

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

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

select * from biolife where biolife.&amp;amp;quot;Length (cm)&amp;amp;quot;<br /> between :min and :max те переменные, которые начинаются с двоеточия и есть параметры. Теперь надо указать их тип. Для этого найдем свойство params TQuery и установим для каждого из наших параметров Value.Type равным Integer.Теперь внесем изменения в форму. Для этого нам понядобятся два TEdit и два TLabel. Как именно они нам понадобились - видно на рисунке Демонстрация работы с параметрами

Для обоих TEdit надо добавить обработчики событий OnChange со следующим содержимым:

procedure<br /> TForm1.Edit1Change(Sender: TObject); var temp, rez: integer; begin val (Edit1.Text, rez,<br /> temp); Query1.ParamByName('min').Value := rez; Query1.Open; end; procedure<br /> TForm1.Edit2Change(Sender: TObject); var temp, rez: integer; begin val (Edit2.Text, rez,<br /> temp); Query1.Active := false; Query1.ParamByName('max').Value := rez; Query1.Open; end; К параметрам, как и к полям можно обращаться по разному. Так, как написано в примере или так:Query1.Param[0].value или вот такQuery1.Param[0].AsIntegerсуть от этого не меняется.

Для большой базы данных такой не следует заново активизировать выполнение запроса при каждом изменении - это будет тормозить. Но у нас небольшая таблица, все работает достаточно быстро.

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

with tempQuery do begin Clear; Add ('select into smallFishes * from biolife'); add ('<br /> where biolife.&amp;amp;quot;Length (cm)&amp;amp;quot; &amp;lt;30'); ExecSQL; end; // with

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

В начале я удаляю старый текст запроса, если он там был. (Clear). Затем создаю новый (Add). Для Delphi не имеет значения, на сколько строк я разобью свой запрос, я мог это сделать и в одной строке. А разбил на две просто для наглядности. Когда новый запрос создан, я активизирую его.

Существуют несколько способов активизации запроса:

  1. Присвоить Active = true;
  2. Вызвать метов ExecSQL;
  3. Вызвать метод Open;

Есть и другие, связанные с вызовом Prepare для того, чтобы BDE "приготовилась" для выборки. Но о них как-нибудь в другой раз. Вызов Open Borland рекомендует для запросов выбирающих данные, а вызов ExecSQL - для запросов модифицирующих данные. Как показывает мой опыт, в большинстве случаев не имеет значения, какой из трех показанных выше способов вы выберете. Но для того, чтобы быть уверенным, что все сработает, следует все-таки следовать рекомендациям разработчика.

У TQuery есть еще много особенностей. Но их мы будем рассатривать по мере того, как будем с ними сталкиваться. Для написания большинства приложений достаточно и тех, что мы рассмотрели. В следующем уроке мы рассмотрим как можно сделать сложный запрос по нескольким таблицам и с сортировками изменяемым.

Подробно о компонентах. TTable.

Начнем урок нестандартно - с небольшего лирического отступления. Как известно, в Object Pascal классы описываются так:

type TMyClass = class(TParentClass) ... свойства, <br /> переменные, методы ... end; то есть принято имя класса начинать с буквы T, от слова type. Аналогично в C++ имена классов начинаются с С, от слова class. Сами компоненнты при этом часто называют без Т в начале: Table, Query и др. Поэтому в дальнейшем будем считать эти два вида именования в разговоре синонимами. Т.е. Table и TTable - один и тот же компонент. В программе же именовать их надо правильно, с буквой T в начале.

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

Установив компонент на форму, следует дать ему осмысленное имя. Например tabClients или ClientsTab если речь идет о таблице клиентов или еще что-нибудь в этом роде. Для этого установим свойство Name компонента. Далее нас будет интересовать свойство DatabaseName. Это имя псевдонима, которое создается в BDE Administrator или в Database Desktop. Подробнее об этом в первом уроке.

После этого установим свойство TableName. Это свойство укажет, на какую из таблиц будет указывать наш TTable в этой базе. После того, как вы установили эти свойства, можно свойство Active попробовать сменить c false на true. Если у вас получилось - вы все сделали правильно. Если нет - возможно вы неправильно указали имя базы или таблицы. (DatabaseName или TableName).

Кроме этих свойств нас заинтересуют следующие:

AutoCalcFields
При установке в false обработчик события OnCalcFields игнорируется. При установке в true - вызывается для каждой записи. Весьма полезно для вычисляемых полей, но имеет особенности, о которых ниже.
Filtered
При установке в false значение свойства Filter и обработчик OnFilterRecord игнорируются. При установке в true - выполняются для каждой записи.
Filter
Имеет смысл только при Filter = true. Фильтрует записи в таблице. Обычно применим тогда, когда можно по одному простому выражению определить, надо ли включать запись в результирующий набор данных или нет. Если для того, чтобы определить это недостаточно одного выражения, применяется обработчик события OnFilterRecord.
IndexFieldName и IndexFields
Позволяет выбрать активный индекс. Если значения не выбраны, текущим считается первичный индекс. В программе так и применяется: ClientsTab.IndexName := 'FamIdx'; или ClientsTab.IndexFieldNames := 'Fam;Im;Otch'; отсортирует таблицу клиентов по фамилиям именам и отчествам. ClientsTab.IndexName := ''; или ClientsTab.IndexFieldNames := ''; отсортитует по первичному ключу. в этих примерах предполагалось, что у вас есть таблица клиентов с индексом FamIdx по полям Fam, Im, Otch. Важно отметить такую особенность TTable при работе с индексами - можно написать ClientsTab.IndexFieldNames:= 'Fam;Im'; при этом таблица будет отсортирована по фамилиям и именам, игнорирую порядок по отчесвам. Но нельзя написать ClientsTab.IndexFieldNames := 'Fam;Otch'; т.к. это нарушает порядок полейЮ по которым был построен индекс.
MasterField и Mastersource
Применяются при организации отношений главный-подчиненный Подробнее как-нибудь позже.
ReadOnly
Простой способ сделать вашу таблицу доступной только для чтения.
UpdateObject
Применяется в паре с CashedUpdate, подробности при рассмотрении клиент-серверных приложений и компонента TQuery.

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

Те свойства, которые видны в Object Inspector лишь вершина айсберга. Большая часть полезностей доступна программным путем. Например, перечислим способы получить данные из поля:

  1. A := Table1Field1.Value;
  2. A := Table1.FieldByName('Field1').Value;
  3. A := Table1.FieldByName('Field1').AsString; // AsInteger, AsFloat и т.д.
  4. A := Table1.FieldValues['Field1'];
  5. A := Table1.['Field1'];
  6. A := Table1.Fields.Fields[1].Value;
  7. A := Table1.Fields.Fields[1].AsString; // AsInteger, AsFloat и т.д.

Кто еще знает, напишите мне, будет интересно.
А вот еще популярные свойтсва

BOF и EOF
Указывают на начало и конец таблицы. Применяются, например, так: while not Table1.EOF do begin ... ... Table1.Next; end; // while Важное замечание. При достижении конца таблицы, когда указатель указывает на последнюю запись, Table1.EOF вернет вам false. А вот если вы попытаетесь выйти за пределы таблицы (Table1.Next), вот тогда EOF вернет true;
RecordCount
Позволяет узнать общее количество записей в таблице.
RecNo
Указывает на номер текущей записи в таблице. В отличие от FoxPro и подобных, не может считаться идентификатором строки, так как зависит от индексации и других факторов. Если вам необходимо перемещаться по таблице, а затем возвращаться обратно, пользуйтесь ключевыми полями и первичными индексами. Или закладками (TBookmark).
State
Указывает на состояние таблицы. Применяется, например, так: if Table1.State in [dsInsert, dsEdit] then Table1.Post; т.е. перед вызовом метода Post сначала проверяется, а имеет ли смысл этот вызов.

Есть еще много других свойств, но их мы будем рассматривать по мере работы.

Кроме свойств нас еще будут интересовать методы. Наиболее используемые:

Edit
Переводит строку в режим редактирования. Без этого вы не сможете в ней ничего изменить. Пример ниже.
Insert/Append
Добавляют строку. Таблица автоматически переходит в режим редактирования добавленной строки. Отличие - Append добавляет строку в конец, а Insert - в текущую позицию.
Post
Сохраняет внесенные изменения. Необходимо вызывать для каждой измененной записи, перед переходом на следующую. Пример применения: while not EmployeeTab.eof do begin EmployeeTab.Edit; EmployeeTab['TabNo'] := EmployeeTab.RecNo; EmployeeTab.Post; EmployeeTab.Next; end; // while Этот фрагмент присваивает всем работникам табельные номера в соответствии с их порядком в таблице. Напомню, что порядок может зависеть от индекса, фильтра и других параметров.
Cancel
Отказ от изменений.
Delete
Удаляет запись.
First/Last
Переход на первую/последнюю запись. Какая запись считается первой/последней, зависит от индекса, фильтра и других параметров.
Prior/Next
Переход на предыдущую/последующую запись.
SetRange, SetRangeStart, SetRangeEnd
используются для фильтрации
FindKey
Поиск записи по ключу. Ключ определяется текущим индексом.
FindNearest
Обычно применяется, когда не найдена запись, точно соответствующая ключу, и надо найти хоть что-нибудь.
Locate
Весьма интересный метод. Позволяет искать по любому полю или его части, по возможности использует имеющиеся индекы, даже если они не активны. Медленне, чем FindKey. Обычно применяется, когда нет ключа для требуемого поля.
Lookup
Аналогична Locate, но перемещения не происходит. Просто возвращается ключ подходящей строки.

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

Последнее, что нас будет интересовать в этом уроке - это события компонента TTable. Их тоже много, мы рассморим самые для нас интересные.

BeforeEdit, BeforeDelete, BeforePost и другие
Названия говорят сами за себя. Вызываются сразу перед соответствующим событием. Однако следует проявлять осторожность - не следует выполнять действия, которые могут повторно вызвать этот же метод. Т.е. нельзя с обработчике BeforePost выполнять сохранения изменений, т.к. это повторно вызовет этот же метод. Стандартная ошибка начинающих. Или вызвать метод Edit в обработчике BeforePost, не учтя, что после внесения изменений они будут сохраняться, что повлечет повторный вызов BeforePost, который вызовет Edit... до бесконечности.
AfterEdit, AfterDelete, AfterPost и другие
Аналогично предыдущим, названия говорят сами за себя. Вызываются сразу после соответствующего события. Те же предостережения.
BeforeScroll, AfterScroll
Вызываются при каждом перемещении между записями.
OnCalcFields
При установленном AutoCalcFields вызывается для каждой записи. Именно в обработчике этого события и происходит вычисление значений вычисляемых полей. Удобно, но надо помнить о:
  1. Если вычислений будет слишком много, программа будет заметно тормозить.
  2. Если внутри этого обработчива выполнить переход на другую запись, этот же метод будет вызван для той записи, и так дале, что может выйти боком.
OnFilterRecord
Возвращает true или false, что указывает, обращать внимание на эту запись или нет. Слишком объемные вычисления могут замедлить программу.
OnPostError, OnEditError и т.п.
Позволяют вам самим обработать исключительную ситуацию. Что-то исправить, или хотя бы вывести сообщение об ошибке на понятном пользователю языке.

Ну хватит. Выводы, которые вы для себя должны были сделать за последние 2 урока:

  • Простые приложения можно слепить на Delphi за пару минут не сильно напрягаясь.
  • Когда надо добавить функциональность, которую не обеспечивают простые решения, у вас есть множество средств для этого. Это очень сильная черта, присуща именно Delphi. В других стредах у вас либо связаны руки, либо вы получаете большую функциональность вместе с большой сложностью. В Delphi вы можете сами выбрать баланс между простотой и функциональностью.
  • В Delphi очень большая библиотека компонент для работы с базами данных. Она вам очень поможет.
  • Всего не запомнишь. Наиболее употребляемые свойства, методы запоминаются, об остальных достаточно просто знать, что они существуют. При необходимости это можно найти в документации.
  • Многое зависит от приемов программирования. Какие-то изобретаешь сам, какие-то заимствуешь у других. Бывает, что и менее опытный чем ты товарищ придумает элегантное решение какой-то проблемы. Не грех и поучиться друг у друга.

Переходим к практике

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

Будем считать, что с основами Delphi вы уже знакомы, и знаете, как создать проект. Вот и создадим пустой проект. В нашем первом проекте все будет просто. Даже очень просто. Зато с первого раза все усвоится. Начнем с хрестоматийного примера создания приложения с помощью одной лишь мыши. Да-да, можете придвинуть мышь и отодвинуть клавиатуру - она нам сегодня не понадобится.

Delphi позволяет работать с базами данных посредством нескольких интерфейсов. До 4-й версии это был только BDE (Borland Database Engine), в 5-й появился ADO (ActiveX Data Object) - Microsoft-овский интерфейс, "родной" для Access и его формата *.mdb. В 6-й версии появился интерфейс dbExpress, который сама Borland рекомендует применять к клиент-серверным базам данных на MySQL и др. Начнем рассмотрение всех этих интерфейсов с BDE как наиболее заслуженного и имеющегося во всех версиях. Остальные потом.

Итак, преимущества BDE:

  • поддержка многих форматов. В самом BDE встроена поддержка многих форматов. К тому же BDE автоматически поддерживает все ODBC-драйвера (Open Database Connectivity - еще один интерфейс к базам данных), установленные в вашей операционной системе.
  • дополнение стандартных способов работы полезными функциями. Например, когда сервер возвращает данные в ответ на SQL - запрос, он никому не должен поддерживать возможность передвигаться в обоих направлениях - он обеспечивает движение только вперед. BDE кэширует эти данные и когда вы запросите предыдущую запись, вытащит ее из кэша. Если бы этого не было, вам пришлось бы запрашивать данные с сервера снова, и проскакивать "лишние" предыдущие записи. Только подумайте, как эта возможность облегчает жизнь во многих случаях.
  • хорошая изученность и отлаженность. Все ошибки и особенности работы хорошо известны и вам будет несложно найти описание или получить совет.

Из недостатков я бы отметил необходимость распространения BDE вместе с вашим приложением, что "утяжеляет" инсталляцию на 2-3 дискеты.

Это было лирическое отступление. А сейчас нам понадобятся компоненты со страниц Data Access и Data Controls. На странице Data Access располагаются невизуальные компоненты, предназначенные для подключения к базам данных. Невизуальные - значит при помещении их на форму ничего вы не увидите. То есть увидите, конечно, в режиме разработки, да размер exe-файла увеличится, но данные на форме не появятся. А чтобы они появились, нам понадобятся компоненты со страницы Data Controls.

На станице Data Access расположились такие компоненты как:

  • Table - таблица. Позволяет подключить одну таблицу данных.
  • Query - запрос. Позволяет работать с результатом запроса как с таблицей. С некоторыми ограничениями, правда.
  • Stored Proc - Хранимая процедура. Программа, хранящаяся на сервере, выполняющая на нем какие-то действия и возвращающая значения. Когда вам необходимо обработать несколько больших (а иногда просто огромных) таблиц, и на основании этой обработки получить небольшую табличку или пару чисел, которые и нужны вашей программе - незаменимая вещь. Может вернуть вашей программе, как пару значений, так и целую таблицу.
  • Database, Session. Все таблицы, запросы и все остальное должны работать через компоненты Database и Session. Если вы их не поместите на форму самостоятельно, Delphi создаст их автоматически при запуске программы. Пока оставим их в покое.
  • BatchMove - компонент, который окажется полезным при переносе большого объема данных из одной таблицы в другую. Обычно используется во всяких конвертерах, хотя и не обязательно.
  • UpdateSQL - компонент, который поможет нам преодолеть те ограничения, о который я говорил в пункте про запросы (Query).
  • Nested Table - таблица в таблице. Позволяет сделать вид, что одна таблица - всего лишь значение поля в другой.
  • DataSource - источник данных. Он стоит на первом месте на этой странице компонент. Это подчеркивает его важность. Именно через него и будут работать компоненты со страницы Data Controls. Подробности ниже.

Компоненты Table, Query и подобные могут использоваться самостоятельно. Именно из них видны данные. Если ваша программа должна выполнять какие-то расчеты, ничего не показывая пользователю (до поры), можно обойтись только ими. Получить данные из них можно, например, так:

A := Table1.FieldByName("Field1").Value;

Или так:

B := Table1Field1.Value;

Или еще целой кучей способов. Их мы рассмотрим в ближайших уроках для каждого компонента в отдельности.

А пока нам важно, что для простого получения данных достаточно одних лишь компонентов типа Table или подобных. А вот для демонстрации их пользователю и для того, чтобы позволить их изменить нам понадобятся компоненты со страницы Data Controls. Но сами по себе они не могут увидеть данные. Для того, чтобы им показать данные нужны:

  1. Компонент, который видит данные, например Table;
  2. Компонент, который показывает данные, например DBGrid;
  3. Мост между ними, а именно компонент DataSource;

Компонентов на странице Data Controls много. Почти все они имеют аналоги на других страницах, и известны вам (аналоги). Например, компонент DBText - просто Label, который данные берет из базы. При этом вы не можете заставить показать нужный вам текст, у него нет не свойства Caption, ни свойства Text. Вместо этого, вам надо поместить значение прямо в базу данных, именно тогда оно будет отображено.

Особенности работы с этими компонентами будут рассмотрены позже. А пока нам достаточно запомнить, что они похожи на уже известные компоненты, но работают через DataSource с невизуальными компонентами.

В общем-то можно обойтись без этих компонентов. Например в событии (подробнее о событиях позже) OnScroll компонента TTable вписав строку

Label1.Caption := Table1Field1.Value;

можно обеспечить показ значения поля обычным компонентом Label. Но для того нам и нужны компоненты Delphi, чтобы не делать много раз одну и ту же работу.

Итак, давайте возьмемся за мышку (клавиатуру отодвинули? ;)) и сделаем что-нибудь, что реально заработает. Найдем страницу компонент Data Access, на ней найдем компонент Table, поместим его на форму. Свяжем его с базой данных DBDEMOS, для чего в свойстве Database этого компонента установим значение DBDEMOS. Эта база - демонстрационная и поставляется вместе с Delphi. В этой базе выберем таблицу animals.dbf, т.е. присвоим это значение свойству TableName. Мы все также пользуемся мышью, все эти значения выбираются из выпадающих списков.

Следующим компонентом, который нам понадобится будет DataSource. Мы ведь собираемся что-то показать пользователю. Итак, установим DataSource на форму, а его свойству DataSet присвоим занчение Table1. Обратите внимание, имя компонента Table, который мы только недавно установили на форму было Table1. В больших проектах лучше давать более осмысленные имена компонентам, но в нашем проекте мы обойдемся и таким. Тем более, что решили обойтись без клавиатуры ;).

Следующие компоненты будут визуальными, со страницы Data Controls. Сначала DBGrid. Установим его на форму, придадим ему нужные размеры. Под нужными каждый понимает то, что ему удобно. Я же предлагаю просто взглянуть на рисунок.

пример расположения компонентов на странице

Для того, чтобы в нашей таблице DBGrid появилась какая-то информация надо присоединить ее к источнику данных (DataSource). Просто установим свойство DataSource равным DataSource1. Если ничего не появилось, посмотрим, активна ли таблица. То есть в свойстве Table1.Active должно стоять true. Так и есть, там стоит false. Ставим true, все работает.

Очередным компонентом будет DBNavigator. Этот компонент не имеет аналогов, поэтому уделим ему немного больше внимания. Этот компонент очень прост в работе, его достаточно просто подключить в источнику данных указав DataSource. Этот компонент позволяет нам перемещаться по записям таблицы (первая, последняя, предыдущая, следующая), переходить в режим редактирования, сохранять результаты или отменять редактирование без сохранения. Каждую кнопку можно выключить, или включить, для чего просто поменять значение свойства VisibleButtons. Подсказки к кнопкам на английском языке, но это дело поправимое, достаточно отредактировать свойство Hints. Это вы сделаете сами в качестве домашнего задания.

Очередной компонент - DBImage. Этот компонент отображает картинку, которая хранится в базе данных. На самом деле, хотя это и не слишком подробно отображено в документации, этот компонент сам позволяет поместить картинку в базу данных. Для этого картинку надо подготовить в каком-нибудь графическом редакторе, затем скопировать в буфер обмена, после чего вставить в DBImage с помощью Ctrl+V или Ctrl+Ins. Установим DataSource этого компонента равным DataSource1 и DataField равным Graphic для того, чтобы он мог отобразить нужную нам картинку.

Последним на сегодня компонентом будет DBMemo. Этот компонент очень похож на обычный компонент Memo, поэтому не будем на нем сильно задерживаться. Установим DataSource этого компонента равным DataSource1 и DataField равным Notes.

Сохраним проект, дадим ему имя на свой вкус, например FirstPractice и запустим его. Если вы все сделали правильно, у вас на экране появится программа, которая умеет просматривать таблицу с рыбами, перемещаться от одной записи к другой, редактировать их, даже работает с рисунками и memo-полями. Без единой строчки кода. Всего за 1 минуту (время, которое вы щелкали мышкой). Не плохо, правда?

В качестве домашнего задания предлагаю вам посмотреть возможность русификации программы.
Для этого просмотрите свойства:

  • Hints у DBNavigator;
  • Columns у DBGrid; Если список колонок пуст, нажмите Add All Fields в этом маленьком появившемся окошке (см рисунок).
  • Title.Caption у каждой колонки.
кнопка, на которую надо нажать
Ну вот и все для начала. Подробнее о компонентах для работы с базами данных читайте в следующих уроках.

Некоторые правила построения баз данных

В предыдущих уроках мы имели возможность убедиться, что "не боги горшки обжигают", т.е. ничего нет сложного в том, чтобы создать свою таблицу, или несколько таблиц, задать ключевые поля, связи между ними и получить, таким образом, базу данных. Не буду никого разубеждать, это действительно не сложно. Вот только база базе рознь. От того, как вы продумаете структуры данных будет многое зависеть. Это касается программирования вообще, а не только баз данных, но здесь это проявляется сильнее всего. И чем больше даных в вашей базе, тем серьезнее надо отнестись к проработке ее структуры.
Существуют стандартные приемы, методы, следование которым позволяет получить хороший результат. Наиболее известными, пожалуй, являются нормализация и индексирование.

Нормализация

Итак, нормализация нам нужна для повышения еффективности нашей базы. Сразу следует оговоириться - говоря "база" я подразумеваю совокупность таблиц. Нормализуется именно вся база, при этом вносятся изменения в отдельные таблицы, при необходимости создаются новые, устанавливаются связи между ними и т.д.
Так вот , нормализация - это проце приведения нашей базы к нормальным формам. Их несколько, приведение происходит последовательно. При этом нужно знать, когда остановиться. Об этом чуть позже. А пока знайте, что нормальных форм существует 6 (известных, никто не мешает вам придумать седьмую), но обычно останавливаются на третьей, иногда на второй. Впрочем, при определенном опыте вы с ходу будете разрабатывать базы такими, что они сразу будут удовлетворять третьей нормальной форме. Теперь подробнее.

Первая нормальная форма

Первая нормальная форма требует, чтобы каждое поле таблицы было неделимым и не содержало повторяющихся групп.
Неделимость означает, что ваше поле не должно делиться на более мелкие. Например, речь идет о базе сотрудников для отдела кадров. Будет ли поле "Фамилия" неделимым? Ясно, что будет, куда же его еще делить. А поле "ФИО"? Тут все зависит от контекста - если вы не предполагаете использовать потом экзотические запросы типа "Выбрать всех Александровичей", то тоже можно считать его неделимым. А вот адрес лучше разделить на улицу, дом и квартиру, т.е. на 2 или 3 поля, тогда вам будет проще выбрать всех живущих на одной улице. Хотя, можете и его считать неделимым, если адрес у вас - второстепенная информация. А вот с телефоном его объединять не стоит. Короче, как у Энштейна, все относительно ;)
Непосторяемость означает, что мы не повторяем значение полей от одного поля к другому. Имеется ввиду не та ситуация, когда мы тупо указали несколько полей с одинаковыми значениями. Например вы делаете записную книжку. Какие поля вы туда внесете? Фамилия, имя, телефон, e-mail... А может несколько телефонов? В наше время почти у каждого есть и рабочий, и домашний, и мобильный... А у некоторых может быть несколько рабочих или несколько мобильных номеров. А сколько e-mail адресов можно себе завести я думаю каждый знает... Так сколько таких полей ввести? Правильнее всего будет создать еще одну таблицу с телефонами, а в нашей только поместить ссылку на нее. Тогда можно будет для каждого указать произвольное число телефонов.

Код Фамилия Адрес....
1 Иванов
2 Петров
Чей Какой Номер
1 Рабочий 1 23-45-67
1 Рабочий 2 23-45-98
1 Домашний 11-34-98
2 Домашний 45-09-87
... ... ...

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

Вторая нормальная форма

Вторая нормальная форма требует, во-первых соответствия первой нормальной форме. А во-вторых, чтобы каждая строка таблицы однозначно и неизбыточно определялась первичным ключем. Здесь два варианта - простой и очень простой.
Первый - если у нас накладные нумеруются каждый день начиная с первой, то номер не может однозначно определить накладную, и не может быть первичным ключем. А номер вместе с датой - может. Можно поискать первичные ключи среди имеющихся полей (номер пасспорта в базе сотрудников), а можно попытаться объединить несколько, пытаясь обеспечить уникальность.
Очень простой вариант - создаем поле-счетчик и указываем его в качестве первичного ключа. Все, требование выполнено.

Третья нормальная форма

Третья нормальная форма требует, во-первых, соответствия второй (а та - первой). А во-вторых - чтобы значение любого поля, не входящего впервичный ключ не зависело от других полей, не входящих в первичный ключ. Поясним.
Ну, от первичного ключа все зависят, потому такие оговорки. Что означает остальное? Если у нас есть ведомость начисления зарплаты:

Таб № Фамилия Должность Сумма
1 Иванов Директор 100
2 Петров его зам 90

то поля "Фамилия" и "Должность" - лишние, потому, что мы эти данные можем взять из таблицы для отдела кадров. То есть при печати эти поля будут, а вот в бази их хранить не следует.

Нормальная форма Бойса-Кодда, четвертая и пятая нормальные формы

Знаете как звучит определение нормальной формы Бойса-Кодда, приведенное у Дейта? Цитирую: "Отношение находится в нормальной форме Бойса-Кодда тогда и только тогда, когда каждая нетривиальная и неприводимая слева функциональная зависимость обладает потенциальным ключом в качестве детерминанта" или "Отношение находится в нормальной форме Бойса-Кодда тогда и только тогда, когда детерминанты являются потенциальными ключами". Не понятно? Объясняю.
Считается что первых трех нормальных форм достаточно для большинства практических применений. Помните, я говорил. что надо знать меру? Пора останавливаться. Если вы собрались писать диссертацию на эту тему, поищите серьезную литературу. Но если вы только осваиваете программирование баз данных - не забивайте себе голову.

Где пора остановиться?

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

Индексирование

Сейчас я хочу обратить ваше внимание на правильную стратегию индексирования. Тема не очень сложная, ее мы рассмотрим в качестве отдыха после нормализации ;)

Индекс по первичному ключу позволяет быстро найти нужную запись. Это пожалуй самый быстрый способ перейти к нужной записи. Кроме того он незаменим при связывании таблиц. Есть и другие достоинства у индексов по первичным ключам. Так что возьмем за правило - каждой таблице по первичному ключу.

Delphi умеет использовать существующие индексы при поиске или сортировке. SQL Server тоже. А вот если приходится искать по неиндексированному полу, SQL Server переходит в режим сканирования. То есть просто пробегает по каждой записи. Попробуйте однажды поискать слово в словаре не по алфавиту, а просматривая все слова, пока не встретите нужное. Представили? А вот был бы индекс - все было бы в сотни или тысячи раз быстрее. Возьмем за правило - поля, по которым часто приходится искать, должны быть проиндексированы

При каждом обновлении таблицы индексы тоже модифицируются. Если их слишком много, это займет много времени. Понятие "слишком" сильно зависит от СУБД, но обычно считается, что 5 индексов на таблицу - нормально. Больше не стоит. Правило - не загромождайте таблицу лишними индексами

При связывании таблиц нам нужно, чтобы при перемещении по главной таблице записи в подчиненной искались как можно быстрее. Поле по которому мы связываем таблицы называется внешним ключем. Хотя оно и называется "ключем", индекс по нему не нужен - никто по нему не ищет. Итак следующее правило - индексы для внешних ключей как правило не нужны

Наблюдение последнее - все что описано выше, и о нормализации, и о правилах индескирования соблюдать совсем не сложно. В моем десятилетнем опыте работы с базами данных не было случая, чтобы мне не удалось их соблюсти. Чаще всего это происходит само собой. Так что, если кто успел испугаться - расслабьтесь ;)

Понятия базы данных, отношения, ключи и др.

Для начала нам следут разобраться с понятиями - весьма модная нынче тема. Я имею ввиду разборки и понятия. ;) Нам следует убедиться, что мы будем правильно понимать друг друга и называть одинаковые вещи одинаковыми именами. Поэтому определимся с терминами. В нашем случае мы будем рассматривать упрощенный набор терминов. Вообще, реляционная теория в чистом виде содержит очень много терминов и понятий. Но в реальных системах управления базами данных (СУБД) никто никогда ее полностью не придерживался. Всегда что-то упрощается, что-то добавляется свое. Так как мы рассматриваем не голую теорию, а реально работающие Delhi, или MS SQL Server, то нас интересует теория, которая относится именно к ним. Для желающих изучить теорию реляционных баз данных в чистом виде могу порекомендовать книжку "Введение в системы баз данных." Автор К. Дж. Дейт


Таблица

Таблица для нас - совокупность строк и столбцов. Почти полная аналогия с таблицами на бумаге. Важные уточнения: Каждый столбец должен иметь имя, уникальное в пределах этой таблицы. А строки, в теории баз данных, могут следовать в любом порядке, и не имеют номеров. Хотя Delphi, FoxPro и другие добавляют к каждой строке номер, но при выборке данных в SQL, вы его, в общем случае, не получите. Поэтому к каждой строке принято добавлять какой-нибудь идентификатор, для того, чтобы потом можно было легко найти ее. Подробнее см. ключи в этом же уроке.


Отношение

В реляционной теории баз данных, разделяют понятия таблицы и отношения. Например, в отношении не оговаривается порядок столбцов, а только их набор. Еще для отношений определены ограничения типа невозможности содержать две совершенно одинаковые строки. Различия мы сформулируем в одной фразе: Отношение - довольно абстрактный вид объекта, а таблица - его конкретное изображение. Поэтому мы, в дальнейшем, для упрощения, будем считать, что это одно и то же. Но важно не забывать, что в чистой теории баз данных - это разные понятия.


Ключи

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

Потенциальные ключи.Потенциальным ключом будем называть такую комбинацию столбцов, которая обладает следующими свойствами:

  • УникальностьюВ таблице нет двух разных строк с одиноковыми значениями в нашем потенциальном ключе.
  • Неизбыточностью.Нельзя убрать один из столбцом из ключа, так, чтобы он не потерял уникльности.

Рассмотрим, например, такую таблицу:

№ паспорта Фамилия Имя Отчество Должность
123456 Иванов Иван Иванович Директор
234567 Петров Петр Иванович Его зам
345678 Сидорова Мария Ивановна Секретарша

Табличка у нас простая и небольшая. Но нам хватит.
В данной таблице в качестве потенциального ключа можно рассматривать любой столбец. Но она у нас будет расширяться, так что будем смотреть в будущее.

Понятно, что отчество не может быть потенциальным ключом - есть совпадения. Фамилия - может, если только мы не планируем появления новых строк в таблице. Можно взять комбинацию фамилии и должности, врядли у нас будет два директора-однофамильца. Номер паспорта также подходит на роль потенциального ключа. Я думаю вы поняли мою мысль - к каждой конкретной таблице потенциальнх ключей может быть много. Выбор потенциального ключа - дело программиста. Тот же номер паспорта может не подойти, если мы ожидаем кого-нибудь с поддельным паспортом ;) Выбор делается каждый раз заново для каждой ситуации.

Первичные ключи.Итак с потенциальными ключами определились. Первичный ключ - это один из потенциальных ключей. Тот, который нам больше понравится. Вам какой больше нравиться? В реальной ситуации, новичок выберет номер паспорта. А что выберет профессионал? Профессионал добавит еще одно поле-счетчик, которое будет содержать уникальное для каждой записи значение. В Delphi такой тип поля называется AutoIncrement, в SQL Server есть целых 2 варианта - TimeStamp и свойтсво Identity поля. Подробнее этот момент мы рассмотрим в уроках по в взаимодействию с SQL Server'ом. Про полезность введения дополнительного поля, так называемого "суррогатного ключа", можно почитать здесь. Мы ведь собрались стать профессионалами? Вот и поучимся у умных людей.
Лиричекое отступление - умный человек, а тем более профессионал никогда не скажет "Я и так все знаю, ничему меня не научишь". Потому что он знает - всегда есть чему учиться.

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

Внешние ключи.Эта тема тесно связана со следующей - "Некоторые правилами построения баз данных" В частности с понятием нормализации Это будет потом, а сейчас только некоторые моменты.
Когда мы создаем какую-нибудь базу данных, например для начисления зарплаты, нам не удобно всех работников упоминать в одной таблице. Если, например, какой-нибудь из них упоминается там не один раз (зарплата, премия, надбавки, снятия, налоги и пр.), то при изменении его/ее фамилии надо будет пробежаться по всем строкам, и поменять все вхождения. Это неудобно. Есть и другие поводы разделить такую таблицу, о них читайте в следующем уроке, а пока просто примем как факт необходимость ее разделения. Так вот, имеем две таблицы:

Код работника Вид движения Сумма
1 Оклад 100
1 Премия 30
1 Налоги -25
2 Оклад 90
... ... ...
Код работника Фимилия Имя Отчество
1 Иванов Иван Иванович
2 Петров Петр Иванович
3 Сидорова Мария Ивановна

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

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

База данных.Ну вот, наконец, и добрались. А что же такое база данных? Базой данных мы будем называть совокупность таблиц, индексов, хранимых процедур, триггеров и всего остального, что касается нашего проекта. В Access'е, например, так вообще все это в одном файле хранится
Ладно, с терминологией вроде определились.

Инструменты от Borland. Инструменты для работы с базами данных, поставляемые вместе с Delphi.

Для работы с базами данных нам понадобятся инструменты. Во-первых, это сам Delphi. ;) Во-вторых это Database Desktop, с помощью которого можно создать таблицу и задать ее свойства. В-третьих это SQL Explorer, который позволяет просматривать данные в таблицах, как подряд, так и выборочно. В третьих это SQL Monitor, позволяющий рассмотреть в деталях, как выполняются наши запросы к ODBC. ODBC мы будем использовать при подключении в MS SQL Server'у, тогда и воспользуемся им. И, наконец, BDE Administrator и DataPump, которыми мы будем пользоваться довольно-таки редко, но знать о них необходимо.

Database Desktop

Database Desktop - программа, с помощью которой мы и будем создавать таблицы Paradox, а так же менять их структуру в последствии. Вообще, Database Desktop применяется не только для этого, но обо всем по порядку.

Для того чтобы создать таблицу следует выбрать пункт File ->New -> Table. При этом вас спросят, какого типа таблицу вы собираетесь создавать (Paradox 7). Нажмите OK, и приступим к собственно созданию таблицы. Первыми у нас будут идти поля, составляющие первичный ключ. Эти поля всегда располагаются в начале таблицы. Чтобы указать, что поле входит в первичный ключ надо в свойстве Key этого поля поставить <*>. Это можно сделать нажатием пробела, когда курсор установлен в этот столбце, или правой кнопки мыши. Рекомендую для первичного ключа создавать специальное поле типа Autoincrement - так называемый <суррогатный ключ>. Преимуществ у этого подхода несколько. Возможно, конечно, создание таблицы вообще без ключей и индексов, но в дальнейшем они нам пригодятся, так что следует все-таки их (ключевые поля) создать.

Итак, первым вводим ключевое поле. Можем назвать его KeyField, или по другому, кому как нравится. Тип ему поставим "+", т.е. Autoincrement. Чтобы указать тип поля можно нажать пробел. Выпадет список всех возможных значений типа. У каждого есть горячая клавиша. В дальнейшем удобнее пользоваться именно ими.

Для поля этого типа длина не задается, поэтому при нажатии Tab мы перескакиваем сразу в колонку Key. Нажмем в ней пробел, указав, таким образом, что это поле входит в первичный ключ.

Следующим полем пусть у нас будет символьное. В колонке Type будет отображено <A> (от &ltAlpha>), а не <S> (от &ltString>), как можно было бы подумать. &ltS> означает &ltShort>, т.е. короткое целое, не путайтесь. Для символьного поля уже следует задать длину в колонке Size. Строк переменной длины не предусмотрено, поэтому следует сразу указать необходимое число символов. В дальнейшем увеличение длины поля может вызвать неудобства в виде перекомпиляции проекта (это зависит от способа обращения к полям). Это связано с тем, что Delphi изменение длины символьного поля рассматривает как изменение типа. Посмотрите на рисунок, здесь показана как раз эта стадия создания таблицы.

Вообще, названия типов говорят сами за себя, подробнее рассматривать, наверное, смысла нет. Добавлю только, из собственного опыта, что пользоваться полем типа <Money> неудобно. Оно требует к значению, которое вы собираетесь записать в такое поле, приписывать знак денежной единицы, причем такой, как определен в региональных настройках Windows.

После того, как мы создали структуру таблицы, самое время указать свойства языка. Для этого в справа вверху в выпадающем списке выбираем <Table language>. Нажмем <Modify>. Укажем значение Pdox ANSI Cyrillic. Это позволит нам правильно видеть и сортировать русскоязычные текстовые поля в таблице.

Создать вторичные индексы можно, выбрав в выпадающем списке, справа вверху пункт <Secondary Indexes> Они пригодятся нам при поиске и сортировке. Однако не стоит ими чрезмерно увлекаться: каждая операция записи данных в таблицу влечет за собой изменение индексов, при слишком большом их количестве и большой таблице это может заметно замедлить работу.

Среди других возможностей отметим защиту паролем ("Password security"), подстановку значений из зависимых таблиц, ("Table Lookup"), просмотр списка зависимых таблиц ("Dependent tables") и контроль ссылочной целостности ("Referential Integrity"). О последнем пункте подробнее. Когда у вас есть главная и подчиненная таблицы возможна ситуация, когда при удалении записей из одной таблицы, другая окажется без связанных значений. Контроль ссылочной целостности позволяет отследить такую ситуацию и запретить удаление. Подробнее об этом здесь.

Еще одна возможность Database Desktop - конструирование таблицы не только вышеописанным способом, но и с помощью SQL запросов, выбрав в меню File ->New -> SQL File. Подробнее о создании таблиц с помощью SQL запросов см. соответствующий урок.

Database Desktop для нас полезен также своим меню "Tools". Здесь можно создать псевдоним "Alias Manager:" (см. BDE Administrator), скопировать содержимое одной таблицы в другую "Utilities->Copy", очистить содержимое таблицы "Utilities->Empty table", просмотреть ее структуру "Utilities->Info structure" и другие возможности.

Примечательна возможность выборки данных в Database Desktop - QBE или Query by example - запрос на базе примера. Мастер позволит вам выбрать таблицу, на базе которой создается запрос, поля, значения которых должны попасть в результирующую таблицу, при необходимости объединить несколько таблиц, и все это просто щелкая мышкой. Однако если мы хотим считать себя профессионалами, знание SQL необходимо. QBE нам в этом поможет - то что мы нащелкали мышкой преобразуются в команды SQL, которые мы можем просмотреть нажав кнопку "Show SQL" на панели инструментов. Но для выборки данных мы чаще будем использовать


SQL Explorer

SQL Explorer. Это наиболее универсальный и часто используемый инструмент при работе с базами данных. С его помощью можно не только создавать псевдонимы БД, но и просматривать и, при необходимости, редактировать их содержимое. В окне SQL Explorer расположены главное меню, инструментальная панель и два рабочих поля -левое в закладками Database и Dictionary и правое с закладками Definition, Data и Enter SQL. Главное меню и инструментальная панель служат для управления утилитой и ее настройки. Левое поле в режиме отображения Database позволяет просматривать уже созданные псевдонимы и создавать новые, редактировать и уничтожать их. Здесь же можно просмотреть структуру БД, связанной с конкретным псевдонимом: входящие в нее таблицы, домены, представления, хранимые процедуры и т.д. В режиме отображения Dictionary левое поле открывает доступ к словарям БД. Закладки правого поля меняются в зависимости от того, какой компонент выбран в левом поле. В режиме Definition оно отображает определение или параметры выбранного компонента (таблицы, поля, хранимой процедуры и т.п.). В режиме Data можно увидеть содержимое таблицы, выбранной в левом окне. Режим Enter SQL предназначен для ввода SQL-операторов, их выполнения и отображения результатов выполнения. Рассмотрим некоторые, важные для нас, пункты меню SQL Explorer:
Dictionary - обслуживает словари, т.е. хранилища информации о базах данных и существующих в них объектах (таблицах, ограничениях, ссылочных целостностях и т.п.):

  • Select - открывает диалоговое окно Select a Dictionary для выбора одного из доступных словарей. Выбранный словарь становится активным словарем, доступ к объектам которого открывает левое поле в режиме Dictionary.
  • Register - заносит текущий словарь в список доступных словарей окна Select a Dictionary.
  • Unregister - удаляет регистрацию текущего словаря из списка Select a Dictionary.
  • New - создает новый словарь.
  • Delete - уничтожает текущий словарь.
  • Import From Database - импортирует в текущий словарь сведения об объекте.
  • Import From File - добавляет в текущий словарь сведения из файла, созданного опцией Export To File. Опция полезна для переноса словарей на машину другого разработчика.
  • Export To File - записывает в файл данные из текущего словаря.

Options определяют некоторые параметры утилиты:

  • Query - задает ограничители строк, предложений, комментариев в тексте запроса, а также определяет, будет ли возвращаемый НД изменяемый.
  • Transaction Isolation - устанавливает уровень разграничения транзакций.
  • Word Wrap - если отмечена, редактор ввода переносит текст запроса на новую строку, если очередное слово не умещается в рамке окна.
  • Show Confirmations - определяет, будет ли появляться диалоговое окно для подтверждения вносимых изменений.
  • Show Warnings - указывает, будет ли появляться диалоговое окно с предупреждением о возможной потере данных. Данные могут быть потеряны, например, при перемещении столбца на новое место, так как в этом случае запрос должен быть повторен с указанием нового порядка следования полей.
  • Sync Pages - переносит схемную информацию из выбранной БД в выбранный словарь при переключении с закладки Database на закладку Dictionary. Перед переносом появится диалоговое окно для подтверждения операции

С помощью SQL Explorer можно создавать новые псевдонимы БД ("Object->New"), изменять их параметры и уничтожать. Однако в отличие от BDE Administrator, утилита SQL Explorer не может изменить свойства всех однотипных псевдонимов или сделать общесистемные установки форматов.

Если раскрыть дерево метаданных для какой-нибудь базы данных, то можно увидеть все или некоторые (зависит от типа БД) из таких узлов:

  • Domains - домены;
  • Tables - таблицы;
  • Views - виртуальные таблицы (представления);
  • Procedures - хранимые процедуры;
  • Functions - функции, определенные пользователем;
  • Generators - генераторы;
  • Exceptions - исключения;
  • Blob Filters - BLOB-фильтры.

В состав метаданных локальных БД входят только таблицы.

Открыв, например, дерево "таблицы", можно увидеть список таблиц, выбрать какую-нибудь можно щелкнув по ней мышкой. При этом в правом окне отобразится детальная информация об этой таблице, такая как: тип, язык, Версия, дата и время последней модификации, и др., в зависимости от типа таблицы. Перейдя на закладку "Data" можно увидеть содержимое этой таблицы, а на закладке "Enter SQL" ввести SQL-запрос и выполнить его. Этой возможностью мы будем часто пользоваться в дальнейшем.


BDE Administrator

BDE Administrator предназначен для создания псевдонимов баз данных и настройки их свойств. В начале урока я упомянул, сто пользоваться им нам придется редко. Это потому, что, обычно, им приходится пользоваться один-два раза за все время разработки проекта.

Создать псевдоним (Alias, в терминах Delphi) можно выбрав в меню Object пункт New. В появившемся окне выбрать тип базы данных - STANDARD для баз данных формата Paradox (.db) и dBase/FoxPro/Clipper (.dbf), INTRBASE для баз данных Interbase и т.д. Обратите внимание, что в этот список также включены все установленные в вашей системе драйвера ODBC. А так же на то, что если у вас уже есть настроенные ODBC-соединения, то они так же будут отображены в списке псевдонимов. Т.е. налицо полная поддержка ODBC через DBE. О том, что нам это дает, мы поговорим позже.

После задания типа базы данных надо ввести имя псевдонима в левом окне, и задать свойства в правом окне. Набор свойств зависит от выбранного типа базы данных. Для простейшего случая - локальная таблица Paradox - здесь только путь, где будут располагаться наши таблицы. Это позволяет нам при переносе программы расположить ее в любой папке, а потом только указать путь к ней в BDE Administrator. Но это уже тогда, когда вы закончите разработку и будете устанавливать свой проект клиенту. А до этого еще далеко:

Заметим еще, что создать псевдоним можно не только с помощью BDE Administrator'а, но и с помощью Database Desktop'а и SQL Explorer'а.

В левом окне на закладке Configuration можно найти еще одно дерево с параметрами. На этот раз речь идет не о параметрах подключения, а о параметрах BDE.

В узле Drivers этого дерева настраиваются параметры драйверов баз данных, как встроенных в BDE (Native), так и заимствованных из ODBC.

Рассмотрим параметры драйвера Paradox, так как этих таблицах мы будем рассматривать однопользовательский подход к проектированию баз данных:

  • NET DIR - Местонахождение файла PDOXUSRS.NET. Для файл-серверной архитектуры необходимо, чтобы он указывал на сетевой, доступный всем пользователям, диск.
  • LangDriver - Название таблицы символов, определяющая набор символов, используемый в таблице и порядок их сортировки. Для русского языка следует установить Pdox ANSI Cyrillic
  • Level - номер версии Paradox, в формате которого BDE создает временные таблицы. Ставим 7
  • Block Size - размер блока, выделяемого за раз при заполнении таблиц. Для Level =7 возможные значения будут 1024, 2048, 4096, 16384 и 32768. Оставим 2048, по умолчанию.
  • Fill Factor - на сколько процентов должна быть заполнена таблица данными, чтобы BDE выделил еще блок. Уменьшение этого параметра повысит быстродействие, но увеличит размеры индексом. Оставим 95, по умолчанию.
  • STRICTINTEGRTY - определяет, будет ли BDE следить за ссылочной целостностью. Оставим TRUE.

К MS SQL Server'у можно подключиться и через BDE, и через ODBC. Рассмотрим оба варианта
Некоторые параметры драйвера MSSQL

  • BATCH COUNT - количество записей, которое накапливает BDE перед сбросом на сервер. При явном завершении транзакции данные будут сброшены не дожидаясь накопления.
  • BLOB EDIT LOGGING - включает/выключает регистрацию изменений полей BLOB (Binary Large Object) в журнале транзакций SQL Server'a. Установка этого параметра в False выключит регистрацию и увеличит производительность, однако это потребует также изменения настройки SQL Server'a, в частности установки параметра BULKCOPY в ON. Оставим этот параметр пока в TRUE.
  • DATABASE NAME - имя базы данных на сервере, к которой мы собираемся подключаться.
  • CONNECT TIMEOUT - количество секунд, которое BDE ожидает ответа сервера. Если за это время соединение установить не удалось, выдается сообщение о невозможности связаться с сервером.
  • MAX QUERY TIME - время в секундах, в течении которого ожидается выполнение запроса. Если запрос выполняется дольше, то он считается зависшим и ожидание прекращается. Такая ситуация может возникнуть при плохой связи, перегрузке сервера, а как же из-за мертвых блокировок.
  • LANGDRIVER - Название таблицы символов, определяющая набор символов, используемый в таблице и порядок их сортировки. Для русского языка следует установить Pdox ANSI Cyrillic
  • OPEN MODE - режим открытия базы данных. Можно поставить в READ ONLY, лишив пользователя возможности что-либо изменить в данных. На самом деле этот способ подойдет только как экстренный и только против чайников.
  • SQLPASSTHRU MODE - пожалуй важнейший для технологии клиент-сервер режим, определяющий, каким образом происходит взаимодействие BDE с сервером. Passthrough SQL - операторы SQL, выполняемые при помощи компонента TQuery. Возможные значения:
    1. SHARED AUTOCOMMIT - Неявное создание и подтверждение транзакций, при выполнении любых операций с базами данных. Просто выполняется TDatabase.StartTransaction перед каждым обращением к серверу и TDatabase.Commit после. Passthrough SQL и команды BDE используют одно и то же соединение с сервером.
    2. SHARED NOAUTOCOMMIT - Неявные транзакции стартуют как и в предыдущем случае, но подтверждение необходимо производить самому. Passthrough SQL и команды BDE используют одно и то же соединение с сервером.
    3. NOT SHARED - Passthrough SQL и команды BDE не могут использовать одно и то же соединение с сервером. Обновляемые запросы не поддерживаются псевдонимами БД, для которых установлен этот режим.
  • SQLQRYMODE - режим выполнения запросов SQL. Возможные значения:
    1. NULL - для доступа к БД запросы сначала посылаются серверу, а затем, если сервер отказал в обработке, обрабатываются локально
    2. SERVER - только серверная обработка запросов
    3. LOCAL - только локальная обработка

Некоторые параметры драйвера SQL Server (ODBC)

  • BATCH COUNT - количество записей, которое накапливает BDE перед сбросом на сервер. При явном завершении транзакции данные будут сброшены не дожидаясь накопления.
  • DATABASE NAME - имя базы данных на сервере, к которой мы собираемся подключаться.
  • CONNECT TIMEOUT - количество секунд, которое BDE ожидает ответа сервера. Если за это время соединение установить не удалось, выдается сообщение о невозможности связаться с сервером.
  • MAX QUERY TIME - время в секундах, в течении которого ожидается выполнение запроса. Если запрос выполняется дольше, то он считается зависшим и ожидание прекращается. Такая ситуация может возникнуть при плохой связи, перегрузке сервера, а как же из-за мертвых блокировок.
  • LANGDRIVER - Название таблицы символов, определяющая набор символов, используемый в таблице и порядок их сортировки. Для русского языка следует установить Pdox ANSI Cyrillic
  • ODBC DSN - Название псевдонима ODBC, из которого был взят этот псевдоним BDE
  • OPEN MODE - режим открытия базы данных. Можно поставить в READ ONLY, лишив пользователя возможности что-либо изменить в данных. На самом деле этот способ подойдет только как экстренный и только против чайников.
  • SQLPASSTHRU MODE - пожалуй важнейший для технологии клиент-сервер режим, определяющий, каким образом происходит взаимодействие BDE с сервером. Passthrough SQL - операторы SQL, выполняемые при помощи компонента TQuery. Возможные значения:
    1. SHARED AUTOCOMMIT - Неявное создание и подтверждение транзакций, при выполнении любых операций с базами данных. Просто выполняется TDatabase.StartTransaction перед каждым обращением к серверу и TDatabase.Commit после. Passthrough SQL и команды BDE используют одно и то же соединение с сервером.
    2. SHARED NOAUTOCOMMIT - Неявные транзакции стартуют как и в предыдущем случае, но подтверждение необходимо производить самому. Passthrough SQL и команды BDE используют одно и то же соединение с сервером.
    3. NOT SHARED - Passthrough SQL и команды BDE не могут использовать одно и то же соединение с сервером.Обновляемые запросы не поддерживаются псевдонимами БД, для которых установлен этот режим.
  • SQLQRYMODE - режим выполнения запросов SQL. Возможные значения:
    1. NULL - для доступа к БД запросы сначала посылаются серверу, а затем, если сервер отказал в обработке, обрабатываются локально
    2. SERVER - только серверная обработка запросов
    3. LOCAL - только локальная обработка

Отметим, что большинство параметров менять нет никакой необходимости. Значения по умолчанию нас вполне устроят.

Последнее приложение, которое мы рассмотрим в рамках этого урока - DataPump. Этот инструмент пригодится для переноса большого количества данных из одного источника в другой. Он и переводится с английского как "насос данных". Например, проект перерос сам себя, понадобились возможности технологии "Клиент/сервер". Вы модернизируете ваш проект, а затем вам надо не потерять наработанные данные. Вот в этом случае вам и пригодится DataPump. Или в какой другой задаче переноса большого количества данных. Мастер проведет вас последовательно по всем шагам, расспросит о ваших пожеланиях, позволит вам выбрать место, откуда брать и куда помещать данные, какие создавать индексы и т.п. Интерфейс весьма прост и нагляден, так что не будем больше тратить на него время.