Обновление сводных таблиц и кэшей сводных таблиц в Aspose.Cells for C++

Введение

Обновление сводной таблицы редко является единственной операцией. За кулисами Aspose.Cells поддерживает многоуровневую цепочку данных, которая связывает исходные данные с отображаемыми значениями на рабочем листе. Понимание этой цепочки является ключом к выбору правильного API обновления для любой ситуации. Цепочка данных состоит из четырёх уровней:

  1. Источник данных — исходные диапазоны рабочего листа, запрос к базе данных или диапазон консолидации, где хранятся исходные значения.
  2. PivotCache — снимок исходных данных в памяти. Каждая сводная таблица построена поверх PivotCache; именно здесь все данные собираются и агрегируются.
  3. PivotTable — объект представления, который определяет поля строк, столбцов, значений и фильтров. PivotTable читает только из своего PivotCache, никогда напрямую из источника данных.
  4. Cells — ячейки Cells рабочего листа, в которые PivotTable отображает вычисленные значения и стили.

Из-за этой цепочки в Aspose.Cells существуют два основных пути обновления:

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

Быстрый старт

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

#include "Aspose.Cells.h"
using namespace Aspose::Cells;
using namespace Aspose::Cells::Pivot;
int main() {
    Aspose::Cells::Startup();
    Workbook wb;
    Worksheet worksheet = wb.GetWorksheets().Get(0);
    Cells cells = worksheet.GetCells();
    cells.Get(u"A1").PutValue(U16String("Fruit"));
    cells.Get(u"B1").PutValue(U16String("Year"));
    cells.Get(u"C1").PutValue(U16String("Amount"));
    cells.Get(u"A2").PutValue(U16String("grape"));
    cells.Get(u"B2").PutValue(2020);
    cells.Get(u"C2").PutValue(50);
    cells.Get(u"A3").PutValue(U16String("blueberry"));
    cells.Get(u"B3").PutValue(2020);
    cells.Get(u"C3").PutValue(60);
    cells.Get(u"A4").PutValue(U16String("kiwi"));
    cells.Get(u"B4").PutValue(2020);
    cells.Get(u"C4").PutValue(70);
    cells.Get(u"A5").PutValue(U16String("cherry"));
    cells.Get(u"B5").PutValue(2020);
    cells.Get(u"C5").PutValue(80);
    cells.Get(u"A6").PutValue(U16String("grape"));
    cells.Get(u"B6").PutValue(2021);
    cells.Get(u"C6").PutValue(90);
    cells.Get(u"A7").PutValue(U16String("blueberry"));
    cells.Get(u"B7").PutValue(2021);
    cells.Get(u"C7").PutValue(100);
    cells.Get(u"A8").PutValue(U16String("kiwi"));
    cells.Get(u"B8").PutValue(2021);
    cells.Get(u"C8").PutValue(110);
    cells.Get(u"A9").PutValue(U16String("cherry"));
    cells.Get(u"B9").PutValue(2021);
    cells.Get(u"C9").PutValue(120);
    int pivotIndex = worksheet.GetPivotTables().Add(u"A1:C9", u"E3", u"Pivot1");
    PivotTable pivotTable = worksheet.GetPivotTables().Get(pivotIndex);
    pivotTable.AddFieldToArea(PivotFieldType::Row, u"Fruit");
    pivotTable.AddFieldToArea(PivotFieldType::Column, u"Year");
    pivotTable.AddFieldToArea(PivotFieldType::Data, u"Amount");
    cells.Get(u"C2").PutValue(55);
    cells.Get(u"C5").PutValue(85);
    cells.Get(u"C9").PutValue(125);
    pivotTable.CalculateData();
    wb.Save(u"output.xlsx");
    Aspose::Cells::Cleanup();
    return 0;
}

Всё остальное в этой статье объясняет, когда следует выбрать более узкий API.

Требуемые директивы включения

