Skip to main content

About incremental refresh

Incremental refresh in Power BI divides a table into date ranges called partitions and reloads only the most recent ones on each refresh. For example, you can keep one year of data and reload only the last seven days. This shortens refreshes and lowers the risk of timeouts.
Incremental refresh is a Power BI feature that works with any data source that supports filtering by timestamp, including the Cognite Data Fusion (REST API) connector. You set it up the same way as for other data sources. For details on refresh policies and troubleshooting, see Microsoft’s incremental refresh documentation.

Any table with a timestamp column

You can use incremental refresh with data model instances, records, data points, and other CDF resources that support filtering by timestamp. The table needs one date and time column that places each row in time. Power BI uses that column to sort every row into a time range.

The timestamp column decides which time range each row belongs to

Why a full import fails on large data

Without incremental refresh, every refresh downloads the whole table. Two limits apply: how fast CDF can return data, and how long Power BI waits before it cancels the refresh. If the download takes longer than the refresh timeout, Power BI cancels the refresh and saves nothing. A faster connection doesn’t solve this. Request less data in each query instead.

A large history runs past the refresh timeout, while one week finishes well within it

How partitions work

Power BI fetches each partition separately and passes its start and end to your query as the RangeStart and RangeEnd parameters. On each scheduled refresh, it downloads only the most recent partitions again and keeps the older ones as they are.

Each refresh runs your query once for each reloaded partition, with that partition's RangeStart and RangeEnd

Before you start

This page assumes you know how to:
  • Make sure the endpoint you want to fetch data from supports timestamp-based filtering.
  • Your queries must return the same column names and data types, even when a date range contains no rows.
  • All date ranges in an incrementally refreshed table must read from the same data source.

Set up incremental refresh

1

Create the parameters

In Power Query, create two datetime parameters named RangeStart and RangeEnd. Set them to a short period, such as one day, so Power BI Desktop loads only a small sample while you develop. After you publish, the Power BI Service sets the values for each partition. The Power BI template includes both parameters.
2

Filter the query

Filter your query with RangeStart and RangeEnd. Use the utility functions to convert the parameters to the format CDF expects, and start from one of the examples.
3

Load the data

Select Close & Apply in the Power Query editor.
4

Configure the incremental refresh policy

In the Fields pane, right-click the table and select Incremental refresh. Set how much history to keep and how much to reload. See Configure incremental refresh.
5

Publish and refresh

Publish the model to the Power BI Service and run the first refresh manually. Then set up a scheduled refresh. Before you publish a large table, plan the first refresh.

Plan the first refresh

The first refresh after you publish loads the whole history you chose to keep. It’s the only refresh that must fit all of it inside the refresh timeout.

The first refresh downloads the whole history. Later refreshes download only the most recent partitions

Check these limits when you plan the first refresh: Power BI can load several partitions at once, but they all count against the same CDF limits. To estimate the download time, multiply the number of rows by the size of each row, then divide by the throughput. Measure representative responses with only the columns you need. For example, assume a table that grows by 7 million rows a day:
  • At 900 bytes per row (all columns), a day is about 6.3 GB. At an assumed effective throughput of 0.83 MB/s, that’s about 2.1 hours per day before model processing. Seven days take about 15 hours, which is likely to exceed the refresh timeout.
  • At 360 bytes per row (only the columns you need), a day is about 2.5 GB and takes roughly 50 minutes at the same throughput, before model processing.
These numbers are illustrative. Measure the actual refresh duration: request latency, connector pacing, retries, transformations, and model processing add to transfer time. To shorten the first refresh: If the first refresh still doesn’t fit, backfill the history outside Power BI.

Filter in CDF, not in Power BI

Where you filter decides how much data Power BI downloads from CDF. If you remove rows or columns in a Power Query step after the connector function returns, Power BI has already downloaded them, and the refresh takes just as long. To download less data, include the filter and the columns you need in the request that CDF receives. This is called server-side filtering. Each connector function has its own place for this: The RangeStart and RangeEnd filter follows the same rule. Put it in the request, as the examples on this page do, so each partition downloads only its own time range.

Backfill large histories outside Power BI

When the history is too large for the first refresh, export the older data once with the API and save it to files. Load those files into Power BI once, outside the scheduled refresh, so the refresh timeout doesn’t apply. Then set up incremental refresh only for the data after the cutoff date where the files end.

History before the cutoff date is loaded from files. Incremental refresh handles the data after it

Use three tables in the model: Create AllRows as a calculated table:
Combine historical and recent rows
UNION matches columns by position, so both tables must have the same columns in the same order. Filter RecentRows with >= on the cutoff date and the files with < on the same date, so no row appears in both tables.
Microsoft requires one data source for all date ranges in an incrementally refreshed table. This is why the files and API use separate tables, and only RecentRows uses incremental refresh. Power BI stores the combined AllRows table as well as its two input tables, so this approach needs extra storage and processing time. If the combined data is too large for your Power BI environment, store the history and new rows together in an external database or other supported source, then load from that source.

