6x Faster Excel Reads in Power Automate: Office Scripts vs “List Rows” [Benchmark]

Are you struggling with Power Automate timeouts when reading large Excel tables?
The standard “List rows present in a table” action is notoriously slow and often fails when handling datasets larger than the 5,000-item pagination limit.

In this article, I’ll share a benchmark-tested Office Script solution that cuts processing time from 44 seconds down to 7 seconds (up to 6x faster) and reliably handles 100,000 rows without errors.

スポンサーリンク

Why “List Rows Present in a Table” Fails on Large Data

The standard method for retrieving Excel data is using the “List rows present in a table” action in the Excel connector.
Screenshot of the List Rows Present in a Table action in Power Automate
However, this action suffers from significant performance bottlenecks. In my benchmark, it took about 44 seconds just to retrieve 5,000 records.
Screenshot showing the 44-second processing time for retrieving 5,000 records
Worse yet, when attempting to retrieve the maximum 100,000 rows, the action frequently failed with timeouts or API errors.
Screenshot showing an error when attempting to retrieve 100,000 rows

For more details on these limitations, check my previous analysis:
Performance Analysis of “List rows present in a table”

In this article, I’ll demonstrate how to use Office Scripts to bypass these limits and retrieve data reliably.

Solution 1: A Versatile Script for Any Table

First, let’s look at a generic function that can retrieve values efficiently without depending on a specific table schema.

The Code

Since Office Scripts cannot easily return an array of ‘any’ objects to Power Automate, this script converts all table rows into a JSON string before returning them.
Screenshot of Office Script code returning table data as JSON string

Copy the code below into your Office Script editor:

function main(workbook: ExcelScript.Workbook, tableName:string) {
    // Get the table with the specified name
    const table = workbook.getTable(tableName);

    // Throw an error if the table doesn't exist
    if (!table) {
        throw new Error(`Table "${tableName}" not found.`);
    }

    // Get the header row of the table
    const headers = table.getHeaderRowRange().getValues()[0];

    // Get the data range of the table (excluding header and total rows)
    const dataRange = table.getRangeBetweenHeaderAndTotal();

    // Get the values from the table
    const values = dataRange.getValues();

    // Array to store the results
    let rowObjects: object[] = [];

    // Convert each row to an object and add to the results array
    for (let i = 0; i < values.length; i++) {
        let rowObject = {};
        for (let j = 0; j < headers.length; j++) {
            // Set the cell value using the header name as the key
            rowObject[headers[j].toString()] = values[i][j];
        }
        rowObjects.push(rowObject);
    }
    return JSON.stringify(rowObjects);
}
💡 How to use in Power Automate:
Add a "Parse JSON" action after the "Run script" action.
Run the flow once to get the output, then use the "Generate from sample" feature in the Parse JSON action to automatically create the schema.
Screenshot showing how to parse the JSON return value in Power Automate

Performance Benchmark

500 Records: 7 seconds (The standard action is similar for small datasets).
Screenshot showing 7-second execution time for 500 records
10,000 Records: Still only 7 seconds. This is where the speed advantage becomes obvious compared to the standard action.
Screenshot showing 7-second execution time for 10,000 records
100,000 Records: Completed in 26 seconds. Unlike the standard action, all data was retrieved successfully without errors.
Screenshot showing 26-second execution time for 100,000 records
Screenshot showing the successfully retrieved data from 100,000 records

Note: The script has a fixed startup overhead (a few seconds), so the efficiency gains become massive as the row count increases.

Solution 2: Specialized Script (Recommended)

While the generic script above is fast, it has two minor downsides:

  • It returns data as a large JSON string (which requires parsing).
  • Dates are returned as Excel Serial Numbers (e.g., 45321 instead of 2024-02-10).

To solve this, I recommend creating a script specialized for your specific table structure.

The Code

This script is optimized for a table with columns named 'str', 'num', and 'date'. It includes a helper function to convert Excel serial dates to ISO format automatically.
Screenshot of a specialized Office Script for a specific table structure
// Define the return type structure based on your columns
type TableRow = {
    str: string;
    num: number;
    date: string;
};

function main(workbook: ExcelScript.Workbook): TableRow[] {
    // Get the specific table by name
    const table = workbook.getTable("table500");

    // Get the range of the table
    const range = table.getRange();

    // Get the values from the table
    const values = range.getValues();

    // Get the indices of the columns to retrieve
    const headers = values[0];
    const strIndex = headers.indexOf("str");
    const numIndex = headers.indexOf("num");
    const dateIndex = headers.indexOf("date");

    // Throw an error if the column indices are not found
    if (strIndex === -1 || numIndex === -1 || dateIndex === -1) {
        throw new Error("Required columns 'str' or 'num' not found in the table");
    }

    // Array to store the results
    const result: { str: string, num: number, date:string }[] = [];

    // Process each row, skipping the header row
    for (let i = 1; i < values.length; i++) {
        const row = values[i];
        const serial_date = Number(row[dateIndex]);
        const date = excelSerialDateToISOString(serial_date);
        result.push({
            str: row[strIndex].toString(),
            num: Number(row[numIndex]),
            date: date
        });
    }

    return result;
}

/**
 * Convert Excel date serial number to ISO string
 * @param serialNumber Excel date serial number
 * @param use1904Windowing Whether to use 1904 as the base year (for legacy Mac support)
 * @returns Date string in ISO format (yyyy-MM-dd)
 */
function excelSerialDateToISOString(serialNumber: number, use1904Windowing: boolean = false): string {
    // Set the base date (1900 or 1904)
    const baseDate = use1904Windowing ? new Date(Date.UTC(1904, 0, 1)) : new Date(Date.UTC(1900, 0, 1));

    // Handle Excel date bug (Excel incorrectly treats 1900 as a leap year)
    let adjustedSerialNumber = serialNumber;
    if (!use1904Windowing && serialNumber >= 60) {
        adjustedSerialNumber--; // Adjust for February 29, 1900 bug
    }

    // Convert serial number to days (in Excel, the integer part is the date, the fractional part is the time)
    const days = Math.floor(adjustedSerialNumber);
    const timeFraction = adjustedSerialNumber - days;

    // Convert to milliseconds and add to the base date
    const milliseconds = Math.round(timeFraction * 24 * 60 * 60 * 1000);
    const date = new Date(baseDate.getTime() + ((days - 1) * 24 * 60 * 60 * 1000) + milliseconds);

    // Return in yyyy-MM-dd format
    const year = date.getFullYear();
    const month = String(date.getMonth() + 1).padStart(2, '0');
    const day = String(date.getDate()).padStart(2, '0');

    return `${year}-${month}-${day}`;
}

Why this approach is better

While the execution speed remains equally fast (7 seconds), this method is superior because dates are returned in ISO format (yyyy-MM-dd).

This eliminates the need for complex date conversion expressions in Power Automate, making your flow cleaner and easier to maintain.
Screenshot showing the performance results of the specialized script

Summary

If you need to process large Excel files in Power Automate, switching to Office Scripts is highly recommended.

  • Speed: Up to 6x faster (44s → 7s).
  • Reliability: Handles 100,000+ rows without timeouts.
  • Flexibility: Custom scripts can handle date formatting directly in Excel.

Related Articles

コメント

Copied title and URL