Все примеры C++ в этой статье начинаются со следующих директив включения заголовков и пространств имён, поскольку типы сводных таблиц находятся в пространстве имён Aspose::Cells::Pivot:

  • #include <system/object.h>
  • #include "Aspose.Cells.h"
  • using namespace Aspose::Cells;
  • using namespace Aspose::Cells::Pivot;

Обновление всех сводных таблиц в рабочей книге

Когда вам нужно убедиться, что каждый кэш сводной таблицы и каждая сводная таблица в рабочей книге отражают самые последние исходные данные, самым простым и всеобъемлющим API является Workbook.RefreshAll(). Один вызов проходит по всей рабочей книге — обновляя каждый PivotCache из его источника, а затем пересчитывая каждую зависимую PivotTable. Это рекомендуемый подход для общих полных обновлений документа, где производительность не критична. В следующем примере создаётся рабочая книга с диапазоном источника Фрукт/Год/Сумма, создаётся одна сводная таблица, изменяются некоторые исходные значения, а затем с помощью RefreshAll() всё приводится в актуальное состояние одним вызовом.

#include "Aspose.Cells.h"
using namespace Aspose::Cells;
int main() {
    Aspose::Cells::Startup();
    Workbook workbook;
    Worksheet worksheet = workbook.GetWorksheets().Get(0);
    worksheet.GetCells().Get(u"A1").PutValue(u"Fruit");
    worksheet.GetCells().Get(u"B1").PutValue(u"Year");
    worksheet.GetCells().Get(u"C1").PutValue(u"Amount");
    worksheet.GetCells().Get(u"A2").PutValue(u"grape");
    worksheet.GetCells().Get(u"B2").PutValue(2020);
    worksheet.GetCells().Get(u"C2").PutValue(100);
    worksheet.GetCells().Get(u"A3").PutValue(u"blueberry");
    worksheet.GetCells().Get(u"B3").PutValue(2021);
    worksheet.GetCells().Get(u"C3").PutValue(150);
    worksheet.GetCells().Get(u"A4").PutValue(u"kiwi");
    worksheet.GetCells().Get(u"B4").PutValue(2020);
    worksheet.GetCells().Get(u"C4").PutValue(200);
    worksheet.GetCells().Get(u"A5").PutValue(u"cherry");
    worksheet.GetCells().Get(u"B5").PutValue(2021);
    worksheet.GetCells().Get(u"C5").PutValue(120);
    worksheet.GetCells().Get(u"A6").PutValue(u"grape");
    worksheet.GetCells().Get(u"B6").PutValue(2021);
    worksheet.GetCells().Get(u"C6").PutValue(180);
    worksheet.GetCells().Get(u"A7").PutValue(u"blueberry");
    worksheet.GetCells().Get(u"B7").PutValue(2020);
    worksheet.GetCells().Get(u"C7").PutValue(130);
    worksheet.GetCells().Get(u"A8").PutValue(u"kiwi");
    worksheet.GetCells().Get(u"B8").PutValue(2021);
    worksheet.GetCells().Get(u"C8").PutValue(220);
    worksheet.GetCells().Get(u"A9").PutValue(u"cherry");
    worksheet.GetCells().Get(u"B9").PutValue(2020);
    worksheet.GetCells().Get(u"C9").PutValue(140);
    int pivotIndex = worksheet.GetPivotTables().Add(u"A1:C9", u"E3", u"Pivot1");
    PivotTable pivotTable = worksheet.GetPivotTables().Get(pivotIndex);
    pivotTable.AddFieldToArea(PivotFieldType::Row, u"Fruit");
    pivotTable.AddFieldToArea(PivotFieldType::Column, u"Year");
    pivotTable.AddFieldToArea(PivotFieldType::Data, u"Amount");
    worksheet.GetCells().Get(u"C2").PutValue(300);
    worksheet.GetCells().Get(u"C5").PutValue(250);
    worksheet.GetCells().Get(u"C9").PutValue(400);
    worksheet.RefreshPivotTables();
    workbook.Save(u"output.xlsx");
    Aspose::Cells::Cleanup();
    return 0;
}

Обновление всех сводных таблиц на одном рабочем листе

