Любой разработчик учетных систем сталкивается с задачей анализа больших массивов данных. Превращение «информационной каши» в структурированные отчеты критически важно для принятия управленческих решений. Это касается, например:
Например, OLAP-кубы (FastCube) позволяют решать эти задачи быстро, наглядно и без написания сложных SQL-запросов. В отличие от двухмерных таблиц, многомерные сводные таблицы дают возможность рассматривать данные под разными углами (срезами) в режиме реального времени.
В этой статье пошагово разберём процесс создания аналитического приложения в Delphi с использованием компонентов TfrGrid (для исходных данных) и TfcxSliceGrid (для построения сводки).
Прежде чем строить аналитику, необходимо увидеть исходную выборку. Для примера возьмем данные машиностроительного предприятия за 2025 год.
Нам понадобится компонент TfrGrid, он входит в состав FastReport VCL и доступен бесплатно в демоверсии.
Главная особенность компонента-сетки — прямое редактирование ячеек (изменение исходных данных в поле БД). В FastGrid это реализовано через автоматическую подстановку редакторов в зависимости от типа поля. Например:
Важно: на данном этапе мы получили интерфейс для просмотра и ручной коррекции исходных данных. Это ещё не конечный результат аналитики.
Теперь перейдем к главному инструменту — сводной таблице. Для этого используем компонент TfcxSliceGrid и привяжем его к тому же источнику данных, что и TfrGrid.
Для удобства работы с временными периодами необходимо настроить дробление даты:
Далее мы напишем обработчик события OnShow формы чтобы каждый раз при запуске не перетаскивать поля мышкой.
uses …, fcxTypes; procedure TForm1.FormShow(Sender: TObject); var LIndex: Integer; begin fcxSlice1.BeginUpdate; try // Добавление полей в область Фильтров fcxSlice1.PageContainer.AddFilterField(fcxSlice1.SliceFields.ItemByName['Machine'], 'Machine', 'Станок'); fcxSlice1.PageContainer.AddFilterField(fcxSlice1.SliceFields.ItemByName['Date'], 'Date', 'Дата'); fcxSlice1.PageContainer.AddFilterField(fcxSlice1.SliceFields.ItemByName['Department'], 'Department', 'Участок'); fcxSlice1.PageContainer.AddFilterField(fcxSlice1.SliceFields.ItemByName['Shift'], 'Shift', 'Смена'); fcxSlice1.PageContainer.AddFilterField(fcxSlice1.SliceFields.ItemByName['Operator'], 'Operator', 'Оператор'); fcxSlice1.PageContainer.AddFilterField(fcxSlice1.SliceFields.ItemByName['Product'], 'Product', 'Изделие'); fcxSlice1.PageContainer.AddFilterField(fcxSlice1.SliceFields.ItemByName['Product_Category'], 'Product_Category', 'Категория изделия'); fcxSlice1.PageContainer.AddFilterField(fcxSlice1.SliceFields.ItemByName['Work_Order'], 'Work_Order', 'Заказ изделия'); fcxSlice1.PageContainer.AddFilterField(fcxSlice1.SliceFields.ItemByName['Downtime_Reason'], 'Downtime_Reason', 'Причина простоя'); fcxSlice1.PageContainer.AddFilterField(fcxSlice1.SliceFields.ItemByName['Plant'], 'Plant', 'Цех'); // Добавление полей в область Строк fcxSlice1.YAxisContainer.AddDimension(fcxSlice1.SliceFields.ItemByName['Supervisor'], 'Supervisor', 'Мастер'); // Задание полей с данными LIndex := fcxSlice1.MeasuresContainer.AddMeasure(fcxSlice1.SliceFields.ItemByName['Planned_Qty'], 'Planned_Qty', 'План, шт', af_Sum); fcxSlice1.MeasuresContainer.Measures[LIndex].Visible := False; fcxSlice1.MeasuresContainer.AddMeasure(fcxSlice1.SliceFields.ItemByName['Produced_Qty'], 'Produced_Qty', 'Выпущено, шт', af_Sum); fcxSlice1.MeasuresContainer.AddMeasure(fcxSlice1.SliceFields.ItemByName['Defective_Qty'], 'Defective_Qty', 'Брак, шт', af_Sum); LIndex := fcxSlice1.MeasuresContainer.AddMeasure(fcxSlice1.SliceFields.ItemByName['Operating_Hours'], 'Operating_Hours', 'Время работы, ч', af_Sum); fcxSlice1.MeasuresContainer.Measures[LIndex].Visible := False; LIndex := fcxSlice1.MeasuresContainer.AddMeasure(fcxSlice1.SliceFields.ItemByName['Downtime_min'], 'Downtime_min', 'Простой, мин', af_Sum); fcxSlice1.MeasuresContainer.Measures[LIndex].Visible := False; LIndex := fcxSlice1.MeasuresContainer.AddMeasure(fcxSlice1.SliceFields.ItemByName['Unit_Cost'], 'Unit_Cost', 'Себестоимость', af_Sum); fcxSlice1.MeasuresContainer.Measures[LIndex].Visible := False; // Добавление полей в область Данных fcxSlice1.XAxisContainer.AddMeasuresField; finally // Перестроение таблицы происходит только здесь fcxSlice1.EndUpdate; end; end;
Использование BeginUpdate и EndUpdate критически важно для производительности, так как предотвращает многократные перерисовки интерфейса при добавлении каждого поля.
Сгенерировав отчет, мы можем мгновенно находить аномалии, просто перетаскивая поля мышью (Drag-and-Drop). Рассмотрим 3 примера того, как OLAP помогает в реальных задачах.
В полученной сетке выставляем следующие группировки: «Мастер» по оси Y, «Брак» по оси X.
Делаем вывод. Если у мастера Смирнова уровень брака подозрительно низкий, это сигнал к изучению его методов работы или повод для премирования. Данные подсказывают, где искать лучшие практики.
Меняем правила группировки. Теперь «Смена» — в оси Y; «План, Выпуск, Брак» — в оси X.
На этих данных делаем вывод, что третья смена показывает низкий план, но высокий уровень брака. Это может свидетельствовать о неэффективности организации труда в ночное время.
Ещё раз меняем параметры группировки. На этот раз «Тип станка» по оси Y, «Брак» по оси X.
Резюмируем. Если у конкретного типа станков значение брака существенно превышает средний показатель, необходима замена оснастки или ремонт.
FastCube позволяет строить множество срезов различной сложности:
Важные функции:
Связка FastGrid + FastCube в среде Delphi/Lazarus — это готовое решение для внедрения BI-аналитики в корпоративные приложения. Вы получаете:
Попробуйте все перечисленные функции в демонстрационной версии FastReport VCL на нашем сайте.