<?xml version="1.0" encoding="utf-8"?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
  version="2.0">
  <channel>
    <title>Dynamics Ax Performance Team Blog</title>
    <atom:link
      href="https://docs.microsoft.com/archive/blogs/axperf/feed.xml"
      rel="self"
      type="application/rss+xml" />
    <link>https://docs.microsoft.com/archive/blogs/axperf/feed.xml</link>
    <description />
    <lastBuildDate>Mon, 20 Jul 2015 22:31:40 GMT</lastBuildDate>
    <language>en-US</language>
    <sy:updatePeriod>hourly</sy:updatePeriod>
    <sy:updateFrequency>1</sy:updateFrequency>
    <item>
      <title>Multiple new Benchmarks released: Microsoft Dynamics AX 2012 R2 Multiple Retail Workloads, Microsoft Dynamics AX 2012 R3 Financial Benchmark, Microsoft Dynamics AX 2012 R3 Day in the Life Benchmark for Retail and Distribution</title>
      <link>https://docs.microsoft.com/archive/blogs/axperf/multiple-new-benchmarks-released-microsoft-dynamics-ax-2012-r2-multiple-retail-workloads-microsoft-dynamics-ax-2012-r3-financial-benchmark-microsoft-dynamics-ax-2012-r3-day-in-the-life-benchmark-fo</link>
      <pubDate>Mon, 05 Jan 2015 14:22:00 GMT</pubDate>
      <dc:creator><![CDATA[Ch. Wolf]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/axperf/2015/01/05/multiple-new-benchmarks-released-microsoft-dynamics-ax-2012-r2-multiple-retail-workloads-microsoft-dynamics-ax-2012-r3-financial-benchmark-microsoft-dynamics-ax-2012-r3-day-in-the-life-benchmark-fo/</guid>
      <description><![CDATA[We are happy to announce that the following benchmarks will be available, just in time for Tech...]]></description>
      <content:encoded><![CDATA[<p>We are happy to announce that the following benchmarks will be available, just in time for Tech conference 2015. We have published both summary and detail documents for each benchmark.</p>
<p><strong>Microsoft Dynamics AX 2012 R2 Benchmark for Multiple Retail Workloads</strong></p>
<p>In 2014, Microsoft conducted a benchmark on Microsoft Dynamics AX 2012 to measure the performance and scalability characteristics of the Microsoft Dynamics AX 2012 R2 retail solution in a simulated retail scenario. The benchmark showcases the ability of AX 2012 R2 to handle various specialized loads concurrently, without compromising performance and scalability on critical business processes. The benchmark includes many functional scenarios across different client and integration technologies, thereby providing a view of retail back-end scenarios.</p>
<p>The following scenarios were executed to complete the benchmark:</p>
<ul>
<li>Trickle feed of point of sale (POS) transactions from stores</li>
<li>Posting trade agreements to stores</li>
<li>Publishing product assortments to stores, including converting pre-actions to actions</li>
</ul>
<p>End-of-day processing of POS transactions:</p>
<ul>
<li>Inventory reservations were made for the imported transactions.</li>
<li>Imported POS transactions were consolidated and aggregated, and then transferred to statements in a peak-load situation.</li>
<li>Statements were processed and transferred to sales orders, and financial updates were posted.</li>
</ul>
<p>Please find the&nbsp;benchmark documents here:</p>
<p><a href="https://mbs.microsoft.com/partnersource/northamerica/deployment/documentation/msdax2012r2multiretailwl">https://mbs.microsoft.com/partnersource/northamerica/deployment/documentation/msdax2012r2multiretailwl</a><br /><a href="https://mbs.microsoft.com/customersource/northamerica/AX/learning/documentation/msdax2012r2multiretailwl">https://mbs.microsoft.com/customersource/northamerica/AX/learning/documentation/msdax2012r2multiretailwl</a></p>
<p><br /><strong>Microsoft Dynamics AX 2012 R3 Financial Benchmark</strong></p>
<p>In August 2014, Microsoft conducted a financial benchmark of Microsoft Dynamics AX 2012 R3 to measure the application&rsquo;s performance and scalability characteristics for core financial functionality.</p>
<p>Multiple different workloads were simulated:</p>
<ul>
<li>A set of financial transactions was run concurrently.</li>
<li>A batch of 500 general ledger (GL) journals of 1,000 journal lines each was posted in a separate test.</li>
<li>A set of management reporter reports and scenarios was executed.</li>
<li>A year-end closing process was run in a separate test.</li>
<li>A consolidation process was run in a separate test.</li>
</ul>
<p>The benchmark was modeled by using two financial periods, previous period and current period. It assumes that the customer is in a closing state for the previous period while entering regular business transactions in the current period. For the closing process (for example, the summary trial balance), some transaction data was created in the previous period before the benchmark run. The defined scenarios were run with 500 concurrent financial users who generated load on the Application Object Server (AOS) instances by using the Microsoft Dynamics AX 2012 R3 Benchmark SDK.</p>
<p>The overall benchmark result shows the capability of Microsoft Dynamics AX and Management Reporter to handle core financial scenarios with volume data while maintaining good performance measures.</p>
<p>Please find the benchmark documents here:</p>
<p><a href="https://mbs.microsoft.com/partnersource/northamerica/deployment/documentation/msdax2012r3financialbm">https://mbs.microsoft.com/partnersource/northamerica/deployment/documentation/msdax2012r3financialbm</a><br /><a href="https://mbs.microsoft.com/customersource/northamerica/AX/learning/documentation/msdax2012r3financialbm">https://mbs.microsoft.com/customersource/northamerica/AX/learning/documentation/msdax2012r3financialbm</a></p>
<p><br /><strong>Microsoft Dynamics AX 2012 R3 Day in the Life Benchmark for Retail and Distribution</strong></p>
<p>Microsoft conducted a 24-hour &ldquo;day in the life&rdquo; benchmark of Microsoft Dynamics AX 2012 R3 to measure the application&rsquo;s performance and scalability characteristics when running retail and distribution workloads. The benchmark ran a variety of functional scenarios that use different client and integration technologies, thereby providing a broad view of enterprise resource planning (ERP) workload performance on AX 2012 R3.</p>
<ul>
<li>The initial benchmark run showcased the daytime and nighttime activities at a complex customer installation that has 2,000 stores, each with 10 point of sale (POS) devices. The benchmark modeled stock movement to replenish stores, and also the use of a web storefront built on Windows Communication Foundation (WCF) services, a call center doing order entry, and a mobile app that connected to Retail Server.</li>
<li>The benchmark also simulated having 3,000 concurrent users perform background tasks.</li>
<li>In additional benchmark runs, we increased the volume to 5,000 concurrent users and also increased the volume of POS transactions.</li>
</ul>
<p>These scenarios generated load on an instance of Application Object Server (AOS). In this benchmark, some AOS instances are configured as multiple logical AOS instances in a cluster. Batch processes run simultaneously with online processes but on separate AOS instances.</p>
<p>AX 2012 R3 demonstrated the ability to scale up and scale out on the AOS tier, and on the database server.</p>
<p>AX 2012 R3 also showed the ability to handle a more complex business process flow smoothly. User experience measures were below 2 to 3 seconds for intensive posting operations, and line save operations averaged sub-second response times.</p>
<p>The benchmark results demonstrate the ability of AX 2012 R3 to concurrently handle more complex scenario flows with a very high volume, without compromising performance and scalability for critical business processes.</p>
<p>Please find the detailed documents here:</p>
<p><a href="https://mbs.microsoft.com/partnersource/northamerica/deployment/documentation/msdax2012r3dayretaildisbm">https://mbs.microsoft.com/partnersource/northamerica/deployment/documentation/msdax2012r3dayretaildisbm</a><br /><a href="https://mbs.microsoft.com/customersource/northamerica/AX/learning/documentation/msdax2012r3dayretaildisbm">https://mbs.microsoft.com/customersource/northamerica/AX/learning/documentation/msdax2012r3dayretaildisbm</a></p>
<p>&nbsp;</p>]]></content:encoded>
    </item>
    <item>
      <title>Improving SSRS Report Performance using new R3 features - Part 6</title>
      <link>https://docs.microsoft.com/archive/blogs/axperf/improving-ssrs-report-performance-using-new-r3-features-part-6</link>
      <pubDate>Tue, 06 May 2014 14:30:00 GMT</pubDate>
      <dc:creator><![CDATA[Youxuan Jin]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/axperf/2014/05/06/improving-ssrs-report-performance-using-new-r3-features-part-6/</guid>
      <description><![CDATA[Using SrsReportDataProviderPreProcessTempDB class
In Ax2012, we introduced class...]]></description>
      <content:encoded><![CDATA[<h1>Using SrsReportDataProviderPreProcessTempDB class</h1>
<p>In Ax2012, we introduced class SrsReportDataProviderPreProcess class as&nbsp;the solution for long running SSRS reports that cause SSRS time out. The approach is to process data in Ax session before calling SSRS. The preprocessed data stored in a regular SQL table shared by all user sessions, only to be striped by session id. That approach creates a bottleneck in case of many concurrent user sessions. In R2, we introduced this new class to allow use of tempDB to carry report data across sessions, from Ax data processing session to SSRS data retrieval session. AOS kernel will not purge tempDB table when session end leaving it to application code to clean up the tempDB table. In case of report, this base class will take care of the clean up.</p>
<p>You should stop use the old SrsReportDataProviderPreProcess class but use the new&nbsp;base class&nbsp;instead for long running reports. You will see performance gain in case of multiple concurrent sessions on the same report.</p>
<p>Currently there are many OOB Ax2012 R3 reports already take advantage of this feature. You can check out how they are leverage the feature&nbsp;there. One typical example is TaxListDP class.</p>
<p><a href="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Blogs.Components.WeblogFiles/00/00/00/93/82/8750.classlist.PNG" original-url="http://blogs.msdn.com/cfs-file.ashx/__key/communityserver-blogs-components-weblogfiles/00-00-00-93-82/8750.classlist.PNG"><img src="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Blogs.Components.WeblogFiles/00/00/00/93/82/8750.classlist.PNG" original-url="http://blogs.msdn.com/resized-image.ashx/__size/550x0/__key/communityserver-blogs-components-weblogfiles/00-00-00-93-82/8750.classlist.PNG" alt="" width="325" height="509" border="0" /></a><br /><br /></p>
<p>This R3 feature is implemented in&nbsp;the kernel. The usage is not limited to reporting. Attached is a sample project showing how this kernel feature is used in case of&nbsp;multi-threading in Ax: each thread creates its own instance of a temp table,&nbsp;inserts rows into the table, and passes the table name back to the main thread (crossing session boundary), where&nbsp;contents from each temp table is then merged into one&nbsp;DB&nbsp;table using set-based insert. At the end, the code must despose the temp tables. The code is for demonstraction only. A more practical&nbsp;application to leverage this feature is in&nbsp;batch processing.</p>
<p>&nbsp;<br /><br /></p>
<p>&nbsp;</p>
<h1>Using Dynamic Set-based insert operation</h1>
<p>In AX2012, set based insert operation is part of X++ syntax and is precompiled. That limits its use when filters are dynamic, i.e. taking from end user at run time.</p>
<p>In AX2012, R3 we implemented a DCR that allows constructing a set based insert <em>where</em> clause based on Query range on the fly. This feature allows much wider adoption&nbsp;of set based operation and greatly improve performance when applied.</p>
<p>A good coding example can be seen in TaxListDP.insertTaxListTaxTmpData. It is done using&nbsp;a new static method on Query object. Here is how it is used.</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Query::insert_recordset(taxListTaxTransTmp, insertRecordsetMap, query);</p>
<p>&nbsp;the first parameter is the tempDB table and the second one is the mapping between the tempDB table fields and query fields. See detail in the AOT class source code. Here is the highlight of the row-based old code:</p>
<p>&nbsp;<a href="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Blogs.Components.WeblogFiles/00/00/00/93/82/0361.oldcode.PNG" original-url="http://blogs.msdn.com/cfs-file.ashx/__key/communityserver-blogs-components-weblogfiles/00-00-00-93-82/0361.oldcode.PNG"><img src="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Blogs.Components.WeblogFiles/00/00/00/93/82/0361.oldcode.PNG" original-url="http://blogs.msdn.com/resized-image.ashx/__size/550x0/__key/communityserver-blogs-components-weblogfiles/00-00-00-93-82/0361.oldcode.PNG" alt="" width="481" height="218" border="0" /></a></p>
<p><span style="line-height: 115%; font-family: 'Calibri','sans-serif'; font-size: 11pt; mso-ascii-theme-font: minor-latin; mso-fareast-font-family: SimSun; mso-fareast-theme-font: minor-fareast; mso-hansi-theme-font: minor-latin; mso-bidi-font-family: 'Times New Roman'; mso-bidi-theme-font: minor-bidi; mso-ansi-language: EN-US; mso-fareast-language: ZH-CN; mso-bidi-language: AR-SA;">Without the DCR, we can&rsquo;t change this code to set-based because query range is set by end users. Now, the highlight of the new high performance code is as follow:</span></p>
<p><span style="line-height: 115%; font-family: 'Calibri','sans-serif'; font-size: 11pt; mso-ascii-theme-font: minor-latin; mso-fareast-font-family: SimSun; mso-fareast-theme-font: minor-fareast; mso-hansi-theme-font: minor-latin; mso-bidi-font-family: 'Times New Roman'; mso-bidi-theme-font: minor-bidi; mso-ansi-language: EN-US; mso-fareast-language: ZH-CN; mso-bidi-language: AR-SA;"><a href="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Blogs.Components.WeblogFiles/00/00/00/93/82/2376.newcode.PNG" original-url="http://blogs.msdn.com/cfs-file.ashx/__key/communityserver-blogs-components-weblogfiles/00-00-00-93-82/2376.newcode.PNG"><img src="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Blogs.Components.WeblogFiles/00/00/00/93/82/2376.newcode.PNG" original-url="http://blogs.msdn.com/resized-image.ashx/__size/550x0/__key/communityserver-blogs-components-weblogfiles/00-00-00-93-82/2376.newcode.PNG" alt="" width="575" height="458" border="0" /></a></span></p>
<p><span style="line-height: 115%; font-family: 'Calibri','sans-serif'; font-size: 11pt; mso-ascii-theme-font: minor-latin; mso-fareast-font-family: SimSun; mso-fareast-theme-font: minor-fareast; mso-hansi-theme-font: minor-latin; mso-bidi-font-family: 'Times New Roman'; mso-bidi-theme-font: minor-bidi; mso-ansi-language: EN-US; mso-fareast-language: ZH-CN; mso-bidi-language: AR-SA;"></span></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p><a href="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Components.PostAttachments/00/10/52/31/54/PrivateProject_TempDBTableDCRSample.xpo" original-url="http://blogs.msdn.com/cfs-file.ashx/__key/communityserver-components-postattachments/00-10-52-31-54/PrivateProject_5F00_TempDBTableDCRSample.xpo">PrivateProject_TempDBTableDCRSample.xpo</a></p>]]></content:encoded>
    </item>
    <item>
      <title>Overcoming parameter sniffing issue in Microsoft Dynamics AX 2012-R2 – CU6</title>
      <link>https://docs.microsoft.com/archive/blogs/axperf/overcoming-parameter-sniffing-issue-in-microsoft-dynamics-ax-2012-r2-cu6</link>
      <pubDate>Mon, 29 Jul 2013 16:36:00 GMT</pubDate>
      <dc:creator><![CDATA[Ganas1]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/axperf/2013/07/29/overcoming-parameter-sniffing-issue-in-microsoft-dynamics-ax-2012-r2-cu6/</guid>
      <description><![CDATA[Parameter sniffing is the default behavior that SQL Server uses when compiling parameterized SQL...]]></description>
      <content:encoded><![CDATA[<p><span style="font-size: small;"><span style="font-family: Calibri;">Parameter sniffing is the default behavior that SQL Server uses when compiling parameterized SQL statements. When a stored procedure or a parameterized SQL statement is compiled or recompiled, the parameter values passed for that invocation are "sniffed" and used for cardinality estimation. The net effect is that the plan is optimized as if those specific parameter values were used as literals in the query. This plan is cached and will be reused.&nbsp; </span></span></p>
<p><span style="font-size: small;"><span style="font-family: Calibri;">Some of the AX customers have uneven disparity in data distribution for different Companies/Les (typically one or two companies may have most of the data volume and all other companies have very less data). Under these conditions the cached plan for a specific company/LE may result in very poor performance for another company/LE.&nbsp; </span></span></p>
<p><span style="font-family: Calibri; font-size: small;">It is possible to change how SQL Server is optimizing queries using trace flag 4136 and this may have a positive effect if parameter sniffing is an issue in your system.&nbsp;More often than not the overall performance degraded when trace flag 4136 has been used.&nbsp;</span><span style="font-size: small;"><span style="font-family: Calibri;">Customers should be careful about using the trace flag 4136, as it can result in poor performance in undesired areas as it is trying to &lsquo;Optimize for UNKNOWN&rsquo;. Please evaluate using below described feature instead.</span></span></p>
<p><span style="font-size: small;"><span style="font-family: Calibri;">Many customers/partners brought this to Microsoft Dynamics AX team&rsquo;s attention.&nbsp; The latest cumulative update for R2 (CU6) addresses this by passing 2 kernel parameters PARTITION, DATAAREAID as literals.&nbsp; If you have R2- CU6 installed and if you know you are facing parameter sniffing issues due to uneven data distribution, you could try enabling this feature.</span></span></p>
<p><em><span style="font-size: small;"><span style="font-family: Calibri;">Note: This feature is turned OFF by default.</span></span></em></p>
<p><span style="font-family: Calibri; font-size: small;">When you installed AX2012 &ndash; R2 CU6, it would have created 2 new records in </span><span style="font-family: courier new,courier;">SYSGLOBALCONFIGURATION</span>. <span style="font-family: Calibri; font-size: small;">With names ( <span style="color: #ff6600;">'DATAAREAIDLITERAL'</span>, <span style="color: #ff6600;">'PARTITIONLITERAL'</span>)</span></p>
<p><span style="font-size: small;"><span style="font-family: Calibri;">To turn the feature ON you need to update these records and set the value to 1.&nbsp; Following update will enable the feature on all the AOS.</span></span></p>
<p><span style="font-family: courier new,courier;"><span style="color: #3366ff;">UPDATE</span> SYSGLOBALCONFIGURATION <span style="color: #3366ff;">SET</span> [VALUE] = 1 <span style="color: #3366ff;">WHERE</span> NAME IN ( <span style="color: #ff6600;">'DATAAREAIDLITERAL'</span>, <span style="color: #ff6600;">'PARTITIONLITERAL'</span>)</span></p>
<p><span style="font-size: small;"><span style="font-family: Calibri;">To turn the feature OFF you need to update these records and set the value to 0.&nbsp; </span></span></p>
<p><em><span style="font-size: small;"><span style="font-family: Calibri;">Note: There is no UI available for this and this needs to be turned ON/OFF from SQL.</span></span></em></p>
<p><span style="font-size: small;"><span style="font-family: Calibri;">After updating the value to 1, when you restart the AOS this feature gets enabled.</span></span></p>
<p><span style="font-size: small;"><span style="font-family: Calibri;">Here is a sample parameterized SQL when this feature is OFF. (Captured thru the trace/traceparser.&nbsp; The field list of the select clause bellow is shown truncated)</span></span></p>
<p><span style="font-family: courier new,courier;"><span style="color: #3366ff;">SELECT TOP</span> 1 T1.SALESID,.......,T1.RECID</span></p>
<p><span style="font-family: courier new,courier;"><span style="color: #3366ff;">FROM</span> SALESTABLE T1</span></p>
<p><span style="font-family: courier new,courier;"><span style="color: #3366ff;">WHERE</span> (((PARTITION=?)</span></p>
<p><span style="font-family: courier new,courier;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; AND (DATAAREAID=?))</span></p>
<p><span style="font-family: courier new,courier;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; AND (SALESID=?))</span></p>
<p><span style="font-size: small;"><span style="font-family: Calibri;">Here is a sample parameterized SQL when this feature is ON. (Captured thru the trace/traceparser.&nbsp; The field list of the select clause bellow is shown truncated)</span></span></p>
<p><span style="font-family: courier new,courier;"><span style="color: #3366ff;">SELECT TOP</span> 1 T1.SALESID,.......,T1.RECID</span></p>
<p><span style="font-family: courier new,courier;"><span style="color: #3366ff;">FROM</span> SALESTABLE T1</span></p>
<p><span style="font-family: courier new,courier;"><span style="color: #3366ff;">WHERE</span> (((PARTITION=5637144576)</span></p>
<p><span style="font-family: courier new,courier;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; AND (DATAAREAID=<span style="color: #ff6600;">N'ceu'</span>))</span></p>
<p><span style="font-family: courier new,courier;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; AND (SALESID=?))</span></p>
<p><span style="font-size: small;"><span style="font-family: Calibri;">The internal performance testing of different scenarios did not show any performance regression when the feature is turned on.&nbsp; But before you enable this feature in production, you should test this feature in your test system thoroughly&nbsp;and ensure it works for you.</span></span></p>
<p><span style="font-family: Calibri; font-size: small;">Note: AX32Serv binary (AX32Serv.exe) of CU6 contain the fix.&nbsp; If you are just taking the binary alone, you may have to insert two records into </span><span style="font-family: courier new,courier;">SYSGLOBALCONFIGURATION </span><span style="font-family: Calibri; font-size: small;">with names </span><span style="font-family: courier new,courier;">'DATAAREAIDLITERAL', 'PARTITIONLITERAL' </span><span style="font-size: small;"><span style="font-family: Calibri;">and set the values to 1 to enable this feature.</span></span></p>]]></content:encoded>
    </item>
    <item>
      <title>Improving SSRS Report Performance at Line level – Part 5</title>
      <link>https://docs.microsoft.com/archive/blogs/axperf/improving-ssrs-report-performance-at-line-level-part-5</link>
      <pubDate>Fri, 26 Jul 2013 11:07:00 GMT</pubDate>
      <dc:creator><![CDATA[Youxuan Jin]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/axperf/2013/07/26/improving-ssrs-report-performance-at-line-level-part-5/</guid>
      <description><![CDATA[Inevitably, we can&rsquo;t completely get rid of all line-based data accesses or calculations. So,...]]></description>
      <content:encoded><![CDATA[<p><span style="font-size: small;"><span style="font-family: Calibri;">Inevitably, we can&rsquo;t completely get rid of all line-based data accesses or calculations. So, we discuss here a few ways to improve without change the basic report logic.</span></span></p>
<h2><span style="font-size: medium;"><span style="color: #4f81bd;"><span style="font-family: Cambria;">Remove duplicated DB calls</span></span></span></h2>
<p><span style="font-size: small;"><span style="font-family: Calibri;">Most of the duplicated DB calls are hidden deep in the stack. For example, Vendor report calls DirPartyTable::ElectornicAddressType2primaryFieldId() 4 times (4 db access) per line, from VendTable::email, telefax, telex, and phone methods, respectively. </span></span></p>
<p><span style="font-size: small;"><span style="font-family: Calibri;">The case of duplicated DB call are mostly implicit, such as calling find() method inside a table method. Some of them are cached in the global cache, some are not. You need to make the decision whether we use global cache, or fix it at the report level. (Note that cache approach won&rsquo;t help in cold start case.)&nbsp; &nbsp;</span></span></p>
<p><span style="font-size: small;"><span style="font-family: Calibri;">A way to detect this pattern is to run a report under the tracer and analyze the trace by comparing number of report lines with number of DB method calls in a code path. Say if a report has 100 output lines and a corresponding DB method call count is 200, then you should exam further if the duplicated method call can be reduced.</span></span></p>
<p><span style="font-size: small;"><span style="font-family: Calibri;">More report examples are ProdPickList report, LedgerJournal report, CustInvinoiceSpec report, etc.</span></span></p>
<h2><span style="font-size: medium;"><span style="color: #4f81bd;"><span style="font-family: Cambria;">Refactor Parent Level Operations</span></span></span></h2>
<p><span style="font-size: small;"><span style="font-family: Calibri;">Sometimes, parent level information is retrieved or calculated at the detail level. It causes repeated data access or calculation. An example is that Customer or Company field values are retrieved through CustTrans table method. One way to fix it is to cache the values in global cache. However, adding them to cache will encure system wide impact. Secondly, caching does not help in case of cold start.</span></span></p>
<p><span style="font-size: small;"><span style="font-family: Calibri;">There are two ways to fix it. First, try to sort the query data source by parent level field values, Customer RecID in the above case, and get the new data row only if a change occurs. This way, we can reuse the parent level values in the subsequent lines until parent record changed.&nbsp; </span></span></p>
<p><span style="font-size: small;"><span style="font-family: Calibri;">Another way to fix is to separate parent level values with the details by processing&nbsp;parent level values outside the loop of lines. You can likely use set-based operations at parent level and join the result with the line-based result. Here is an example from CustInvoice report:</span></span></p>
<table style="width: 100%;" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td>
<div>
<p><span style="font-size: x-small;">&nbsp;<strong>while</strong> (queryRun.next())</span></p>
<p><span style="font-size: x-small;">{</span></p>
<p><span style="font-size: x-small;">...</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; summaryAccount = this.getSummaryAccount(custTrans.AccountNum, custTrans.PostingProfile);</span></p>
<p>&nbsp;<span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>if</strong> (dimensionFocusName != '' &amp;&amp; summaryAccount != <strong>0</strong>)</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; dimensionFocusDisplayValue = DimensionStorage::getDisplayValueForFocusName(</span><span style="font-size: x-small;">summaryAccount,</span><span style="font-size: x-small;">&nbsp;&nbsp; dimensionFocusName);</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp; ...</span></p>
<p><span style="font-size: x-small;">}</span></p>
</div>
</td>
</tr>
</tbody>
</table>
<p><span style="font-size: small;"><span style="font-family: Calibri;">In Contoso as an example, CustTran table has 10,000 records. The number of combinations of the two keys, AccountNum and PostingProfile, is just over 100, which is the total number of summaryAccount records. The current code use a Map to cache the result in memory, but it is still a line-based look up.</span></span></p>
<p><span style="font-family: Calibri; font-size: small;">To optimize it, you can save the result of </span>getSummaryAccount() in <span style="font-size: small;"><span style="font-family: Calibri;">a lookup temp table, keyed by AccountNum and PostingProfile. Here are the new steps in the sequence:</span></span></p>
<ol>
<li><span style="font-family: Calibri; font-size: small;">Update&nbsp;the main while loop by removing call to&nbsp;</span>getSummaryAccount()<span style="font-size: small;"><span style="font-family: Calibri;"> method and the logic after to&nbsp;get&nbsp;display names. You need to added AccountNum and PostingProfile in to the temp table and save the field values in the insert method.</span></span></li>
<li><span style="font-size: small;"><span style="font-family: Calibri;">Add&nbsp;a set-based insert after the main loop into the lookup temp table from custInvoiceTmp table, with group by the two key fields. </span></span></li>
<li><span style="font-family: Calibri; font-size: small;">Add&nbsp;a select-while loop to the lookup temp table to call </span>getSummaryAccount<span style="font-family: Calibri;"><span style="font-size: small;"> method and </span>getDisplayValueForFocusName <span style="font-size: small;">method; save the display name in the same table using update. Note that you don&rsquo;t need the map look up anymore. </span></span></li>
<li><span style="font-size: small;"><span style="font-family: Calibri;">Add&nbsp;a set-based update to set the DismensionFocusDisplayValue field in CusInvoiceTmp table by joining the lookup temp table on the key fields. </span></span></li>
<li><span style="font-family: Calibri; font-size: small;">Optional of doing step 3 and 4, you can avoid calling </span>getDisplayValueForFocusName<span style="font-size: small;"><span style="font-family: Calibri;"> method line-by-line in step 3 by duplicate its logic with the set-based update in step 4 above; just add another join. </span></span></li>
</ol>
<p><span style="font-size: small;"><span style="font-family: Calibri;">This pattern applies not only to the parent-child tables, but also to any cases that the field values of a temp table comes from a table with much fewer distinct lines, say with ratio of 1:10 or larger. </span></span></p>
<h2><span style="font-size: medium;"><span style="color: #4f81bd;"><span style="font-family: Cambria;">Fewer Instantiations of Helper Class Objects</span></span></span></h2>
<p><span style="font-size: small;"><span style="font-family: Calibri;">A typical example of this pattern is the use of CurrencyExchangeHelper, which instantiates an object for each line. The cost can become significant. In PurchRanking report for example, it takes up to 60% of total processing time. </span></span></p>
<p><span style="font-size: small;"><span style="font-family: Calibri;">You can try to instantiate one instance for the entire process; and then assign different parameters in the iteration of&nbsp;lines&nbsp;for calling&nbsp;calculations. </span></span></p>
<h2><span style="font-size: medium;"><span style="color: #4f81bd;"><span style="font-family: Cambria;">Skip postLoad Kernel Call</span></span></span></h2>
<p><span style="font-size: small;"><span style="font-family: Calibri;">After a data row is loaded, Ax kernel makes a call back to application postLoad() override method, which will give application the first chance to tweak the data or fire dependency logic. If the table used by your report does&nbsp;have the method override, you can improve the report performance by telling the Ax kernel to skip the method call. Report developers must make the decision to see if the postLoad override method can be skipped in the context of the report. </span></span></p>
<p><span style="font-size: small;"><span style="font-family: Calibri;">In one measurement, we see about 5% of performance improvement just on the method call overhead. The saving could be more significant considering the time spend on&nbsp; processing the method logic.</span></span></p>
<p><span style="font-size: small;"><span style="font-family: Calibri;">Here is the code to show how to do it:</span></span></p>
<table style="width: 100%;" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td>
<div>
<p><span style="font-size: x-small;"><strong>static</strong> <strong>void</strong> Job2(Args _args)</span></p>
<p><span style="font-size: x-small;">{</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; QueryRun&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; queryCreatePayment;</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; Query&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; query;</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; VendTrans&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; vendTrans;</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; QueryBuildDataSource&nbsp;&nbsp;&nbsp; qbds;</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; VendTrans&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; vendTransTable;</span></p>
<p>&nbsp;</p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; query = <strong>new</strong> Query(<strong>queryStr</strong>(VendPaymProposal));</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; qbds = query.dataSourceName('VendTrans');</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; vendTransTable = qbds.getNo();</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; vendTransTable.skipPostLoad(<strong>true</strong>);&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //set it to true to skip the call</span></p>
<p>&nbsp;</p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; queryCreatePayment = <strong>new</strong> QueryRun(query);</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; queryCreatePayment.setRecord(vendTransTable);&nbsp;&nbsp;&nbsp;&nbsp;//Trick is here that we have to set the record</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; <strong>while</strong> (queryCreatePayment.next())</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; {</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; vendTrans = queryCreatePayment.get(<strong>tableNum</strong>(VendTrans));</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; }</span></p>
<p><span style="font-size: x-small;">}</span></p>
</div>
</td>
</tr>
</tbody>
</table>
<p>&nbsp;</p>
<p><span style="font-size: small;"><span style="font-family: Calibri;">And, here is the trace comparison between a normal default trace and the trace with the skipping enabled.&nbsp; The postLoad method here does not do much:</span></span></p>
<p>&nbsp;<a href="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Blogs.Components.WeblogFiles/00/00/00/93/82/1346.report7.png" original-url="http://blogs.msdn.com/cfs-file.ashx/__key/communityserver-blogs-components-weblogfiles/00-00-00-93-82/1346.report7.png"><img src="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Blogs.Components.WeblogFiles/00/00/00/93/82/1346.report7.png" original-url="http://blogs.msdn.com/resized-image.ashx/__size/550x0/__key/communityserver-blogs-components-weblogfiles/00-00-00-93-82/1346.report7.png" alt="" width="412" height="158" border="0" /></a></p>
<p><span style="font-size: small;"><span style="font-family: Calibri;">With skippostLoad set to true, the extra calls are gone:</span></span></p>
<p><span style="font-family: Calibri; font-size: small;">&nbsp;</span>&nbsp;<a href="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Blogs.Components.WeblogFiles/00/00/00/93/82/4201.report5.png" original-url="http://blogs.msdn.com/cfs-file.ashx/__key/communityserver-blogs-components-weblogfiles/00-00-00-93-82/4201.report5.png"><img src="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Blogs.Components.WeblogFiles/00/00/00/93/82/4201.report5.png" original-url="http://blogs.msdn.com/resized-image.ashx/__size/550x0/__key/communityserver-blogs-components-weblogfiles/00-00-00-93-82/4201.report5.png" alt="" width="416" height="86" border="0" /></a></p>
<p><span style="font-size: small;"><span style="font-family: Calibri;">&nbsp;</span></span></p>
<h1><span style="font-size: small;"><span style="font-family: Calibri;">&nbsp;</span></span></h1>]]></content:encoded>
    </item>
    <item>
      <title>Improving SSRS Report Performance with Hybrid of Line-based and Set-based Processing – Part 4</title>
      <link>https://docs.microsoft.com/archive/blogs/axperf/improving-ssrs-report-performance-with-hybrid-of-line-based-and-set-based-processing-part-4</link>
      <pubDate>Tue, 23 Jul 2013 09:58:00 GMT</pubDate>
      <dc:creator><![CDATA[Youxuan Jin]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/axperf/2013/07/23/improving-ssrs-report-performance-with-hybrid-of-line-based-and-set-based-processing-part-4/</guid>
      <description><![CDATA[This pattern applies to a wide range of reports. It is the most applied pattern so far as based on...]]></description>
      <content:encoded><![CDATA[<p><span style="font-size: small;"><span style="font-family: Calibri;">This pattern applies to a wide range of reports. It is the most applied pattern so far as based on our experience. The idea is to isolate line-based code to the minimum and leverage set-based operations to the maximum at the same time. &nbsp;Here is a list of approaches.</span></span></p>
<h2><span style="font-size: medium;"><span style="color: #4f81bd;"><span style="font-family: Cambria;">Working with Dynamic Parameters</span></span></span></h2>
<p><span style="font-size: small;"><span style="font-family: Calibri;">As it is mentioned in an earlier blog (part 1), Ax2012 set-based operations are X++ language elements compiled into SQL at compile time. They do not take, as input, the dynamic query ranges from users at run time. To leverage set-based operations, what you can do is to keep the original line-base loop but move every possible set-based process outside this loop. We improved the trial balance report In Ax2012 R2 this way. </span></span></p>
<p><span style="font-family: Calibri; font-size: small;">In the trial blance report, it first detects of any dynamic parameter is added by calling a new method </span>queryHasRangesOrFilters()<span style="font-size: small;"><span style="font-family: Calibri;">. If it is, the main queryRun.next() loop is executed but only to collect those filtered line ides from GL table. Then, the rest of the process are changed to set-based. </span></span></p>
<p><span style="font-family: Calibri; font-size: small;">&nbsp;</span></p>
<p><span style="font-size: small;"><span style="font-family: Calibri;">The line-by-line loop on queryRun.next() can&rsquo;t be replaced whenever dynamic parameter is enabled. But you should make this loop lean, only to process those that must be line-based and leave the rest to set-based processing. Vend report in Ax 2012 R2 is another example. </span></span></p>
<p><span style="font-size: small;"><span style="font-family: Calibri;">Note: Ax2012 R3 enables set-based insert with dynamic parameters. See&nbsp;post Part 6 on&nbsp;this&nbsp;series for detail.</span></span></p>
<table style="width: 100%;" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td>
<div>
<p><span style="font-size: x-small;"><strong>public</strong> <strong>void</strong> processReport()</span></p>
<p><span style="font-size: x-small;">{</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; QueryRun&nbsp;&nbsp;&nbsp; queryRun;</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; queryRun = <strong>new</strong> QueryRun(this.parmQuery());</span></p>
<p>&nbsp; <span style="font-size: x-small;">&nbsp; <strong>ttsbegin</strong>;</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; <strong>while</strong>(queryRun.next())</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; {</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; vendorListBasicTmp.clear();</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; vendTable = queryRun.get( <strong>tableNum</strong>(VendTable));</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; this.insertIntoTempTable();</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; }</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; <strong>ttscommit</strong>;</span></p>
<p><span style="font-size: x-small;">}</span></p>
<p>&nbsp;</p>
<p><span style="font-size: x-small;"><strong>private</strong> <strong>void</strong> insertIntoTempTable()</span></p>
<p><span style="font-size: x-small;">{</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; vendorListBasicTmp.VendAccount&nbsp; = vendTable.AccountNum;</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; vendorListBasicTmp.Name&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = vendTable.name();</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; vendorListBasicTmp.Phone&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = vendTable.phone();</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; vendorListBasicTmp.Email&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = vendTable.email();</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; vendorListBasicTmp.Telefax&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = vendTable.telefax();</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; vendorListBasicTmp.Telex&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = vendTable.telex();</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; vendorListBasicTmp.InvoiceAccountName&nbsp;&nbsp; = vendTable.invoiceAccountName();</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; vendorListBasicTmp.ItemBuyerGroupId&nbsp;&nbsp;&nbsp;&nbsp; = vendTable.ItemBuyerGroupId;</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; vendorListBasicTmp.Currency&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;= vendTable.currencyName();</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; vendorListBasicTmp.CashDisc&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = vendTable.cashDiscName();</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; vendorListBasicTmp.PriceGroupName&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = vendTable.priceDiscGroupName();</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; vendorListBasicTmp.LineDiscName&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = vendTable.lineDiscName();</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; vendorListBasicTmp.MultiLineDiscName&nbsp;&nbsp;&nbsp; = vendTable.multiLineDiscName();</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; vendorListBasicTmp.EndDiscName&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = vendTable.endDiscName();</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; vendorListBasicTmp.TaxGroupName&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = vendTable.taxGroupName();</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; vendorListBasicTmp.DeliveryMode&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;= vendTable.dlvModeName();</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; vendorListBasicTmp.AccountName&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = vendTable.customerAccountName();</span></p>
<p>&nbsp; <span style="font-size: x-small;">&nbsp; logisticsPostalAddress&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = DirParty::primaryPostalAddress(vendTable.Party);</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; vendorListBasicTmp.Address&nbsp;&nbsp; = logisticsPostalAddress.Address;</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; vendorListBasicTmp.CountyName= LogisticsAddressCounty::find(logisticsPostalAddress.CountryRegionId,logisticsPostalAddress.State,logisticsPostalAddress.County).Name;</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; vendorListBasicTmp.StateName = LogisticsAddressState::name(logisticsPostalAddress.CountryRegionId,logisticsPostalAddress.State);</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; vendorListBasicTmp.CountryName = LogisticsAddressCountryRegion::name(logisticsPostalAddress.CountryRegionId);</span></p>
<p>&nbsp;&nbsp; <span style="font-size: x-small;">vendorListBasicTmp.PaymentTerm = PaymTerm::find(vendTable.PaymTermId).Description;</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp; &nbsp;vendorListBasicTmp.insert();</span></p>
<p><span style="font-size: x-small;">}</span></p>
<p><span style="font-family: Calibri; font-size: x-small;">&nbsp;</span></p>
</div>
</td>
</tr>
</tbody>
</table>
<p>&nbsp;</p>
<p><span style="font-family: Calibri; font-size: small;">All the method calls in </span>insertIntoTempTable<span style="font-size: small;"><span style="font-family: Calibri;">() method are database find() type of methods, which can be expressed as join clauses in a set-based statement. To use set-based data access when dynamic parameter is enabled, you first store all the VentdTable line recIDs, and&nbsp;available field valuess,&nbsp;in the temp table without calling any method. The second step is to do set-based update over the first results to join with other tables to get the rest of the field values.</span></span></p>
<p><span style="font-size: small;"><span style="font-family: Calibri;">Please note that replacing a line-base table display method duplicates the business logic in the method code in the form of set-based expressions. Such denormalization is a tradeoff for performance. Developers need to make the call based on the each scenario. </span></span></p>
<h2><span style="font-size: medium;"><span style="color: #4f81bd;"><span style="font-family: Cambria;">Table Methods of Database Operation</span></span></span></h2>
<p><span style="font-size: small;"><span style="font-family: Calibri;">As mentioned above in Vend report, a table method of database operation, such as look up or aggregate, can be replaced with a join clause in the set-based operation. One typical example is find() method that most tables have.&nbsp; Basically, the loop up logic in the table function needs to be repeated by an exist-join clause in the set-based statement, such as this code snip from VendAccruedPurchasesDP_NA.</span></span></p>
<table style="width: 100%;" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td>
<div>
<p><span style="font-family: Calibri; font-size: x-small;">physicalDate = AccountingEvent::<strong>findByAccountingEventType(</strong></span><span style="font-family: Calibri; font-size: x-small;">SourceDocumentLine<strong>::find(</strong>vendPackingSlipTrans.SourceDocumentLine).SourceDocumentHeader,</span><span style="font-family: Calibri; font-size: x-small;"> AccountingEventType::Original).AccountingDate;</span></p>
<p><span style="font-family: Calibri; font-size: x-small;">financialDate = AccountingEvent::<strong>findByAccountingEventType</strong>(</span><span style="font-family: Calibri; font-size: x-small;">SourceDocumentLine::<strong>find</strong>( vendPackingSlipTrans.SourceDocumentLine).SourceDocumentHeader,</span><span style="font-family: Calibri; font-size: x-small;"> AccountingEventType::Finalize).AccountingDate;</span></p>
<p><span style="font-family: Calibri; font-size: x-small;">if ( (physicalDate &amp;&amp; physicalDate &lt;= cutOffDate) &amp;&amp;</span><span style="font-family: Calibri; font-size: x-small;">&nbsp; (financialDate &gt; cutOffDate || !financialDate))</span></p>
<p><span style="font-family: Calibri; font-size: x-small;">{</span></p>
<p><span style="font-family: Calibri; font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; bSend&nbsp; = true;</span></p>
<p><span style="font-family: Calibri; font-size: x-small;">}</span></p>
<p><span style="font-family: Calibri; font-size: x-small;">else</span></p>
<p><span style="font-family: Calibri; font-size: x-small;">{</span></p>
<p><span style="font-family: Calibri; font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if ( (physicalDate &gt; cutOffDate&nbsp;&nbsp; || !physicalDate) &amp;&amp;</span><span style="font-family: Calibri; font-size: x-small;">&nbsp; (financialDate &lt;= cutOffDate &amp;&amp; financialDate))</span></p>
<p><span style="font-family: Calibri; font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {</span></p>
<p><span style="font-family: Calibri; font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; bSend = true;</span></p>
<p><span style="font-family: Calibri; font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</span></p>
<p><span style="font-family: Calibri; font-size: x-small;">}</span></p>
</div>
</td>
</tr>
</tbody>
</table>
<p><span style="font-family: Calibri;"><span style="font-size: small;">The look up and the subsequent comparison need to be converted to table join clauses of the insert_recordset statement, such as this:</span> </span></p>
<table style="width: 100%;" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td>
<div>
<p><span style="font-family: Calibri;">&nbsp; #LOCALMACRO.OrigAndFinalizeExist</span></p>
<p><span style="font-family: Calibri;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>exists join</strong> origEvent where</span></p>
<p><span style="font-family: Calibri;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; origEvent.SourceDocumentHeader == vendPackingSlipTransSDL.SourceDocumentHeader &amp;&amp;</span></p>
<p><span style="font-family: Calibri;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; origEvent.Type == AccountingEventType::Original</span></p>
<p><span style="font-family: Calibri;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>exists join</strong> finalEvent where</span></p>
<p><span style="font-family: Calibri;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; finalEvent.SourceDocumentHeader == vendPackingSlipTransSDL.SourceDocumentHeader &amp;&amp;</span></p>
<p><span style="font-family: Calibri;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; finalEvent.Type == AccountingEventType::Finalize &amp;&amp;</span></p>
<p><span style="font-family: Calibri;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ((origEvent.AccountingDate &lt;= cutOffDate &amp;&amp;</span></p>
<p><span style="font-family: Calibri;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; finalEvent.AccountingDate &gt; cutOffDate) ||</span></p>
<p><span style="font-family: Calibri;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (origEvent.AccountingDate &gt; cutOffDate &amp;&amp;</span></p>
<p><span style="font-family: Calibri;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; finalEvent.AccountingDate &lt;= cutOffDate))</span></p>
</div>
</td>
</tr>
</tbody>
</table>
<p>&nbsp;<span style="font-size: medium;"><span style="color: #4f81bd;"><span style="font-family: Cambria;">Set-based approach in the Nested Loops</span></span></span></p>
<p><span style="font-size: small;"><span style="font-family: Calibri;">Many report RDP classes have nested loops. The outer loop iterates over the result of queryRun object line-by-line. And then for each line there is a nested inner loop, which is mostly un-related to the dynamic parameter as the outer loop does. In most cases, such a nested loop can be in set-based to reduce the X++ code complicity from O(n*n) to O(n). </span></span></p>
<p><span style="font-size: small;"><span style="font-family: Calibri;">Report examples of this type are CustBillofExchange, TaxPurchLedger, CustLedgerTrans, etc.</span></span></p>
<p><span style="font-size: small;"><span style="font-family: Calibri;">Sometimes, the nested loop is wrapped in a private method call.&nbsp; Here is an example from LedgerTransStatementDP class:</span></span></p>
<p>&nbsp;&nbsp;</p>
<table style="width: 100%;" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td>
<div>
<p><span style="font-size: x-small;"><strong>private</strong> LedgerJournalAC getOffsetAccount(TmpDimTransExtract _tmpDimTransExtract)</span></p>
<p><span style="font-size: x-small;">{</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp; ...</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; <strong>while</strong> <strong>select</strong> MainAccountId <strong>from</strong> mainAccount</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>group</strong> <strong>by</strong> mainAccount.MainAccountId</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; <strong>where</strong> mainAccount.MainAccountId != _tmpDimTransExtract.AccountMain</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;&amp; mainAccount.LedgerChartOfAccounts == LedgerChartOfAccounts::current()</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; <strong>exists</strong> <strong>join</strong> dimensionAttributeValueCombination</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>where</strong> dimensionAttributeValueCombination.MainAccount == mainAccount.RecId</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; <strong>exists</strong> <strong>join</strong> generalJournalAccountEntry</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>where</strong> dimensionAttributeValueCombination.RecId == generalJournalAccountEntry.LedgerDimension</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; <strong>exists</strong> <strong>join</strong> generalJournalEntry</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>where</strong> generalJournalEntry.RecId == generalJournalAccountEntry.GeneralJournalEntry</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;&amp; generalJournalEntry.Ledger == Ledger::current()</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; <strong>exists</strong> <strong>join</strong> subledgerVoucherGeneralJournalEntry</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>where</strong> subledgerVoucherGeneralJournalEntry.GeneralJournalEntry == generalJournalEntry.RecId</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;&amp; subledgerVoucherGeneralJournalEntry.Voucher == _tmpDimTransExtract.Voucher</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;&amp; subledgerVoucherGeneralJournalEntry.AccountingDate == _tmpDimTransExtract.TransDate</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; {</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <em>// An account was found that is not the account on the current calculation group.</em></span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <em>// Add the offset account to the set. Because it's a set each unique account is only added once.</em></span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; offsetAccountSet.add(mainAccount.MainAccountId);</span></p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-size: x-small;">&nbsp; <strong>if</strong> (offsetAccountSet.elements() &gt; <strong>3</strong>)</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <em>// Use 'Multiple' when there are more than 3 offset accounts.</em></span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; offsetAccount = "@SYS78137";</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>break</strong>;</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; }</span></p>
<p>&nbsp;</p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; <strong>if</strong> (offsetAccount == '')</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; {</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <em>// Generate the string to display with the offset accounts that were found.</em></span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; setEnumerator = offsetAccountSet.getEnumerator();</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>while</strong> (setEnumerator.moveNext())</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>if</strong> (<strong>strLen</strong>(offsetAccount) != <strong>0</strong>)</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; offsetAccount += ', ';</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;offsetAccount += setEnumerator.current();</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; }</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; <strong>return</strong> <span style="background-color: #ffff00;">offsetAccount</span>;</span></p>
<p><span style="font-size: x-small;">}</span></p>
</div>
</td>
</tr>
</tbody>
</table>
<p><span style="font-size: small;"><span style="font-family: Calibri;">This method is called per line. The equivalent set-based code as implemented in Ax2012 R2 appears quite different:</span></span></p>
<table style="width: 100%;" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td>
<div>
<p><span style="font-size: x-small;"><strong>private</strong> <strong>void</strong> processOffsetAccount(LedgerTransStatementTmp _ledgerTransStatementTmp)</span></p>
<p><span style="font-size: x-small;">{</span></p>
<p><span style="font-size: x-small;">LedgerTransStatementOffsetAccountTmp&nbsp;&nbsp;&nbsp; <span style="background-color: #ffff00;">offsetAccountTmp</span>;</span></p>
<p><span style="font-size: x-small;">...</span></p>
<p><span style="font-size: x-small;">&nbsp;#LOCALMACRO.JoinGJAE&nbsp;&nbsp;&nbsp;</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; <strong>exists</strong> <strong>join</strong> dimensionAttributeValueCombination</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>where</strong> dimensionAttributeValueCombination.MainAccount == mainAccount.RecId</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; <strong>exists</strong> <strong>join</strong> generalJournalAccountEntry</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>where</strong> generalJournalAccountEntry.LedgerDimension == dimensionAttributeValueCombination.RecId</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;&amp; generalJournalAccountEntry.GeneralJournalEntry == offsetAccountTmp.GeneralJournalEntry</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;&amp; generalJournalAccountEntry.RecId != offsetAccountTmp.GeneralJournalAccountEntry;</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; ENDMACRO</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; this.setUserConnection(offsetAccountTmp);</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; <em>// Start by pushing all data to process into a seperate temp table where</em></span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; <em>// the individual account values can be dropped into individual columns</em></span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; <strong>insert_recordset</strong> <span style="background-color: #ffff00;">offsetAccountTmp</span> (GeneralJournalEntry, GeneralJournalAccountEntry)</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>select</strong> GeneralJournalEntry, GeneralJournalAccountEntry <strong>from</strong> _ledgerTransStatementTmp</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>where</strong> _ledgerTransStatementTmp.createdTransactionId == this.parmCreatedTransactionId();</span></p>
<p>&nbsp;<span style="font-size: x-small;">&nbsp;&nbsp; <em>// Fill in the first offset account (where possible)</em></span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; <strong>update_recordSet</strong> <span style="background-color: #ffff00;">offsetAccountTmp</span></span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>setting</strong> OffsetAcct1 = mainAccount.MainAccountId</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>join</strong> mainAccount</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #JoinGJAE</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; <em>// Fill in the second offset account (where possible)</em></span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; <strong>update_recordSet</strong> <span style="background-color: #ffff00;">offsetAccountTmp</span></span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>setting</strong> OffsetAcct2 = mainAccount.MainAccountId</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>join</strong> mainAccount</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>where</strong> mainAccount.MainAccountId != offsetAccountTmp.OffsetAcct1</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #JoinGJAE</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; <em>// Fill in the third offset account (where possible)</em></span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; <strong>update_recordSet</strong> <span style="background-color: #ffff00;">offsetAccountTmp</span></span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>setting</strong> OffsetAcct2 = mainAccount.MainAccountId</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>join</strong> mainAccount</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>where</strong> mainAccount.MainAccountId != offsetAccountTmp.OffsetAcct1</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;&amp; mainAccount.MainAccountId != offsetAccountTmp.OffsetAcct2</span></p>
<p><span style="font-size: x-small;">&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#JoinGJAE</span></p>
<p>&nbsp;</p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; <em>// Remove all cases where more than three accounts exist</em></span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; <strong>update_recordSet</strong> <span style="background-color: #ffff00;">offsetAccountTmp </span><strong>setting</strong></span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; IsMoreThanThree = NoYes::Yes,</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; OffsetDescription = "@SYS78137" <em>// Multiple</em></span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>exists</strong> <strong>join</strong> mainAccount</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;<strong>where</strong> mainAccount.MainAccountId != offsetAccountTmp.OffsetAcct1</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;&amp; mainAccount.MainAccountId != offsetAccountTmp.OffsetAcct2</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;&amp; mainAccount.MainAccountId != offsetAccountTmp.OffsetAcct3</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #JoinGJAE</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; <em>// Concatenate the values into a total</em></span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; <strong>update_recordset</strong> <span style="background-color: #ffff00;">offsetAccountTmp </span><strong>setting</strong></span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; OffsetDescription = offsetAccountTmp.OffsetAcct1</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>where</strong> offsetAccountTmp.OffsetAcct1</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;&amp; offsetAccountTmp.IsMoreThanThree == NoYes::No;</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; <strong>update_recordset</strong> <span style="background-color: #ffff00;">offsetAccountTmp </span><strong>setting</strong></span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; OffsetDescription = offsetAccountTmp.OffsetDescription + ',' + offsetAccountTmp.OffsetAcct2</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>where</strong> offsetAccountTmp.OffsetAcct2</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;&amp; offsetAccountTmp.IsMoreThanThree == NoYes::No;</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; <strong>update_recordset</strong> <span style="background-color: #ffff00;">offsetAccountTmp </span><strong>setting</strong></span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; OffsetDescription = offsetAccountTmp.OffsetDescription + ',' + offsetAccountTmp.OffsetAcct3</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>where</strong> offsetAccountTmp.OffsetAcct3</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;&amp; offsetAccountTmp.IsMoreThanThree == NoYes::No;</span></p>
<p>&nbsp;<span style="font-size: x-small;">&nbsp;&nbsp; <em>// Copy the descriptions back to the source table</em></span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; <strong>update_recordSet</strong> _ledgerTransStatementTmp <strong>setting</strong></span></p>
<p><span style="font-size: x-small;"><span style="background-color: #ffff00;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; OffsetAccount</span> = offsetAccountTmp.OffsetDescription</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>where</strong> _ledgerTransStatementTmp.createdTransactionId == this.parmCreatedTransactionId()</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>join</strong> offsetAccountTmp</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>where</strong> offsetAccountTmp.GeneralJournalAccountEntry == _ledgerTransStatementTmp.GeneralJournalAccountEntry</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;&amp; offsetAccountTmp.GeneralJournalEntry == _ledgerTransStatementTmp.GeneralJournalEntry;</span></p>
<p><span style="font-size: x-small;">}</span></p>
</div>
</td>
</tr>
</tbody>
</table>
<p>&nbsp;</p>
<p><span style="font-family: Calibri; font-size: small;">Notice the use of a temp table, </span>LedgerTransStatementOffsetAccountTmp<span style="font-size: small;"><span style="font-family: Calibri;">, which stores the intermediate result. Such a table is often needed for set-based approach.</span></span></p>
<p><span style="font-size: small;"><span style="font-family: Calibri;">The set-based approach&nbsp;is not straightforward, but it is much faster than the line-based method call.</span></span></p>
<h2><span style="font-size: medium;"><span style="color: #4f81bd;"><span style="font-family: Cambria;">Aggregation and Sequencing</span></span></span></h2>
<p><span style="font-size: small;"><span style="font-family: Calibri;">In a line-based processing loop, it is easy to aggregate a total or a rolling total in a variable. It is done differently in the set-based approach.</span></span></p>
<p><span style="font-size: small;"><span style="font-family: Calibri;">To get the summary in the temp table, it is often done in two steps: inserting all the detail lines first and followed by inserting summary lines in the same table. You can&nbsp;add a flag&nbsp;field to the table, such as isFinalRecord, to separate the aggregated values with detail values. In the end, you&nbsp;delete all the detail lines before rendering the report. In LedgerTransStatement report (R2 code), the set based operations are in stages: retrieve and process the raw detail records, and then grouping. In the end in recorderRows() method, only aggregated lines are kept:</span></span></p>
<table style="width: 100%;" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td>
<div>
<p>&nbsp;&nbsp;&nbsp; <span style="font-size: x-small;"><em>// Remove the original records that created the balances</em></span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; <strong>delete_from</strong> newTmp</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>where</strong> newTmp.IsFinalRecord == NoYes::No</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;&amp; newTmp.createdTransactionId == this.parmCreatedTransactionId();</span>&nbsp;</p>
</div>
</td>
</tr>
</tbody>
</table>
<p>&nbsp;</p>
<p><span style="font-size: small;"><span style="font-family: Calibri;">A rolling total, or a current amount, depends on the order of the result set. We can define an incremental id field to tag the temp table based on the order-by. Then do set-based with aggregation on those that has the id value smaller than the current row. </span></span></p>
<p><span style="font-size: small;"><span style="font-family: Calibri;">See Ax6.2 code example in LedgerTrialBalanceDP.updateAccumulatedAmounts():</span></span></p>
<table style="width: 100%;" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td>
<div>
<p><span style="font-size: x-small;"><strong>private</strong> <strong>void</strong> updateAccumulatedAmounts(LedgerTrialBalanceStagingTmp _ledgerTrialBalanceStagingTmp)</span></p>
<p><span style="font-size: x-small;">{</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; LedgerTrialBalanceTmpAccumulated&nbsp;&nbsp;&nbsp; tmpAccumulated;</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; LedgerTrialBalanceStagingTmp&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ledgerTrialBalanceStagingTmpReference;</span></p>
<p>&nbsp;</p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; this.setUserConnection(ledgerTrialBalanceStagingTmpReference);</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; ledgerTrialBalanceStagingTmpReference.linkPhysicalTableInstance(_ledgerTrialBalanceStagingTmp);</span></p>
<p>&nbsp;</p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; this.setUserConnection(tmpAccumulated);</span></p>
<p>&nbsp;</p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; <strong>insert_recordset</strong> tmpAccumulated (LedgerTrialBalanceTmp, AccumulatedDebit, AccumulatedCredit)</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>select</strong> RecId <strong>from</strong> _ledgerTrialBalanceStagingTmp</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>group</strong> <strong>by</strong> _ledgerTrialBalanceStagingTmp.RecId</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>join</strong> <span style="background-color: #ffff00;"><strong>sum</strong>(AmountDebitTrans), <strong>sum</strong>(AmountCreditTrans)</span> <strong>from</strong> ledgerTrialBalanceStagingTmpReference</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>where</strong> ledgerTrialBalanceStagingTmpReference.PrimaryFocus ==</span><span style="font-size: x-small;">&nbsp; _ledgerTrialBalanceStagingTmp.PrimaryFocus</span></p>
<p><span style="font-size: x-small; background-color: #ffff00;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;&amp; ledgerTrialBalanceStagingTmpReference.RecId &lt;= _ledgerTrialBalanceStagingTmp.RecId;</span></p>
<p>&nbsp;</p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; <strong>update_recordSet</strong> _ledgerTrialBalanceStagingTmp <strong>setting</strong></span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Accumulated = tmpAccumulated.AccumulatedDebit - tmpAccumulated.AccumulatedCredit</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>join</strong> tmpAccumulated</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>where</strong> tmpAccumulated.LedgerTrialBalanceTmp == _ledgerTrialBalanceStagingTmp.RecId;</span></p>
<p>&nbsp;</p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; <em>// Clear out the AmountDebitTrans and AmountCreditTrans from opening transactions</em></span></p>
<p><span style="font-size: x-small;">&nbsp; &nbsp;&nbsp;<strong>update_recordSet</strong> _ledgerTrialBalanceStagingTmp <strong>setting</strong></span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; OpeningBalance = _ledgerTrialBalanceStagingTmp.AmountDebitTrans - _ledgerTrialBalanceStagingTmp.AmountCreditTrans,</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; AmountDebitTrans = <strong>0</strong>,</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; AmountCreditTrans = <strong>0</strong></span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; <strong>where</strong> _ledgerTrialBalanceStagingTmp.TransactionType == FiscalPeriodType::Opening;</span></p>
<p><span style="font-size: x-small;">}</span></p>
</div>
</td>
</tr>
</tbody>
</table>
<p>&nbsp;</p>
<h2><span style="font-size: medium;"><span style="color: #4f81bd;"><span style="font-family: Cambria;">Working with Complicated Code Logic</span></span></span></h2>
<p><span style="font-size: small;"><span style="font-family: Calibri;">We already know that some line-based method calls contains only database look up logic. They can be combined into join clauses in the set-based operations. However, whenever there is line-based code logic, it is often hard or impossible to rewrite it into set-based without affecting fundamental logic of the report and data schema. Examples are rounding and exchange rate code. They will remain as line-based.</span></span></p>
<p><span style="font-size: small;"><span style="font-family: Calibri;">For example, display method InventTable.itemName() has code logic that can&rsquo;t be in set-based.&nbsp; Reports that use it must keep the call in the line-based loop. The general task for developers is to identify what code can be in set-based and what must stay as line-based.</span></span></p>]]></content:encoded>
    </item>
    <item>
      <title>Improving SSRS Query-based Report Performance by not Using Display Methods - Part 3</title>
      <link>https://docs.microsoft.com/archive/blogs/axperf/improving-ssrs-query-based-report-performance-by-not-using-display-methods-part-3</link>
      <pubDate>Tue, 23 Jul 2013 09:19:00 GMT</pubDate>
      <dc:creator><![CDATA[Youxuan Jin]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/axperf/2013/07/23/improving-ssrs-query-based-report-performance-by-not-using-display-methods-part-3/</guid>
      <description><![CDATA[This post is about Query based reports in Ax2012. Ax2012 supports table display method in query...]]></description>
      <content:encoded><![CDATA[<p><span style="font-size: small;"><span style="font-family: Calibri;">This post is about Query based reports in Ax2012. Ax2012 supports table display method in query based reports. It is convenient to use. For example, the query for VendDueReportDetail is on VendTable and looks like this in VS designer:</span></span></p>
<p>&nbsp;<a href="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Blogs.Components.WeblogFiles/00/00/00/93/82/8055.report3.png" original-url="http://blogs.msdn.com/cfs-file.ashx/__key/communityserver-blogs-components-weblogfiles/00-00-00-93-82/8055.report3.png"><img src="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Blogs.Components.WeblogFiles/00/00/00/93/82/8055.report3.png" original-url="http://blogs.msdn.com/resized-image.ashx/__size/550x0/__key/communityserver-blogs-components-weblogfiles/00-00-00-93-82/8055.report3.png" alt="" border="0" /></a></p>
<p><span style="font-size: small;"><span style="font-family: Calibri;">The table display methods above look up VendInvoiceJour and VendTrans tables to get the last dates respectively. </span></span></p>
<p><span style="font-size: small;"><span style="font-family: Calibri;">However, there is a performance cost associated with this convenience. The call to a table display method is line-based. When a report is critical in performance, you can make it run faster by replaying the display method calls with Ax Views. </span></span></p>
<p><span style="font-size: small;"><span style="font-family: Calibri;">For the above report, we added two views, with aggregation, that duplicate what the display methods do.&nbsp;&nbsp;Please See xpo attatchment for one of them. The other&nbsp;view is almost the same.&nbsp;In one test case, this particular change improved the db access time from 0.42&rdquo; to 0.05&rdquo;. Here is the&nbsp;AOT of&nbsp;one&nbsp;view.</span></span></p>
<p><span style="font-family: Calibri; font-size: small;">&nbsp;<a href="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Blogs.Components.WeblogFiles/00/00/00/93/82/2043.report4.png" original-url="http://blogs.msdn.com/cfs-file.ashx/__key/communityserver-blogs-components-weblogfiles/00-00-00-93-82/2043.report4.png"><img src="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Blogs.Components.WeblogFiles/00/00/00/93/82/2043.report4.png" original-url="http://blogs.msdn.com/resized-image.ashx/__size/550x0/__key/communityserver-blogs-components-weblogfiles/00-00-00-93-82/2043.report4.png" alt="" border="0" /></a></span></p>
<p><span style="font-size: small;"><span style="font-family: Calibri;">&nbsp;</span></span></p>
<p><span style="font-size: small;"><span style="font-family: Calibri;">This pattern of improvement makes sense for the display method that is database bound and the logic in the display method can be expressed in views. For example, display method InventTable.itemName() can&rsquo;t be expressed in views.</span></span></p>
<p><span style="font-size: small;"><span style="font-family: Calibri;">Please consider using calculated fields in Views before using display method. Calculated fields will be translated into SQL expression as part of target SQL statement, instead of being executed row-by-row in AOS.</span></span></p>
<p><span style="font-size: small;"><span style="font-family: Calibri;"></span></span></p>
<p><a href="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Components.PostAttachments/00/10/43/62/11/View_VendInvoiceJourLastInvoiceDate.xpo" original-url="http://blogs.msdn.com/cfs-file.ashx/__key/communityserver-components-postattachments/00-10-43-62-11/View_5F00_VendInvoiceJourLastInvoiceDate.xpo">View_VendInvoiceJourLastInvoiceDate.xpo</a></p>]]></content:encoded>
    </item>
    <item>
      <title>Improving SSRS Report Performance by Applying Filters/Ranges Earlier - Part 2</title>
      <link>https://docs.microsoft.com/archive/blogs/axperf/improving-ssrs-report-performance-by-applying-filtersranges-earlier-part-2</link>
      <pubDate>Fri, 19 Jul 2013 16:46:00 GMT</pubDate>
      <dc:creator><![CDATA[Youxuan Jin]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/axperf/2013/07/19/improving-ssrs-report-performance-by-applying-filtersranges-earlier-part-2/</guid>
      <description><![CDATA[We post it second because it is a low hanging fruit for better performance. One example helps...]]></description>
      <content:encoded><![CDATA[<p><span style="font-size: small;"><span style="font-family: Calibri;">We post it second because it is a low hanging fruit for better performance. One example helps explains this pattern. Here is a code excerpt from CustTransOpenPerDate report. </span></span></p>
<table style="width: 100%;" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td>
<div>
<p><span style="font-size: x-small;">perDate = contract.parmPerDate();</span></p>
<p><span style="font-size: x-small;"><strong>while</strong> (queryRun.next())</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; {</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>if</strong> (queryRun.changed(<strong>tablenum</strong>(CustTable)))</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; custTable = queryRun.get(<strong>tablenum</strong>(CustTable));</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; custTrans = queryRun.get(<strong>tablenum</strong>(CustTrans));</span></p>
<p>&nbsp;</p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>if</strong> (custTrans.TransDate &lt;= perDate&nbsp;&nbsp; &amp;&amp;</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (custTrans.Closed&nbsp;&nbsp; == dateNull() ||</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; custTrans.Closed&nbsp;&nbsp; &gt; perDate))</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &hellip;</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; this.insertCustTransOpenPerDateTmp();</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; }</span></p>
<p><span style="font-family: Calibri; font-size: x-small;">&nbsp;</span></p>
</div>
</td>
</tr>
</tbody>
</table>
<p><span style="font-size: small;"><span style="font-family: Calibri;">In one case, there are over 8000 CustTrans lines fetched but only 41 lines are added to the report. In this example, more lines are retrieved less restrictively and filtered later in code instead. Most lines are discarded anyway, resulting in excessively unnecessary DB access. </span></span></p>
<p><span style="font-size: small;"><span style="font-family: Calibri;">The fix is to remove the if-condition inside the main loop and add perDate to the queryRun object data range. </span></span></p>
<table style="width: 100%;" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td>
<div>
<p><span style="font-size: x-small;">perDate = contract.parmPerDate();</span></p>
<p><span style="font-size: x-small;">&hellip;</span></p>
<p><span style="font-size: x-small; background-color: #ffff00;"><em>// new code:</em></span></p>
<p><span style="font-size: x-small; background-color: #ffff00;">qbdsCustTrans = SysQuery::findOrCreateDataSource(queryRun.query(), tableNum(CustTrans));</span></p>
<p><span style="font-size: x-small; background-color: #ffff00;">qbdsCustTrans.addRange(fieldNum(CustTrans, TransDate)).value(queryRange(null, perDate));</span></p>
<p><span style="font-size: x-small; background-color: #ffff00;">qbdsCustTrans.addRange(fieldNum(CustTrans, Closed)).value(queryValue(dateNull()));</span></p>
<p><span style="font-size: x-small; background-color: #ffff00;">qbdsCustTrans.addRange(fieldNum(CustTrans, Closed)).value(strFmt('&gt;%1', queryValue(perDate)));</span></p>
<p><span style="font-size: x-small;"><strong>&nbsp;</strong></span></p>
<p><span style="font-size: x-small;"><strong>while</strong> (queryRun.next())</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; {</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>if</strong> (queryRun.changed(<strong>tablenum</strong>(CustTable)))</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; custTable = queryRun.get(<strong>tablenum</strong>(CustTable));</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // delete old code</span></p>
<p><span style="font-size: x-small; text-decoration: line-through;">&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;custTrans = queryRun.get(<strong>tablenum</strong>(CustTrans));</span></p>
<p><span style="font-size: x-small; text-decoration: line-through;">&nbsp;</span></p>
<p><span style="font-size: x-small; text-decoration: line-through;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>if</strong> (custTrans.TransDate &lt;= perDate&nbsp;&nbsp; &amp;&amp;</span></p>
<p><span style="font-size: x-small; text-decoration: line-through;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (custTrans.Closed&nbsp;&nbsp; == dateNull() ||</span></p>
<p><span style="font-size: x-small; text-decoration: line-through;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; custTrans.Closed&nbsp;&nbsp; &gt; perDate))</span></p>
<p><span style="font-size: x-small; text-decoration: line-through;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &hellip;</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; this.insertCustTransOpenPerDateTmp();</span></p>
<p><span style="font-size: x-small; text-decoration: line-through;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; }</span><span style="font-family: Calibri; font-size: x-small;">&nbsp;</span></p>
</div>
</td>
</tr>
</tbody>
</table>
<p>&nbsp;</p>
<p><span style="font-size: small;"><span style="font-family: Calibri;">The key thing to look for this pattern is to see if every iteration of the main loop results in one or more insert/update to the report data temp table. If not, then you need to identify skipping conditions and see if the conditions can be represented in terms of the main loop query range expression.</span></span></p>
<p><span style="font-size: small;"><span style="font-family: Calibri;">Ax2012 R2 improved CustDueReportDetail report this way. Here are the high light of the old code. You can test yourself to see if how you would improve it using this pattern. </span></span></p>
<table style="width: 100%;" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td>
<div>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; <strong>while</strong>(queryRun.next())</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; {</span></p>
<p>&nbsp;<span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <em>// deleted irrelevant code here</em></span></p>
<p>&nbsp;<span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>while</strong> <strong>select</strong> SettleAmountCur, DueDate, UtilizedCashDisc, TransDate</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>from</strong> custSettlement</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>where</strong> custSettlement.TransRecId == custTrans.RecId &amp;&amp;</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ((asOfDate &amp;&amp; custSettlement.TransDate &gt; asOfDate) || !asOfDate)</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>if</strong>(!(setRecord.in(custTrans.RecId) || setRecord.in(custTrans.OffsetRecid)))</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;transDate = asOfDate ? custTrans.TransDate : custSettlement.TransDate;</span></p>
<p>&nbsp;<span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <em>// deleted irrelevant code here</em></span></p>
<p><span style="font-size: x-small;"><em>&nbsp;</em></span><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>if</strong>(!(asOfDate &amp;&amp; transDate &gt;= asOfDate))</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; this.insertCustDueReportDetailTmp(</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; custSettlement.SettleAmountCur,</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; asOfDate &amp;&amp; custTrans.DueDate ? custTrans.DueDate : custSettlement.DueDate,</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; custSettlement.UtilizedCashDisc,</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;transDate);</span></p>
<p>&nbsp;<span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</span></p>
<p>&nbsp;<span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <em>// deleted irrelevant code here</em></span></p>
<p><span style="font-size: x-small;"><em>&nbsp;</em></span><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>while</strong> <strong>select</strong> AmountCur, DueDate, PossibleCashDisc, TransDate</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>from</strong> custTransOpen</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<strong>where</strong> custTransOpen.RefRecId == custTrans.RecId &amp;&amp;</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ((asOfDate &amp;&amp; custTransOpen.TransDate &lt;= asOfDate) || !asOfDate)</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; this.insertCustDueReportDetailTmp(</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;custTransOpen.AmountCur,</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; custTransOpen.DueDate,</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; custTransOpen.PossibleCashDisc,</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; custTransOpen.TransDate);</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; }</span></p>
</div>
</td>
</tr>
</tbody>
</table>
<p>&nbsp;</p>
<p><span style="font-size: small;"><span style="font-family: Calibri;">Another case in this pattern is&nbsp;trying to do aggregation in SQL instead of in X++ code.&nbsp;LedgerAccountBlanceSheetByDim report can be re-written in set-based, but the line-based fix in this pattern also yields huge performance gain:</span></span></p>
<p><span style="font-family: Calibri; font-size: small;">In method LedgerAccountBlanceSheetByDim.</span>prepareTmpTableData()<span style="font-size: small;"><span style="font-family: Calibri;">, a while loop iterates over GJAE table to total up the amount. </span></span></p>
<table style="width: 100%;" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td>
<div>
<p><span style="font-size: x-small;"><strong>while</strong> <strong>select</strong> AccountingCurrencyAmount, isCredit <strong>from</strong> generalJournalAccountEntry</span></p>
<p><span style="font-size: x-small;">{ // code that add up the Amount...&nbsp;&nbsp;&nbsp;</span></p>
<p><span style="font-size: x-small;">}</span></p>
</div>
</td>
</tr>
</tbody>
</table>
<p>&nbsp;</p>
<p><span style="font-size: small;"><span style="font-family: Calibri;">The improved code uses SQL aggregation, which improved the speed in multy folds:</span></span></p>
<table style="width: 100%;" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td>
<div>
<p><span style="font-size: x-small;"><strong>&nbsp;&nbsp;&nbsp; while</strong> <strong>select</strong> <span style="background-color: #ffff00;"><strong>sum</strong></span>(AccountingCurrencyAmount) <strong>from</strong> generalJournalAccountEntry</span></p>
<p><span style="background-color: #ffff00;"><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>group</strong> <strong>by</strong> generalJournalAccountEntry.IsCredit, </span><span style="font-size: x-small;">mainAccount.MainAccountId, </span><span style="font-size: x-small;">mainAccount.RecId, </span><span style="font-size: x-small;">dimensionAttributeLevelValueView.DisplayValue</span></span></p>
<p><span style="font-size: x-small; background-color: #ffff00;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>order</strong> <strong>by</strong> mainAccount.MainAccountId, dimensionAttributeLevelValueView.DisplayValue</span></p>
<p><span style="font-size: x-small; background-color: #ffff00;">{ // no more code to add up. </span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; ...</span></p>
<p><span style="font-size: x-small; background-color: #ffff00;">}</span></p>
<p>&nbsp;</p>
</div>
</td>
</tr>
</tbody>
</table>]]></content:encoded>
    </item>
    <item>
      <title>Improving SSRS Report Performance with Set-Based Operations - part 1</title>
      <link>https://docs.microsoft.com/archive/blogs/axperf/improving-ssrs-report-performance-with-set-based-operations-part-1</link>
      <pubDate>Fri, 19 Jul 2013 16:17:00 GMT</pubDate>
      <dc:creator><![CDATA[Youxuan Jin]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/axperf/2013/07/19/improving-ssrs-report-performance-with-set-based-operations-part-1/</guid>
      <description><![CDATA[Introduction
Ax reports were migrated to Microsoft SSRS in Ax2012, mostly in the form of Report...]]></description>
      <content:encoded><![CDATA[<h2><span style="font-size: medium;"><span style="color: #4f81bd;"><span style="font-family: Cambria;">Introduction</span></span></span></h2>
<p><span style="font-size: small;"><span style="font-family: Calibri;">Ax reports were migrated to Microsoft SSRS in Ax2012, mostly in the form of Report Data Provider classes (RDP).&nbsp; We want to share with our customers and ISVs, through this series of blogs, with some best practices to improve SSRS report performance.&nbsp; </span></span></p>
<p><span style="font-size: small;"><span style="font-family: Calibri;">Our goal is to improving report performance without introducing much disruptions or risks to the existing reports. So, the recommended patterns of changes are mostly focus on optimizing data access and reducing duplicates in the scope of your current report designs. In some cases, a report needs to be redesigned for better performance, which is outside the scope of this discussion.</span></span></p>
<h2><span style="font-size: medium;"><span style="color: #4f81bd;"><span style="font-family: Cambria;">Using Set-Based Operations</span></span></span></h2>
<p><span style="font-size: small;"><span style="font-family: Calibri;">Set-based operations in Ax are exponentially faster than the equivalent line based operations. Most RDP reports today access data line by line.&nbsp; In Ax2012 R2, we updated a few reports with set-based data access and observe better performance.&nbsp;</span></span></p>
<p><span style="font-size: small;"><span style="font-family: Calibri;">A typical line based logic in a RDP class processReport() method looks like this:</span></span></p>
<table style="width: 100%;" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td>
<div>
<p>&nbsp;&nbsp;<span style="font-size: x-small;">&nbsp; queryRun = <strong>new</strong> QueryRun(query);</span></p>
<p>&nbsp;<span style="font-size: x-small;">&nbsp;&nbsp; <strong>while</strong> (queryRun.next())</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; {</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; custTable = queryRun.get(<strong>tableNum</strong>(CustTable));</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; custTrans = queryRun.get(<strong>tableNum</strong>(CustTrans));</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; this.fetchLedgerTrans(custTrans, custTable.name());</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp; ...</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; }</span></p>
</div>
</td>
</tr>
</tbody>
</table>
<p>&nbsp;</p>
<p><span style="font-size: small;"><span style="font-family: Calibri;">And another example &ndash; a method in a RDP:</span></span></p>
<table style="width: 100%;" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td>
<div>
<p><span style="font-size: x-small;"><strong>private</strong> <strong>void</strong> insertTaxTransFromTaxGeneralJournal(Query _query)</span></p>
<p><span style="font-size: x-small;">{</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; MainAccount mainAccount;</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; GeneralJournalEntry generalJournalEntry;</span></p>
<p>&nbsp;<span style="font-size: x-small;">&nbsp;&nbsp; TaxTrans taxTrans;</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; TaxTransGeneralJournalAccountEntry taxTransGeneralJournalAccountEntry;</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; QueryRun queryRun = <strong>new</strong> QueryRun(_query);</span></p>
<p>&nbsp;<span style="font-size: x-small;">&nbsp;&nbsp; <strong>while</strong> (queryRun.next())</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; {</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; taxTrans = queryRun.get(<strong>tableNum</strong>(TaxTrans));</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; taxTransGeneralJournalAccountEntry = queryRun.get(<strong>tableNum</strong>(TaxTransGeneralJournalAccountEntry));</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; generalJournalEntry = GeneralJournalEntry::findByGeneralJournalAccountEntry( taxTransGeneralJournalAccountEntry.GeneralJournalAccountEntry);</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mainAccount = queryRun.get(<strong>tableNum</strong>(MainAccount));</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>if</strong> (taxTransGeneralJournalAccountEntry.TaxTransRelationship == TaxTransRelationshipType::UseTaxPayable)</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; this.insertTaxTransTempTable(taxTrans, mainAccount, -taxTrans.TaxAmount,&nbsp; generalJournalEntry.JournalNumber);</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>else</strong></span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; this.insertTaxTransTempTable(taxTrans, mainAccount, taxTrans.TaxAmount,&nbsp; generalJournalEntry.JournalNumber);</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; }</span></p>
<p><span style="font-size: x-small;">}</span></p>
</div>
</td>
</tr>
</tbody>
</table>
<p><span style="font-size: small;"><span style="font-family: Calibri;">You can re-write it into X++ set-based operations to transform data from source tables into the target tempDB table through selects and joins. A general approach of doing this is to exam the Query object range conditions and trying to express them in terms of set-based expressions. Relations in the query object need to be expressed as join clause in the set-based statement. An example for the above line-based code would be something similar to this:</span></span></p>
<table style="width: 100%;" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td>
<div>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>insert_recordset</strong> taxTransTmp</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; AccountingDate,&nbsp; Voucher,&nbsp;&nbsp; TaxAmountMST,&nbsp; CustVendAC,</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; TaxAccountName,</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; LedgerJournalId,</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; MainAccountId,&nbsp;&nbsp;&nbsp; MainAccountName</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; )</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>select</strong> TransDate, Voucher, <strong>sum</strong>(TaxAmount), TaxCode <strong>from</strong> taxTrans</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>group</strong> <strong>by</strong> TransDate, Voucher, TaxCode</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>where</strong> taxTrans.TransDate &gt;= fromDate</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;&amp; taxTrans.TransDate &lt;= toDate</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;&amp; taxTrans.Voucher &gt;= fromVoucher</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;&amp; taxTrans.Voucher &lt;= toVoucher</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;&amp; taxTrans.TaxDirection == TaxDirection::UseTax</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;&amp; (taxTrans.SourceDocumentLine == <strong>0</strong></span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ||( taxTrans.TaxOrigin == TaxOrigin::Payment</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; || taxTrans.TaxOrigin == TaxOrigin::TaxReversed</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; || taxTrans.TaxOrigin == TaxOrigin::CashDisc))</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>join</strong> TaxName <strong>from</strong> taxTable</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>group</strong> <strong>by</strong> TaxName</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>where</strong> taxTrans.TaxCode == taxTable.TaxCode</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>join</strong> taxTransGeneralJournalAccountEntry</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>where</strong> taxTrans.RecId == taxTransGeneralJournalAccountEntry.TaxTrans</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;&amp; taxTransGeneralJournalAccountEntry.TaxTransRelationship ==</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; TaxTransRelationshipType::UseTaxPayable</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>join</strong> generalJournalAccountEntry</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>where</strong> taxTransGeneralJournalAccountEntry.GeneralJournalAccountEntry ==</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; generalJournalAccountEntry.RecId</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>join</strong> JournalNumber <strong>from</strong> generalJournalEntry</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<strong>group</strong> <strong>by</strong> JournalNumber</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>where</strong> generalJournalAccountEntry.GeneralJournalEntry == generalJournalEntry.RecId</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>join</strong> davc</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>where</strong> taxTransGeneralJournalAccountEntry.LedgerDimension == davc.RecId</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>join</strong> MainAccountId, Name <strong>from</strong> mainAccount</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>group</strong> <strong>by</strong> MainAccountId, Name</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>where</strong> mainAccount.RecId == davc.MainAccount</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;&amp; mainAccount.LedgerChartOfAccounts == LedgerChartOfAccounts::current()</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;&amp; mainAccount.MainAccountId &gt;= fromMainAccount</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;&amp; mainAccount.MainAccountId &lt;= toMainAccount;</span></p>
</div>
</td>
</tr>
</tbody>
</table>
<p>&nbsp;</p>
<p><span style="font-family: Calibri;"><span style="font-size: small;">However, not all line based RDP providers can be in set-based, for mainly two reasons: first, the use of dynamic parameters in reports. Second, complicated line based logic in X++ code that can&rsquo;t be expressed in set-based expression, such as exchange rate calculations. In later posts, we will show how to improve the performance using partially set-based approach, which we call it the hybrid pattern.</span></span></p>
<p><span style="font-family: Calibri;"><span style="font-size: small;">The two constrains are easy to identify. Here are the details.</span></span></p>
<h3><span style="color: #4f81bd;"><span style="font-family: Cambria;"><span style="font-size: small;">Identifying Dynamic Parameters Constraint</span></span></span></h3>
<p><span style="font-size: small;"><span style="font-family: Calibri;">When dynamic parameter is enabled for a report, end users can define and modify the query ranges in the report query object. The Report framework runtime will execute the query with user modified query ranges. Such runtime feature is not supported in today&rsquo;s X++ set-based operations, where all filter conditions are precompiled. A simple way to find out if a report is enabled with dynamic parameter is to run the report and see if the query form has the &ldquo;Select&rdquo; button:</span></span></p>
<p>&nbsp;<a href="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Blogs.Components.WeblogFiles/00/00/00/93/82/7271.report1.png" original-url="http://blogs.msdn.com/cfs-file.ashx/__key/communityserver-blogs-components-weblogfiles/00-00-00-93-82/7271.report1.png"><img src="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Blogs.Components.WeblogFiles/00/00/00/93/82/7271.report1.png" original-url="http://blogs.msdn.com/resized-image.ashx/__size/550x0/__key/communityserver-blogs-components-weblogfiles/00-00-00-93-82/7271.report1.png" alt="" width="398" height="205" border="0" /></a></p>
<p><span style="font-family: Calibri; font-size: small;">&nbsp;</span></p>
<p><span style="font-family: Calibri;"><span style="font-size: small;">Or in VS report designer see if the property is checked.</span></span></p>
<p>&nbsp;<a href="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Blogs.Components.WeblogFiles/00/00/00/93/82/7180.report2.png" original-url="http://blogs.msdn.com/cfs-file.ashx/__key/communityserver-blogs-components-weblogfiles/00-00-00-93-82/7180.report2.png"><img src="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Blogs.Components.WeblogFiles/00/00/00/93/82/7180.report2.png" original-url="http://blogs.msdn.com/resized-image.ashx/__size/550x0/__key/communityserver-blogs-components-weblogfiles/00-00-00-93-82/7180.report2.png" alt="" width="324" height="351" border="0" /></a></p>
<p><span style="font-family: Calibri;"><span style="font-size: small;">Of all the 1100 RDP reports shipped in Ax2012, about 700 of them have dynamic parameters enabled.&nbsp;&nbsp;</span></span></p>
<p><span style="font-family: Calibri;"><span style="font-size: small;">Ax2012 R3 added support for&nbsp;set-based insert with dynamic parameters. Please refer to Part 6 of this posting series for details.</span></span></p>
<h3><span style="color: #4f81bd;"><span style="font-family: Cambria;"><span style="font-size: small;">Identifying Code Logic Constraint</span></span></span></h3>
<p><span style="font-size: small;"><span style="font-family: Calibri;">An RDP class typically contains code logic to retrieve and/or calculate field values for each target record line, in a line-by-line way. Generally speaking, such X++ code can&rsquo;t be expressed in set-based operation clauses. For example, CurrencyExchangeHelper class has the logic that can&rsquo;t be written in the form of X++ set-based clause expression. When this happens, the line-based code in question has to stay.&nbsp; </span></span></p>
<p><span style="font-size: small;"><span style="font-family: Calibri;">In a few cases, a field value that comes from complicate code logic may already exist in some other related table field. If you can identify that duplicating field, you should use that field value. A good example is in the Ax2012 VendAccruedPurchaseDP:</span></span></p>
<table style="width: 100%;" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td>
<div>
<p><span style="font-size: x-small;"><strong>private</strong> <strong>void</strong> insert()</span></p>
<p><span style="font-size: x-small;">{</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; ...</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; vendAccruedPurchasesTmp_NA.ItemId&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = purchLine.ItemId;</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; vendAccruedPurchasesTmp_NA.ReferenceId&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;= purchTable.PurchId;</span></p>
<p><span style="font-size: x-small;">&nbsp;<strong>&nbsp;&nbsp; vendAccruedPurchasesTmp_NA.ItemName&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = inventTable.itemName();</strong></span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; vendAccruedPurchasesTmp_NA.VendName&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = dirPartyTable.Name;</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; ...</span></p>
<p><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; vendAccruedPurchasesTmp_NA.insert();</span></p>
<p><span style="font-size: x-small;">}</span></p>
</div>
</td>
</tr>
</tbody>
</table>
<p>&nbsp;</p>
<p><span style="font-family: Calibri;"><span style="font-size: small;">The table method itemName() contains the logic that can&rsquo;t be expressed as set-based. A proposed option of using set-based in this case is to use PurchLine.Name. </span></span></p>
<p><span style="font-family: Calibri;"><span style="font-size: small;">In many cases, table methods, such as find(), are used only to get values, line-by-line, through a database select statement. Such select statement can become an exist-join clause to the set-based operation. This is discussed later in this blog.</span></span></p>
<h3><span style="color: #4f81bd;"><span style="font-family: Cambria;"><span style="font-size: small;">Replacing Line-based code with Set-based</span></span></span></h3>
<p><span style="font-size: small;"><span style="font-family: Calibri;">Finally, if none of the above two constrains&nbsp;applies to a report,&nbsp; you should be able to&nbsp;transform&nbsp;the report into set-based. We identified a few reports shipped in Ax2012 to be the candidates. Here are some of them:</span></span></p>
<p><span style="font-size: small;"><span style="font-family: Calibri;"><span style="font-size: small;"><span style="font-family: Calibri;"><strong>TaxLedgerReconciliation, <span style="font-size: small;"><span style="font-family: Calibri;"><strong>VendLedgerReconciliation, <span style="font-size: small;"><span style="font-family: Calibri;"><strong>BankLedgerReconciliation, <span style="font-size: small;"><span style="font-family: Calibri;"><strong>CustLedgerReconciliation, <span style="font-size: small;"><span style="font-family: Calibri;"><strong>LedgerAccountBalanceSheetByDim, <strong><span style="font-size: small;"><span style="font-family: Calibri;">BankAccountStatement, etc.</span></span></strong></strong></span></span></strong></span></span></strong></span></span></strong></span></span></strong></span></span></span></span></p>
<p><span style="font-size: small;"><span style="font-family: Calibri;">In Ax2012 R2, we optimized three reports by using some set-based operations: <strong>LedgerTrialBalance</strong>, <strong>VendAccruedPurch</strong>, and <strong>LedgerTransStatement</strong>.&nbsp;We will continue improving SSRS reports in the future releases.</span></span></p>
<p><span style="font-size: small;"><span style="font-family: Calibri;">(to be continued)</span></span></p>]]></content:encoded>
    </item>
    <item>
      <title>Improve Check Report Performance by Reducing the Size of Company Logo and Signature Image Files</title>
      <link>https://docs.microsoft.com/archive/blogs/axperf/improve-check-report-performance-by-reducing-the-size-of-company-logo-and-signature-image-files</link>
      <pubDate>Fri, 19 Jul 2013 14:58:43 GMT</pubDate>
      <dc:creator><![CDATA[Ch. Wolf]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/axperf/2013/07/19/improve-check-report-performance-by-reducing-the-size-of-company-logo-and-signature-image-files/</guid>
      <description><![CDATA[Microsoft Dynamics AX 2012 uses SSRS as its report engine. For each data source in a report, SSRS...]]></description>
      <content:encoded><![CDATA[<p>Microsoft Dynamics AX 2012 uses SSRS as its report engine. For each data source in a report, SSRS retrieves the data by making a call to AOS Query Service, which streams back the data in xml. The size of streaming dataset from AOS Query Service has a limit of about 1GB, partly due to the limitation of .Net (Stack Overflow). And, the Query Service performance will drop significantly or throw exception when approaching this limit.</p>
<p>Normally, a report is unlikely to reach this limit to cause out-of-memory exception or a significant performance drop. However, Check report is more subject to it, where data are prepared in a temp table and then streamed to SSRS. A check often contains images of the company Logo and signature.&nbsp; A batch of check printing requests often contains a large number of checks, say 500 or even 1000s. In such cases, customers will get close to or even hit the limit. The check printing either becomes unbearably slow or fails.</p>
<p>You can work around the issue in the current released version by reducing the image size of both company logo and signature using compressing method.&nbsp; You may not see the improvement unless you batch up a large number of checks to print.</p>
<p>To change the company logo, you need to use an image compressing tool to compress your company logo and signature files into smaller files. Avoid using BMP file format; use compressed image format such as JPG instead. Once you have the smaller image file ready,&nbsp; open bank accounts form and select Set up/Check: Company&gt;Cash and bank management&gt;Common&gt;Bank accounts</p>
<p>&nbsp;<a href="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Blogs.Components.WeblogFiles/00/00/00/93/82/7128.Untitled.jpg" original-url="http://blogs.msdn.com/cfs-file.ashx/__key/communityserver-blogs-components-weblogfiles/00-00-00-93-82/7128.Untitled.jpg"><img src="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Blogs.Components.WeblogFiles/00/00/00/93/82/7128.Untitled.jpg" original-url="http://blogs.msdn.com/resized-image.ashx/__size/550x0/__key/communityserver-blogs-components-weblogfiles/00-00-00-93-82/7128.Untitled.jpg" alt="" border="0" /></a></p>
<p>Click on Company and Signature tab respectively to load the smaller files you created earlier.</p>
<p>Please note that smaller images in reports in general benefit the printing speed.</p>]]></content:encoded>
    </item>
    <item>
      <title>Retail Benchmark Summary and Detailed Results published</title>
      <link>https://docs.microsoft.com/archive/blogs/axperf/retail-benchmark-summary-and-detailed-results-published</link>
      <pubDate>Thu, 18 Oct 2012 12:11:00 GMT</pubDate>
      <dc:creator><![CDATA[Ch. Wolf]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/axperf/2012/10/18/retail-benchmark-summary-and-detailed-results-published/</guid>
      <description><![CDATA[In 2012, Microsoft conducted a benchmark on Microsoft Dynamics AX 2012 to measure the performance...]]></description>
      <content:encoded><![CDATA[<p>In 2012, Microsoft conducted a benchmark on Microsoft Dynamics AX 2012 to <br />measure the performance and scalability characteristics of the Microsoft <br />Dynamics AX 2012 Feature Pack retail solution in a simulated retail scenario. <br />The benchmark showcases the ability of Microsoft Dynamics AX 2012 to handle <br />various specialized loads concurrently, without compromising performance and <br />scalability on critical business processes.</p>
<p>Please find the reports here:</p>
<p><a title="PartnerSource" href="https://mbs.microsoft.com/partnersource/deployment/documentation/whitepapers/ax2012_retailbenchmarks_wps">PartnerSource</a></p>
<p><a title="CustomerSource" href="https://mbs.microsoft.com/customersource/documentation/whitepapers/ax2012_retailbenchmarks_wps.htm?printpage=false&amp;sid=jbkyhebggihm2puqzucazm0z&amp;stext=retail benchmark ax 2012">CustomerSource</a></p>]]></content:encoded>
    </item>
  </channel>
</rss>