Иногда требуется обновить только те сводные таблицы, которые находятся на одном конкретном рабочем листе — например, когда известно, что сводные таблицы на других листах не связаны и их не нужно трогать. Для этого случая Aspose.Cells предоставляет Worksheet.RefreshPivotTables(), который ограничен одним экземпляром Worksheet.

#include "Aspose.Cells.h"
using namespace Aspose::Cells;
int main() {
    Aspose::Cells::Startup();
    Workbook workbook;
    Worksheet worksheet = workbook.GetWorksheets().Get(0);
    // Запись строки заголовков Fruit / Year / Amount
    worksheet.GetCells().Get(u"A1").PutValue(u"Fruit");
    worksheet.GetCells().Get(u"B1").PutValue(u"Year");
    worksheet.GetCells().Get(u"C1").PutValue(u"Amount");
    // Запись 8 строк данных (строки 2-9, укладывающиеся в исходный диапазон A1:C9)
    worksheet.GetCells().Get(u"A2").PutValue(u"Grape");
    worksheet.GetCells().Get(u"B2").PutValue(2020);
    worksheet.GetCells().Get(u"C2").PutValue(100);
    worksheet.GetCells().Get(u"A3").PutValue(u"Blueberry");
    worksheet.GetCells().Get(u"B3").PutValue(2020);
    worksheet.GetCells().Get(u"C3").PutValue(200);
    worksheet.GetCells().Get(u"A4").PutValue(u"Kiwi");
    worksheet.GetCells().Get(u"B4").PutValue(2020);
    worksheet.GetCells().Get(u"C4").PutValue(300);
    worksheet.GetCells().Get(u"A5").PutValue(u"Cherry");
    worksheet.GetCells().Get(u"B5").PutValue(2020);
    worksheet.GetCells().Get(u"C5").PutValue(400);
    worksheet.GetCells().Get(u"A6").PutValue(u"Grape");
    worksheet.GetCells().Get(u"B6").PutValue(2021);
    worksheet.GetCells().Get(u"C6").PutValue(150);
    worksheet.GetCells().Get(u"A7").PutValue(u"Blueberry");
    worksheet.GetCells().Get(u"B7").PutValue(2021);
    worksheet.GetCells().Get(u"C7").PutValue(250);
    worksheet.GetCells().Get(u"A8").PutValue(u"Kiwi");
    worksheet.GetCells().Get(u"B8").PutValue(2021);
    worksheet.GetCells().Get(u"C8").PutValue(350);
    worksheet.GetCells().Get(u"A9").PutValue(u"Cherry");
    worksheet.GetCells().Get(u"B9").PutValue(2021);
    worksheet.GetCells().Get(u"C9").PutValue(450);
    // Добавление сводной таблицы с именем "Pivot1", размещённой в ячейке E3, с источником A1:C9
    int pivotIndex = worksheet.GetPivotTables().Add(u"A1:C9", u"E3", u"Pivot1");
    PivotTable pivotTable = worksheet.GetPivotTables().Get(pivotIndex);
    // Назначение полей: Fruit в строки, Year в столбцы, Amount в данные
    pivotTable.AddFieldToArea(PivotFieldType::Row, u"Fruit");
    pivotTable.AddFieldToArea(PivotFieldType::Column, u"Year");
    pivotTable.AddFieldToArea(PivotFieldType::Data, u"Amount");
    // Изменение свойства представления/макета — это изменение только отображения,
    // поэтому оно НЕ требует повторного чтения исходных данных через PivotCache.Refresh().
    pivotTable.SetRefreshDataOnOpeningFile(false);
    // CalculateData() повторно отрисовывает отображение ЭТОЙ сводной таблицы (данные + стиль) из
    // данных, уже хранящихся в PivotCache. Поскольку исходные данные не изменились,
    // обратное обращение к источнику не выполняется — пересчитываются только кэшированные значения
    // в ячейках листа.
    pivotTable.CalculateData();
    // Сохранение книги на диск
    workbook.Save(u"output.xlsx");
    Aspose::Cells::Cleanup();
    return 0;
}

