Отчет с двумя уровнями данных (master-detail) в Delphi/Lazarus на примере FastReport VCL / FMX

Отчет с двумя уровнями данных (master-detail) в Delphi/Lazarus на примере FastReport VCL / FMX

В этой статье я хочу вам рассказать о такой мощной возможности FastReport, как “многоуровневые отчёты” - можно сравнить их структуру с деревом “ствол, ветки потолще, из каждой растут потоньше, и так далее - вплоть до листиков” или со структурой предприятия “Отделы, подотделы, сотрудники”. Часто их называют “master-detail” или “главный-подчинённый” - и делаются такие отчёты из нескольких таблиц. В одной таблице список главных сущностей, в другой, связанной с первой, список подчинённых сущностей со ссылкой на первую - какой конкретно сущности из первой подчинена конкретная сущность из подчинённой. И так далее.

FastReport поддерживает вложенность до 6 уровней (можно и больше, используя объект "Вложенный отчет", но об этом позже). В реальных приложениях редко приходится печатать отчеты с большой вложенностью данных; как правило, ограничиваются 1-3 уровнями.

Пример разработки многоуровневого отчёта

Рассмотрим создание на примере двухуровневого отчета. Он будет содержать данные из таблиц Customer и Orders. Первая таблица – это список клиентов, вторая – список заказов, сделанных клиентами. Таблицы содержат данные следующего вида:

Customer:

CustNo       Company
1221          Kauai Dive Shoppe
1231          Unisco
1351          Sight Diver

Orders:

OrderNo       CustNo       SaleDate
1003            1351          12.04.1988
1023            1221          01.07.1988
1052            1351          06.01.1989
1055            1351          04.02.1989
1060            1231          28.02.1989
1123            1221          24.08.1993

Как видно, вторая таблица содержит список всех заказов, сделанных всеми компаниями. Чтобы получить список заказов, сделанных конкретной компанией, из таблицы следует отобрать записи, у которых поле CustNo = номеру выбранной компании. Отчет, построенный на этих данных, будет выглядеть следующим образом:

1221       Kauai Dive Shoppe
    1023       01.07.1988
    1123       24.08.1993
1231       Unisco
    1060       28.02.1989
1351       Sight Diver
    1003       12.04.1988
    1052       06.01.1989
    1055       04.02.1989

Приступим к созданию отчета. Создадим новый проект в Delphi, на форму положим два компонента TTable, компонент TDataSource, два компонента TfrxDBDataSet и один TfrxReport. 

Форма с компонентами

Подключаем данные из базы к объектам отчёта

Настроим компоненты следующим образом:

Table1:
DatabaseName = 'DBDEMOS'
TableName = 'Customer.db'
Table2:
DatabaseName = 'DBDEMOS'
TableName = 'Orders.db'
 
DataSource1:
DataSet = Table1
 
frxDBDataSet1:
DataSet = Table1
UserName = 'Customers'
 
frxDBDataSet2:
DataSet = Table2
UserName = 'Orders'
 

В дизайнере отчета подключим наши источники данных в окне "Отчет|Данные…". 

Подключение источников данных

Добавим на страницу бэнды "Данные 1 уровня"(master) и "Данные 2 уровня"(detail). И из панели данных (справа) вытащим на соответствующие бэнды поля таблиц (главной и подчинённой). Получится примерно так:

Шаблон отчёта

Обратите внимание – бэнд "Данные 1 уровня" должен располагаться выше! Если разместить его под бэндом "Данные 2 уровня", FastReport сообщит об ошибке при запуске отчета.

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

Вернемся к нашим источникам данных. У компонента Table2 установим свойство MasterSource = DataSource1. Таким образом мы установили связь "главный-подчиненный". Теперь надо задать условие фильтрации записей в подчиненном источнике. Для этого вызовите редактор свойства MasterFields у компонента Table2:

Редактор свойства MasterFields

Нам надо связать два поля CustNo в обоих источниках. Для этого выберите индекс CustNo в списке сверху, выберите поля и нажмите кнопку "Add". Связка полей переместится в нижнее окно. После этого закройте редактор кнопкой ОК.

При запуске отчета FastReport сделает следующее. Выбрав очередную запись из главной таблицы (Customer), он установит фильтр на подчиненную таблицу (Orders). В таблице останутся только те записи, которые удовлетворяют условию Orders.CustNo = Customer.CustNo. Т.е. для каждого клиента будут показаны только его заказы:

Итоговый отчёт

Аналогичным образом можно строить отчеты, содержащие до 6 уровней данных.