Handle updates and deletions

The timestamp column you partition by decides whether changes to existing items reach your report: To capture updates, partition by the last update time and remove the duplicates in DAX.

Keep the latest version of each item

Add a calculated table that keeps, for each item, only the row with the highest lastUpdatedTime. Use the columns that identify an item as the key in ALLEXCEPT, for example externalId, or space and externalId for data modeling instances:
Keep the latest row for each item
Replace Items with the name of your incrementally refreshed table. If you backfill the history, use AllRows instead so the files and recent data are deduplicated together. Use LatestItems in your reports.

Exclude deleted items

Some endpoints return a deletion marker, also called a tombstone, when an item is deleted. A tombstone is a row with the item’s key, a newer lastUpdatedTime, and a field that marks it as deleted. When the latest row for an item is a tombstone, exclude the item. Add the condition to the same filter, and replace status and "deleted" with the field and value your endpoint uses:
Keep the latest row for each item and exclude deleted items
Endpoints return tombstones for a limited time only. A loaded tombstone stays in Power BI until its partition is reloaded. If Power BI reloads the partition after the endpoint stops returning the tombstone, the tombstone is gone, and the earlier version of the deleted item reappears in the report. To prevent this, keep the reload period in the incremental refresh policy shorter than the time the endpoint keeps tombstones. Leave a margin of at least one partition, plus time to retry failed refreshes. Without tombstones, incremental refresh can’t detect deletions in partitions that are no longer reloaded. To remove deleted items, run a full refresh of the table.

Utility functions

Power BI supplies RangeStart and RangeEnd as plain datetime values that represent UTC. Since CDF endpoints expect timestamps in specific formats, convert them to the format each request type expects:
  • GetCDF/PostCDF: Normal GET/POST requests to the Cognite APIs expect timestamps in milliseconds since epoch (UTC).
  • GraphQL: Data modeling GraphQL requests expect timestamps in ISO 8601 string format.
  • ODataCDF/ODataDataModel: OData requests expect timestamps in datetimezone format for column filtering.
The Power BI template already includes these functions. If you start from the template, you don’t need to copy them into your report.
By default, refresh operations in the Power BI service determine the refresh window in UTC. If you configure a non-UTC time zone under Refresh, RangeStart and RangeEnd follow that time zone instead, and these functions no longer match CDF. Keep the default to avoid the mismatch. See Configure incremental refresh.

ConvertDateTimeToMs

Convert DateTime to milliseconds since epoch

ConvertDateTimeToIso

Convert DateTime to ISO 8601 text representation

ConvertDateTimeToDateTimeZone

Convert DateTime to DateTimeZone

EnforceSchema

EnforceSchema returns a table with the columns you list, even when the API response is empty. Missing columns are added with null values.
Enforce schema for a table

ColumnNamesToText

ColumnNamesToText writes the column names of a table as an M list that you can paste into EnforceSchema. Run it once on a GetCDF, PostCDF, or GraphQL response that returns at least one row.
Convert column names to text
EnforceSchema preserves existing column types and adds missing columns with null values. Apply explicit types after it, as shown below, so empty and populated partitions have the same schema.

Incremental refresh examples

These examples use PostCDF, GraphQL, and ODataDataModel. There is no GetCDF example, because you read data model instances and records through POST requests, GraphQL, or OData, not through GET endpoints with timestamp filters. Add ConvertMsToUtcDateTime for the POST example. Use an inclusive start and exclusive end (RangeStart <= timestamp < RangeEnd) so adjacent partitions neither overlap nor omit boundary rows. Nested fields such as properties are returned as JSON text for a stable, loadable schema. Expand them into explicitly typed columns instead if you need to report on individual properties. The PostCDF and GraphQL examples partition by lastUpdatedTime, so an updated item loads again into a newer partition and the table can hold several rows for it. To keep only the latest row for each item, see Handle updates and deletions. The ODataDataModel example partitions by a view property instead, because the service can’t filter on lastUpdatedTime.

PostCDF example

This example shows an incremental refresh when fetching CogniteActivity instances with the PostCDF function:
Incremental refresh with PostCDF

GraphQL example

This example shows how to implement an incremental refresh with a GraphQL query for CogniteActivity instances:
Incremental refresh with GraphQL

ODataDataModel example

This example shows incremental refresh with ODataDataModel using column-level filtering, which Power BI can push down to the server. Note that when using OData you don’t need to enforce any schema since the OData service always returns a table with the same schema, even if no data is returned.
Partition by a view property, such as startTime on CogniteActivity. The data modeling OData service can’t filter on createdTime or lastUpdatedTime. If you filter on those columns, you don’t get an error, but Power BI downloads the whole table for every partition and filters it afterward.
Rows with no startTime aren’t in any partition, and changes to older activities aren’t captured after their partitions stop refreshing.
Incremental refresh with ODataDataModel (column filtering)
Last modified on November 2, 2026