Press "Enter" to skip to content

Improve Power Apps to SQL Patch Performance

For my use case, I had to:
1. Load a SQL table/view to a collection
2. Record user updates in collection
3. Insert updated records to SQL

The challenge I faced was the slow performance of ForAll and Patch commands when used with SQL. This is the standard method outlined in Power Apps reference documentation, but I couldn’t get it to work in scale.

To illustrate this point, I created a sample Power App and connected it to [SalesLT].[SalesOrderDetail] table in AdventureWorks database.

Combining SQL with For Loop for Maximum Pain

ForAll / Patch Command

ForAll(
c_sales_order_detail,
Patch(
'[PA].[SalesOrderDetail]',
Defaults('[PA].[SalesOrderDetail]'),
{
SalesOrderID: SalesOrderID,
SalesOrderDetailID: SalesOrderDetailID,
OrderQty: OrderQty,
ProductID: ProductID,
UnitPrice: UnitPrice,
UnitPriceDiscount: UnitPriceDiscount,
LineTotal: LineTotal,
ModifiedDate: Now()
}
)
);
ForAll( c_sales_order_detail, Patch( '[PA].[SalesOrderDetail]', Defaults('[PA].[SalesOrderDetail]'), { SalesOrderID: SalesOrderID, SalesOrderDetailID: SalesOrderDetailID, OrderQty: OrderQty, ProductID: ProductID, UnitPrice: UnitPrice, UnitPriceDiscount: UnitPriceDiscount, LineTotal: LineTotal, ModifiedDate: Now() } ) );
ForAll(
    c_sales_order_detail,
    Patch(
        '[PA].[SalesOrderDetail]',
        Defaults('[PA].[SalesOrderDetail]'),
        {
            SalesOrderID: SalesOrderID,
            SalesOrderDetailID: SalesOrderDetailID,
            OrderQty: OrderQty,
            ProductID: ProductID,
            UnitPrice: UnitPrice,
            UnitPriceDiscount: UnitPriceDiscount,
            LineTotal: LineTotal,
            ModifiedDate: Now()
        }
    )
);

ForAll combined with Patch took 4 minutes and 2 seconds to write 542 records! Try demoing this to a client on WebEx, with spotty conference room WiFi. Well hopefully after this post, you will never have to experience it yourself.

JSON to the rescue

Patch Command with JSON

Patch(
'[PA].[SalesOrderDetailJson]',
Defaults('[PA].[SalesOrderDetailJson]'),
{
OutputJson: JSON(c_sales_order_detail)
}
);
Patch( '[PA].[SalesOrderDetailJson]', Defaults('[PA].[SalesOrderDetailJson]'), { OutputJson: JSON(c_sales_order_detail) } );
Patch(
    '[PA].[SalesOrderDetailJson]',
    Defaults('[PA].[SalesOrderDetailJson]'),
    {  
        OutputJson: JSON(c_sales_order_detail)
    }
);

Using JSON took .652 seconds for a whopping increase of 37,000% in processing time. By converting the collection to JSON format, you can avoid ForAll loop and multiple iterations of Patch with a single write to SQL.

To bring this solution in a full circle, JSON functions in SQL will replicate the table/view structure:

SELECT CAST(JSON_VALUE(js.value, '$.SalesOrderID') AS INT) AS SalesOrderID
,CAST(JSON_VALUE(js.value, '$.SalesOrderDetailID') AS INT) AS SalesOrderDetailID
,CAST(JSON_VALUE(js.value, '$.OrderQty') AS SMALLINT) AS OrderQty
,CAST(JSON_VALUE(js.value, '$.ProductID') AS INT) AS ProductID
,CAST(JSON_VALUE(js.value, '$.UnitPrice') AS MONEY) AS UnitPrice
,CAST(JSON_VALUE(js.value, '$.UnitPriceDiscount') AS MONEY) AS UnitPriceDiscount
,CAST(JSON_VALUE(js.value, '$.LineTotal') AS NUMERIC(38, 6)) AS LineTotal
,CAST(JSON_VALUE(js.value, '$.rowguid') AS uniqueidentifier) AS rowguid
,CAST(JSON_VALUE(js.value, '$.ModifiedDate') AS DATETIME) AS ModifiedDate
FROM [PA].[SalesOrderDetailJson] AS sod
CROSS APPLY OPENJSON (sod.OutputJson) AS js
SELECT CAST(JSON_VALUE(js.value, '$.SalesOrderID') AS INT) AS SalesOrderID ,CAST(JSON_VALUE(js.value, '$.SalesOrderDetailID') AS INT) AS SalesOrderDetailID ,CAST(JSON_VALUE(js.value, '$.OrderQty') AS SMALLINT) AS OrderQty ,CAST(JSON_VALUE(js.value, '$.ProductID') AS INT) AS ProductID ,CAST(JSON_VALUE(js.value, '$.UnitPrice') AS MONEY) AS UnitPrice ,CAST(JSON_VALUE(js.value, '$.UnitPriceDiscount') AS MONEY) AS UnitPriceDiscount ,CAST(JSON_VALUE(js.value, '$.LineTotal') AS NUMERIC(38, 6)) AS LineTotal ,CAST(JSON_VALUE(js.value, '$.rowguid') AS uniqueidentifier) AS rowguid ,CAST(JSON_VALUE(js.value, '$.ModifiedDate') AS DATETIME) AS ModifiedDate FROM [PA].[SalesOrderDetailJson] AS sod CROSS APPLY OPENJSON (sod.OutputJson) AS js
SELECT	CAST(JSON_VALUE(js.value, '$.SalesOrderID') AS INT) AS SalesOrderID
		,CAST(JSON_VALUE(js.value, '$.SalesOrderDetailID') AS INT) AS SalesOrderDetailID
		,CAST(JSON_VALUE(js.value, '$.OrderQty') AS SMALLINT)	AS OrderQty
		,CAST(JSON_VALUE(js.value, '$.ProductID') AS INT) AS ProductID
		,CAST(JSON_VALUE(js.value, '$.UnitPrice') AS MONEY) AS UnitPrice
		,CAST(JSON_VALUE(js.value, '$.UnitPriceDiscount') AS MONEY) AS UnitPriceDiscount
		,CAST(JSON_VALUE(js.value, '$.LineTotal') AS NUMERIC(38, 6)) AS LineTotal
		,CAST(JSON_VALUE(js.value, '$.rowguid') AS uniqueidentifier) AS rowguid
		,CAST(JSON_VALUE(js.value, '$.ModifiedDate') AS DATETIME) AS ModifiedDate
FROM	[PA].[SalesOrderDetailJson] AS sod
CROSS APPLY OPENJSON (sod.OutputJson) AS js

I hope this helps you in your journey to integrate Power Apps with SQL. If you have other tips-and-tricks you would like to share, please leave a comment below!

Be First to Comment

Leave a Reply

Your email address will not be published.