Delphi 3. Библиотека программиста

       

Простейший пример иерархических рекурсивных данных


Реляционная модель хорошо работает для базовых/подчиненных записей в пределах одной таблицы, если в ней существует лишь один уровень принадлежности — другими словами, если каждая запись либо принадлежит другой записи, либо владеет другой записью. В табл. 13.1 приведен список персонала (состоящий из начальников и подчиненных), который при одном уровне принадлежности можно было бы разделить на две таблицы.

Таблица 13.1. Простейший пример рекурсивных иерархических данных

Emp_ID Boss_ID Emp_Name Boss 1 <nil> Frank Eng Boss 2 <nil> Sharon Oakstein Boss 3 <nil> Charles Willings Staff 1 Boss 1 Roger Otkin Staff 2 Boss 1 Marylin Fionne Staff 3 Boss 1 Judy Czeglarek Staff 4 Boss 2 Sean O'Donhail Staff 5 Boss 3 Karol Klauss Staff 6 Boss 3 James Riordan

В табл. 13.2 перечислены все значения свойств для двух наборов компонентов TTable, TDataSource и TDBGrid, связанных с одной и той же физической таблицей. Первый набор свойств предназначен для вывода родительских записей, а второй — для вывода дочерних записей, принадлежащих текущему выбранному родителю. Свойства MasterSource и MasterFields подчиненного компонента TTable автоматически ограничивают его набором записей, подчиненных текущей записи родительской таблицы.

Таблица 13.2. Значения свойств для отображения записей-родителей и записей-детей

Свойства компонентов для родительских записей

Table1.TableName = 'employees'

Table1.IndexFieldName = 'Boss_ID;Emp_ID'

Table1.SetRange([''],['']);



DataSource1.DataSet = 'Table1'

DBGrid1.DataSource = 'DataSource1'

Свойства компонентов для дочерних записей

Table2.TableName = 'employees'

Table2.IndexFieldName = 'Boss_ID;Emp_ID'

Table2.MasterSource = 'DataSource1'

Table2.MasterFields = 'Emp_ID'

DataSource2.DataSet = 'Table2'

DBGrid2.DataSource = 'DataSource2'

Чтобы ограничить родительский компонент TTable и не выводить в нем дочерние записи, задайте условие-фильтр, пропускающий лишь записи с пустым полем Boss_ID (это и есть родительские записи).

Замечание

Вместо свойства Filter можно использовать метод SetRange. С помощью этого метода мы заставим Table1 выводить только записи о начальниках (то есть записи с Boss_ID = nil). Вызов Table1.SetRange можно включить в обработчик Table1.AfterOpen, чтобы метод гарантированно вызывался независимо от того, оставлена ли таблица открытой в режиме конструирования или она открывается во время выполнения.

На рис. 13.2 изображена форма Delphi с двумя компонентами TDBGrid, свойства которых настроены в соответствии с табл. 13.2. Слева перечислены записи о начальниках (родительские записи), справа — записи о подчиненных (дочерние записи). Все эти записи взяты из одной физической таблицы.

При каждом изменении DataSource1 (связанного с Table1) происходит автоматическое обновление Table2, как будто выполняется код из листинга 13.1.

Рис. 13.2. Отношения master/detail между записями одной таблицы

Листинг 13.1. Эквивалентный код для автоматического выбора записей
при изменении состояния MasterSource

procedure TForm1.DataSource1DataChange (Sender : TObject; Field : TField); begin if (Field = nil) or (Field.FieldName = 'Emp_ID') then Table2.SetRange([Table1.FieldByName ('Emp_ID').AsString]), [Table1.FieldByName('Emp_ID').AsString]); end;

Замечание

Этот способ сработает лишь в том случае, если в Table2 имеется индекс для поля Boss_ID, чтобы в одной таблице можно было отфильтровать все записи, где в Table1.Boss_ID находится пустая строка, а в другой — записи, для которых выполняется условие Table2.Boss_ID = Table1.Emp_ID. Индекс может содержать дополнительные поля, определяющие порядок записей в отфильтрованном наборе. В нашем случае в Table2 выводятся лишь подчиненные одного начальника, причем их список сортируется по полю Emp_ID. Если таблицей управляет SQL Server, то все столбцы, не относящиеся к категории BLOB (больших двоичных объектов, Binary Large Objects), считаются индексированными, хотя при использовании столбцов, не имеющих физических индексов, производительность работы снижается. Свойство Filter не требует наличия индексов, но по возможности старается использовать их.



Содержание раздела