Обновление одной сводной таблицы

Когда вам нужен детальный контроль над одной сводной таблицей, API на основе кэша предоставляет два варианта. Выбор между ними зависит от того, что именно изменилось: исходные данные или только параметры представления/макета самой сводной таблицы.

Исходные данные изменились — используйте PivotCache.Refresh()

Если исходные данные изменились, правильной точкой входа является pivotTable.GetPivotCache().Refresh(). Этот вызов перечитывает исходные данные в кэш, а затем пересчитывает каждую PivotTable, которая зависит от этого кэша.

Изменились только представление/макет — используйте CalculateData()

Если исходные данные не изменились, а были изменены только параметры представления или макета сводной таблицы (например, поле было перемещено в другую область или был переключён параметр обновления при открытии), нет необходимости обращаться к источнику данных. Кэш уже содержит правильные данные; необходимо пересчитать только отображаемую PivotTable. В этом случае pivotTable.CalculateData() является правильным выбором. В следующем примере изменяется свойство сводной таблицы, не связанное с источником, а затем вызывается CalculateData() для её повторного отображения из существующего кэша.

#include "Aspose.Cells.h"
using namespace Aspose::Cells;
using namespace Aspose::Cells::Pivot;
int main() {
    Aspose::Cells::Startup();
    Workbook wb;
    Worksheet sheet = wb.GetWorksheets().Get(0);
    sheet.GetCells().Get(u"A1").PutValue(u"Fruit");
    sheet.GetCells().Get(u"B1").PutValue(u"Year");
    sheet.GetCells().Get(u"C1").PutValue(u"Amount");
    sheet.GetCells().Get(u"A2").PutValue(u"Grape");      sheet.GetCells().Get(u"B2").PutValue(2020); sheet.GetCells().Get(u"C2").PutValue(1000);
    sheet.GetCells().Get(u"A3").PutValue(u"Blueberry");  sheet.GetCells().Get(u"B3").PutValue(2020); sheet.GetCells().Get(u"C3").PutValue(2000);
    sheet.GetCells().Get(u"A4").PutValue(u"Kiwi");       sheet.GetCells().Get(u"B4").PutValue(2020); sheet.GetCells().Get(u"C4").PutValue(1500);
    sheet.GetCells().Get(u"A5").PutValue(u"Cherry");     sheet.GetCells().Get(u"B5").PutValue(2020); sheet.GetCells().Get(u"C5").PutValue(2500);
    sheet.GetCells().Get(u"A6").PutValue(u"Grape");      sheet.GetCells().Get(u"B6").PutValue(2021); sheet.GetCells().Get(u"C6").PutValue(3000);
    sheet.GetCells().Get(u"A7").PutValue(u"Blueberry");  sheet.GetCells().Get(u"B7").PutValue(2021); sheet.GetCells().Get(u"C7").PutValue(1800);
    sheet.GetCells().Get(u"A8").PutValue(u"Kiwi");       sheet.GetCells().Get(u"B8").PutValue(2021); sheet.GetCells().Get(u"C8").PutValue(2200);
    sheet.GetCells().Get(u"A9").PutValue(u"Cherry");     sheet.GetCells().Get(u"B9").PutValue(2021); sheet.GetCells().Get(u"C9").PutValue(2700);
    int idx1 = sheet.GetPivotTables().Add(u"A1:C9", u"E3", u"Pivot1");
    PivotTable pivotTable1 = sheet.GetPivotTables().Get(idx1);
    pivotTable1.AddFieldToArea(PivotFieldType::Row, u"Fruit");
    pivotTable1.AddFieldToArea(PivotFieldType::Column, u"Year");
    pivotTable1.AddFieldToArea(PivotFieldType::Data, u"Amount");
    int idx2 = sheet.GetPivotTables().Add(u"A1:C9", u"E15", u"Pivot2");
    PivotTable pivotTable2 = sheet.GetPivotTables().Get(idx2);
    pivotTable2.AddFieldToArea(PivotFieldType::Row, u"Fruit");
    pivotTable2.AddFieldToArea(PivotFieldType::Column, u"Year");
    pivotTable2.AddFieldToArea(PivotFieldType::Data, u"Amount");
    sheet.GetCells().Get(u"C2").PutValue(5000);
    sheet.GetCells().Get(u"C5").PutValue(7500);
    sheet.GetCells().Get(u"C9").PutValue(9500);
    pivotTable2.CalculateData();
    wb.Save(u"output.xlsx");
    Aspose::Cells::Cleanup();
    return 0;
}

