Update & Create Excel Records 50-100x Faster
I was able to develop an Office Script to update rows and an Office Scripts to create rows from Power Automate array data. So instead of a flow creating a new action API call for each individual row update or creation, this flow can just send an array of new data and the Office Scripts will match up primary key values, update each row it finds, then create the rows it doesn't find.
And these Scripts do not require manually entering or changing any column names in the Script code.
• In testing for batches of 1000 updates or creates, it's doing ~2000 row updates or creates per minute, 50x faster than the standard Excel create row or update row actions at max 50 concurrency. And it accomplished all the creates or updates with less than 4 actions or only .4% of the standard 1000 action API calls.
• The Run Script code for processing data has 2 modes, the Mode 2 batch method that saves & updates a new instance of the table before posting batches of table ranges back to Excel & the Mode 1 row by row update calling on the Excel table.
The Mode 2 script batch processing method will activate for updates on tables less than 1 million cells. It does encounter more errors with larger tables because it is loading & working with the entire table in memory.
Shoutout to Sudhi Ramamurthy for this great batch processing addition to the template!
Code Write-Up: https://docs.microsoft.com/en-us/office/dev/scripts/resources/samples/write-large-dataset
Video: https://youtu.be/BP9Kp0Ltj7U
The Mode 1 script row by row method will activate for Excel tables with more than 1 million cells. But it is still limited by batch file size so updates on larger tables will need to run with smaller cloud flow batch sizes of less than 1000 in a Do until loop.
The Mode 1 row by row method is also used when the ForceMode1Processing field is set to Yes.
Be aware that some characters in column names, like \ / - _ . : ; ( ) & $ may cause errors when processing the data. Also backslashes \ in the data, which are usually used to escape characters in strings, may cause errors when processing the JSON.
Version 7 Note
Diverting from what is shown in the video, I was able to remove almost all the flow actions in the "Match new and existing data key values then batch update" scope after replicating their functions in the scripts themselves. The flow now goes directly from the "SelectGenerateData" action to the "Run script Update Excel rows" action and the script handles matching up the UpdatedData JSON keys/field names to the destination table headers.
Also version 7 changes the primary key set up in the SelectGenerateData and changes the logic for skipping cell value updates & blanking out cell values.
Now the primary key column name from the destination table must be present in the SelectGenerateData action with the dynamic content for the values you want to match up to update. No more 'PrimaryKey' line, the update script will automatically reference the primary key column in the SelectGenerateData data based on the PrimaryKeyColumnName input on the update script action in the flow.
Now leaving a blank "" in the SelectGenerateData action will make that cell value in the Excel table empty, while leaving a null in the SelectGenerateData action will skip updating / not alter the cell value that currently exists in the Excel table there.
Version 6 which looks closer to the version shown in the video can still be accessed here: https://powerusers.microsoft.com/t5/Power-Automate-Cookbook/Excel-Batch-Create-Update-and-Upsert/m-p...
Version 7 Set-Up Instructions
Go to the bottom of this post & download the BatchExcel_1_0_0_xx.zip file. Go to the Power Apps home page (https://make.powerapps.com/). Select Solutions on the left-side menu, select Import solution, Browse your files & select the BatchExcel_1_0_0_xx.zip file you just downloaded. Then select Next & follow the menu prompts to apply or create the required connections for the solution flows.
Once imported, find the Batch Excel solution in the list of solution & click it to open the solution. Then click on the Excel Batch Upserts V7 item to open the flow. Once inside the flow, delete the PlaceholderValue Delete after import action.
Open the Office Script Batch Update compose action to the BatchUpdateV7 script code. Select everything inside the compose input & control + C copy it to the clipboard.
Then find & open an Excel file in Excel Online. Go to the Automate tab, click on All Scripts & then click on New Script.
When the new script opens, select everything in the script & control + V paste the BatchUpdateV7 script from the clipboard into the menu. Then rename the script BatchUpdateV7 & save it. That should make the BatchUpdateV7 reference-able in the later Run script flow action.
Do the same process to import the BatchCreateV7 script.
Then go to the List rows Sample source data action. If you are going to use an Excel table as the source of updated data, then you can fill in the Location, Document Library, File, & Table information on this action. If you are going to use a different source of updated data like SharePoint, SQL, Dataverse, an API call, etc, then delete the List rows Sample source data placeholder action & insert your new get data action for your other source of updated data.
Following that, go to the Excel batch update & create scope. Open the PrimaryKeyColumnName action, remove the placeholder values in the action input & input the column name of the unique primary key for your destination Excel table. For example, I use ID for the sample data.
Then go to the SelectGenerateData action.
If you replaced the List rows Sample source data action with a new get data action, then you will need to replace the values dynamic content from that sample action with the new values dynamic content of your new get data action (the dynamic content that outputs a JSON array of the updated data).
In either case, you will need to input the table header names from the destination Excel table on the left & the dynamic content for the updated values from the updated source action on the right. You MUST include the column header for the destination primary key column & the the primary key values from the updated source data here for the Update script to match up what rows in the destination table need which updates from the source data. All the other columns are optional / you only need to include them if you want to update their values.
After you have added all the columns & updated data you want to the SelectGenerateData action, then you can move to the Run script Update Excel rows action. Here add the Location, Document Library, File Name, Script, Table Name, Primary Key Column Name, ForceMode1Processing, & UpdatedData. You will likely need to select the right-side button to switch the UpdatedData input to a single array input before inserting the dynamic content for the SelectGenerateData action.
Then open the Condition If new records continue else skip action & open the Run script Create Excel rows action. In this run script action input the Location, Document Library, Script, Table Name, & CreateData. You again will likely need to select the right-side button to change the CreateData input to a single array input before inserting the dynamic content for the Filter array Get records not found in table output.
If you need just a batch update, then you can remove the Filter array Get records not found in table & Run script Create Excel rows actions.
If you need just a batch create, then you can replace the Run script Batch update rows action with the Run script Batch create rows action, delete the update script action, and remove the remaining Filter array Get records not found in table action. Then any updated source data sent to the SelectGenerateData action will just be created, it won't check for rows to update.
Thanks for any feedback,
Please subscribe to my YouTube channel (https://youtube.com/@tylerkolota?si=uEGKko1U8D29CJ86).
And reach out on LinkedIn (https://www.linkedin.com/in/kolota/) if you want to hire me to consult or build more custom Microsoft solutions for you.
Office Script Code
(Also included in a Compose action at the top of the template flow)
Batch Update Script Code: https://drive.google.com/file/d/1kfzd2NX9nr9K8hBcxy60ipryAN4koStw/view?usp=sharing
Batch Create Script Code: https://drive.google.com/file/d/13OeFdl7em8IkXsti45ZK9hqDGE420wE9/view?usp=sharing
(ExcelBatchUpsertV7 is the core piece, ExcelBatchUpsertV7b includes a Do until loop set-up if you plan on updating and/or creating more than 1000 rows on large tables.)
watch?v=HiEU34Ix5gA
Hello,
I'm running into an issue using the update script --- if I am updating a table with data that isn't fully inclusive (ie - filtered out data that doesn't need to be updated) the script is changing the data not being updated to a blank value (although other columns associated with the same primary key are being updated). Is this because that column is being updated for other values and not the specific rows filtered out?
For example, say my dataset looks like this:
Name | Date1 | Date2 | Date3 |
John | 4/1/2023 | 4/8/2023 | 4/12/2023 |
Bill | 4/1/2023 | 4/3/2023 | 4/8/2023 |
Robert | 4/1/2023 | 4/12/2023 | |
Stan | 4/1/2023 | 4/12/2023 | 4/8/2023 |
If my Update File looks like this:
Name | Date1 | Date2 | Date3 |
Robert | 4/1/2023 | 4/13/2023 | 4/12/2023 |
Stan | 4/1/2023 | 4/12/2023 | 4/8/2023 |
My results end up looking like this:
Name | Date1 | Date2 | Date3 |
John | |||
Bill | |||
Robert | 4/1/2023 | 4/13/2023 | 4/12/2023 |
Stan | 4/1/2023 | 4/12/2023 | 4/8/2023 |
Thanks in advance.
@seanmccormac
I'm not replicating that issue...
Before update:
After update:
Do these columns have formulas?
Are the primary key values for these rows definitely not in the update data? I see the example is using names, are those names repeated anywhere in the data?
And did you try changing the ForceMode1Processing from "No" to "Yes" to see if that resolved the issue?
Hello,
When I set it to "yes", I get this error 100% of the time (I think because the file I'm using is not as large as the script is meant for):
@seanmccormac
The mode 2 processing works by pulling in the entire table into memory & updating each row in that in-memory copy before pasting it back to the sheet in batches of rows. I'm not sure if the issue is something that happens during that pasting of the rows back to the sheet. It involves more code, but the mode 2 can handle much larger batches until it hits a table size limit.
The mode 1 processing is meant to work on a destination table of any size. And it just finds each row in the table & updates it. No additional in-memory copy or batch re-pasting of the table.
The downside of mode 1 is it can only handle around 500 to 1500 updates in each run of the script depending on how much data you are trying to update.
What is the size of your set of update data? If you take a smaller number of rows, like the initial 500, does the script succeed with mode 1 processing?
Hi @takolota
Awesome solution as always!
I only have one doubt about changing the source.
I'm using the 5.3b because of the size of the data set and in my case I want to try and get the data from a SharePoint list instead of an Excel file.
The issue comes with the "Get Items" step for SharePoint since it doesnt have "Skip Count".
I saw the code you places in comments of the "List rows" step, but unfortunately I didnt quite understand where it should be placed.
Would you mind giving me some clarification?
Thanks in advance.
@Reinand
Thanks for bringing this up.
So if you have 100,000 rows or less to update / create from SharePoint, then I suggest actually putting the Get items (/w pagination set to 100,000) right before the Do Until loop. Then you can set up a Compose with that expression & pass it to SelectGenerateUpdateData or directly input the expression in the GenerateUpdateData action...
take(skip(outputs('Get_items')?['body/value'], mul(iterationIndexes('Do_until_Update'), variables('BatchSize'))), variables('BatchSize'))
There is also a way to go beyond the 100,000 SharePoint items by setting up another Do until loop that runs until the Get items is empty and then within that loop you would need to sort the data by ID, capture the last ID returned from SP, then set the next loop with the next 100,000 SP items to Filter Query to only items with an ID greater than the last ID of the previous loop.
But hopefully you don't have to add those extra complications.
No need for the extra steps, this is exactly what I've been looking for!
Appreciate you taking the time clarify and you're doing an awesome job with these Flows.
Cheers
Version 5.4
-Fix some functionality lost in a previous version allowing null values to over-write cells again
-More people should be able to access the flow through the regular import method now.
(Some users encountered errors preventing them from importing the flow. All initial data source & table references in the template flow have been replaced with a blank placeholder value so the flow isn't trying to read references that do not exist.)
-New BatchUpdateV6 script with more efficient Mode 1 processing allowing for 1.2x to 2x larger batch sizes on the larger tables & jobs, especially on updates with a larger number of columns
Batch Update Script Code: https://drive.google.com/file/d/1kfzd2NX9nr9K8hBcxy60ipryAN4koStw/view?usp=sharing
(This updated script helps reduce the number of times it has to read from the Excel table during each row update. The cell by cell updating within the row was successfully moved to an in-memory row copy that is then inserted back to the table. Instead of 1 read per column, the script now reads each whole row 2 times regardless of the number of columns being updated in each row.)
Good Afternoon,
I hope this is an easy fix and I appreciate anyone's time. I have loaded the V5.4b and I believe that everything is set up correctly. I am using it an Excel spreadsheet in Onedrive with two tables: The target table is Table1 and the source table is Table2. There are approximately 5000 rows and 20 columns in Table2. The structure of Table1 is identical and the columns are identical to Table2. When I run the flow I receive an error "Unable to process template language expressions in action 'ComposeDestinationJSON' inputs at line '0' and column '0': 'The template language function 'json' parameter is not valid." The first 1500 rows are created correctly and they are also updated correctly but nothing happens after row 1500 in the target Table.
I am unfortunately not fluent using Power Automate but what I have tried is changing the batch size down to 100 and up to 1000 and also ForceMode1 set to Yes and No with the same results. If I delete row 1500 to the end on the source sheet the flow runs as expected with no errors.
The error shown below is what I am getting with a long list that includes all the data from row 1501 to the end.
@m00ch
It looks like it is not recognizing the input as a proper stringified JSON. Could you share a sample of your data & maybe a larger part of the error message? If needed, you can private message me with a file.