Рабочая книга часто содержит много сводных таблиц, которые все построены поверх одного общего кэша. Чтобы перечислить их — например, перед выполнением пакетного обновления или для диагностики влияния общего кэша — используйте PivotCache.GetPivotTables(). Этот метод возвращает коллекцию каждой PivotTable, которая зависит от данного кэша.

Миграция с устаревшего PivotTable.RefreshData()

До Aspose.Cells for C++ v26.7 стандартным способом обновления сводной таблицы был вызов PivotTable.RefreshData() для каждой сводной таблицы отдельно. Начиная с версии v26.7 этот метод помечен как устаревший и должен быть заменён API с поддержкой кэша, описанными выше. Есть две причины, по которым подход с использованием RefreshData() для каждой таблицы проблематичен в реальных рабочих книгах:

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

Какой API обновления следует использовать?

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

Цель Рекомендуемый API Примечания
Обновить всё в рабочей книге Workbook.RefreshAll() Один вызов; охватывает все кэши и таблицы.
Обновить только сводные таблицы на одном листе Worksheet.RefreshPivotTables() Ограничено одним рабочим листом.
Исходные данные изменились для одного кэша pivotTable.GetPivotCache().Refresh() Обновляет ВСЕ сводные таблицы, использующие этот общий кэш.
Изменились только параметры представления/макета pivotTable.CalculateData() Пропускает ненужное обращение к источнику.
Список всех сводных таблиц на общем кэше pivotCache.GetPivotTables() Используйте для перечисления перед массовым обновлением.
На практике предпочтительнее использовать API на основе кэша вместо устаревшего RefreshData() для каждой таблицы. Они учитывают общие кэши, избегают избыточных обращений к источнику и позволяют выбрать наименьшую область, удовлетворяющую вашим требованиям к обновлению.

Распространённые ошибки

  • Забыли обновить перед сохранением. Сводная таблица записывает свои отображаемые значения на рабочий лист только тогда, когда её цепочка данных обновлена. Если вы изменили исходные ячейки, вызовите PivotCache.Refresh() (или Workbook.RefreshAll()) перед Workbook.Save(), иначе сохранённый файл всё ещё будет содержать старые агрегированные значения.
  • Вызов устаревшего RefreshData() для каждой таблицы. В версии v26.7 метод PivotTable.RefreshData() помечен как устаревший и повторно получает данные из источника при каждом вызове. При наличии нескольких сводных таблиц, использующих общий кэш, это означает N избыточных обращений к источнику. Замените на один вызов PivotCache.Refresh() с последующим CalculateData() для каждой таблицы.
  • Обновление при изменении только макета. Если вы изменили только представление сводной таблицы (порядок столбцов, ConsolidationFunction и т. д.), не затрагивая исходные данные, вызов PivotCache.Refresh() не нужен и будет медленным. Вызовите pivotTable.CalculateData() для повторного отображения из существующего кэша.
  • Внешний источник не поддерживается PivotCache.Refresh(). Если источник сводной таблицы поступает из внешнего подключения (база данных, OLAP-куб и т. д.), PivotCache.Refresh() не может обновить его в версии v26.7 — в настоящее время он поддерживает только типы источников Sheet и Consolidation. Для внешних источников заново откройте рабочую книгу или перестройте кэш из источника.
using Aspose.Cells;
Workbook workbook = new Workbook("input.xlsx");
workbook.RefreshAll();
workbook.Save("output.xlsx");