<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Al hasib Tamim</title>
    <description>The latest articles on DEV Community by Al hasib Tamim (@al_hasibtamim_7a98724a4e).</description>
    <link>https://dev.to/al_hasibtamim_7a98724a4e</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4206714%2F5b8da3df-6a8b-4be9-85ad-a5f89103e231.png</url>
      <title>DEV Community: Al hasib Tamim</title>
      <link>https://dev.to/al_hasibtamim_7a98724a4e</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/al_hasibtamim_7a98724a4e"/>
    <language>en</language>
    <item>
      <title>How to Fix Epicor Kinetic BAQ Timeouts: PartTran, TranGLC &amp; Query Tuning</title>
      <dc:creator>Al hasib Tamim</dc:creator>
      <pubDate>Sun, 25 Oct 2026 17:51:36 +0000</pubDate>
      <link>https://dev.to/al_hasibtamim_7a98724a4e/how-to-fix-epicor-kinetic-baq-timeouts-parttran-tranglc-query-tuning-57mf</link>
      <guid>https://dev.to/al_hasibtamim_7a98724a4e/how-to-fix-epicor-kinetic-baq-timeouts-parttran-tranglc-query-tuning-57mf</guid>
      <description>&lt;p&gt;Most Epicor Kinetic BAQ and SSRS report timeouts should be investigated as a SQL performance problem before blindly increasing timeout settings. Common culprits include reading millions of unindexed rows, joining tables at the wrong level of detail, implicit data-type conversions on generic keys, repeated subqueries, and expensive report datasets.&lt;/p&gt;

&lt;p&gt;Here is a practical, step-by-step diagnostic guide for Epicor DBAs, developers, and Kinetic analysts.&lt;/p&gt;




&lt;h2&gt;
  
  
  How the Pieces Fit Together
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;th&gt;Potential Bottleneck&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;Erp.PartTran&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Inventory transactions and adjustments&lt;/td&gt;
&lt;td&gt;Large scans when filters do not align with available indexes or lack selectivity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;Erp.TranGLC&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;General-ledger transaction links&lt;/td&gt;
&lt;td&gt;Broad joins across transaction types, incomplete keys, implicit conversions, or row multiplication&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;BAQ calculated fields&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Derive values for returned records&lt;/td&gt;
&lt;td&gt;Correlated subqueries repeating scans across thousands of outer rows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SSRS datasets &amp;amp; subreports&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Presentation, grouping, and subreports&lt;/td&gt;
&lt;td&gt;Transferring large raw datasets across the network or firing subreports per row&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Step 1: Isolate the Expensive Table or Join
&lt;/h2&gt;

&lt;p&gt;When a BAQ times out, do not guess which join is responsible. Isolate the execution:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Test in the BAQ Designer first:&lt;/strong&gt; Run the query with Analyze / Test. Does the timeout occur immediately at 30 seconds (standard command timeout) or after several minutes?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Comment out &lt;code&gt;TranGLC&lt;/code&gt;:&lt;/strong&gt; Does the query drop from 90 seconds down to 2 seconds? If yes, the issue is generic key mapping or missing &lt;code&gt;RelatedToFile&lt;/code&gt; discriminators.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Filter to a single &lt;code&gt;PartNum&lt;/code&gt;:&lt;/strong&gt; Does the query run instantly for one part but time out across all parts? If yes, you have an unindexed date scan on &lt;code&gt;PartTran&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Step 2: Check PartTran Filters &amp;amp; Index Alignment
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;PartTran&lt;/code&gt; is often the largest single table in an Epicor database, frequently holding tens of millions of rows.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Date-Filter Trap
&lt;/h3&gt;

&lt;p&gt;A common pattern in inventory reporting is filtering only by date:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;pt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TranDate&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="s1"&gt;'2026-01-01'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In many standard Epicor index configurations, composite indexes on &lt;code&gt;PartTran&lt;/code&gt; begin with &lt;code&gt;Company&lt;/code&gt; and &lt;code&gt;PartNum&lt;/code&gt; before &lt;code&gt;TranDate&lt;/code&gt;. A filter on &lt;code&gt;TranDate&lt;/code&gt; alone cannot perform an efficient index seek across all parts; the engine must scan the table or a wide index subtree.&lt;/p&gt;

&lt;h3&gt;
  
  
  Practical Fixes
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Narrow the scope before joining:&lt;/strong&gt; Combine date boundaries with specific &lt;code&gt;TranType&lt;/code&gt; filters (e.g., &lt;code&gt;STK-MTL&lt;/code&gt;, &lt;code&gt;PUR-STK&lt;/code&gt;, &lt;code&gt;MFG-STK&lt;/code&gt;) or specific warehouse/plant constraints.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Avoid wrapping indexed columns in functions:&lt;/strong&gt; Writing &lt;code&gt;YEAR(pt.TranDate) = 2026&lt;/code&gt; breaks SARGability and forces a full table scan. Always use range predicates:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;   &lt;span class="n"&gt;pt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TranDate&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="s1"&gt;'2026-01-01'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;pt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TranDate&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="s1"&gt;'2027-01-01'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 3: Make TranGLC Joins Selective &amp;amp; Type-Safe
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Erp.TranGLC&lt;/code&gt; is a polymorphic bridge table connecting general ledger distributions to invoices, receipts, jobs, and inventory transactions. Because it serves many tables, it presents two major performance traps:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Always Include &lt;code&gt;RelatedToFile&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Never join to &lt;code&gt;TranGLC&lt;/code&gt; on transaction keys alone. If you do not specify &lt;code&gt;RelatedToFile = 'InvcDtl'&lt;/code&gt; (or &lt;code&gt;'PartTran'&lt;/code&gt;), SQL Server must evaluate keys across millions of unrelated GL distributions from every module.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Avoid Implicit Conversions on Join Keys
&lt;/h3&gt;

&lt;p&gt;In &lt;code&gt;TranGLC&lt;/code&gt;, &lt;code&gt;Key1&lt;/code&gt;, &lt;code&gt;Key2&lt;/code&gt;, and &lt;code&gt;Key3&lt;/code&gt; are stored as &lt;strong&gt;&lt;code&gt;nvarchar&lt;/code&gt;&lt;/strong&gt;. However, the source keys in business tables are often integers (such as &lt;code&gt;InvoiceNum&lt;/code&gt;, &lt;code&gt;InvoiceLine&lt;/code&gt;, &lt;code&gt;PackNum&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Illustrative type-safe join pattern:&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;gl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Company&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Company&lt;/span&gt;
&lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;gl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RelatedToFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'InvcDtl'&lt;/span&gt;
&lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;gl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Key1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;CONVERT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nvarchar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InvoiceNum&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;gl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Key2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;CONVERT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nvarchar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InvoiceLine&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;If data types do not match, SQL Server will convert the column on every row, disabling index seeks.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: Replace Repeated Scalar Lookups
&lt;/h2&gt;

&lt;p&gt;A frequent cause of BAQ timeouts is retrieving the "latest transaction date" or "last cost" using a calculated field containing a subquery:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- The slow scalar approach (runs once per row in the outer query!):&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;TOP&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;pt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TranDate&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Erp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PartTran&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;pt&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;pt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Company&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Company&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;pt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PartNum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PartNum&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;pt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TranDate&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TranNum&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your outer query returns 50,000 parts, this subquery runs &lt;strong&gt;50,000 times&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The High-Performance Fix: Common Table Expressions &amp;amp; Window Functions
&lt;/h3&gt;

&lt;p&gt;Pre-aggregate or rank the data once in an inner SubQuery using &lt;code&gt;ROW_NUMBER()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;RankedPartTran&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt;
        &lt;span class="n"&gt;pt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Company&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;pt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PartNum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;pt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TranDate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;pt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TranNum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;ROW_NUMBER&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;pt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Company&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PartNum&lt;/span&gt;
            &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;pt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TranDate&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TranNum&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;RowIdent&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Erp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PartTran&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;pt&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;pt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Company&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'EPIC01'&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PartNum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;rpt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TranDate&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;LatestTranDate&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Erp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Part&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;RankedPartTran&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;rpt&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;rpt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Company&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Company&lt;/span&gt;
   &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;rpt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PartNum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PartNum&lt;/span&gt;
   &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;rpt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RowIdent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Company&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'EPIC01'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;This scans &lt;code&gt;PartTran&lt;/code&gt; exactly once instead of 50,000 times.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 5: Join on Company and the Complete Relationship Key
&lt;/h2&gt;

&lt;p&gt;Epicor is a multi-company application. Omitting &lt;code&gt;Company&lt;/code&gt; from any join condition can cause:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Accidental Cartesian products&lt;/strong&gt; across companies.&lt;/li&gt;
&lt;li&gt;Inability of SQL Server to utilize compound primary keys where &lt;code&gt;Company&lt;/code&gt; is the leading column.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Step 6: Tune SSRS Datasets and Subreports
&lt;/h2&gt;

&lt;p&gt;If the BAQ runs fast in the designer but the SSRS report times out in the System Monitor:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Avoid SSRS Subreports inside Tablix rows:&lt;/strong&gt; A subreport embedded in a row executes a separate query against the report server for every single record rendered.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Filter in SQL, not in SSRS:&lt;/strong&gt; Do not pull 100,000 rows into the RDL dataset and filter them with an SSRS expression. Push the predicate into the BAQ or RDD so SQL Server only streams what is actually printed.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The &lt;code&gt;QueryTimeout = 0&lt;/code&gt; Trap
&lt;/h2&gt;

&lt;p&gt;When a BAQ or report times out, the tempting quick fix is setting &lt;code&gt;QueryTimeout = 0&lt;/code&gt; (or 600 seconds) in the BAQ execution settings or SSRS report properties.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why this is dangerous:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;0&lt;/code&gt; means infinite timeout. A poorly constructed Cartesian join or non-SARGable scan will sit on your SQL Server holding locks and consuming CPU/memory workers until the SQL instance restarts.&lt;/li&gt;
&lt;li&gt;Always fix the underlying index alignment or subquery structure first.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Troubleshooting Summary
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symptom&lt;/th&gt;
&lt;th&gt;Probable Cause&lt;/th&gt;
&lt;th&gt;Action Item&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Times out consistently at 30s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Default command timeout reached&lt;/td&gt;
&lt;td&gt;Check execution plan; isolate if &lt;code&gt;PartTran&lt;/code&gt; or &lt;code&gt;TranGLC&lt;/code&gt; is causing table scans&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Fast for 1 part, times out for all parts&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Broad &lt;code&gt;PartTran&lt;/code&gt; scan without leading index key&lt;/td&gt;
&lt;td&gt;Add mandatory filters or date/plant boundaries; avoid &lt;code&gt;YEAR(Date)&lt;/code&gt; functions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Slows dramatically after adding &lt;code&gt;TranGLC&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Missing &lt;code&gt;RelatedToFile&lt;/code&gt; or data-type mismatch&lt;/td&gt;
&lt;td&gt;Add explicit &lt;code&gt;RelatedToFile = '...'&lt;/code&gt; filter and &lt;code&gt;CONVERT()&lt;/code&gt; integer keys to &lt;code&gt;nvarchar&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Report hangs in &lt;code&gt;ACTIVE&lt;/code&gt; state&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;SSRS subreport row multiplication or network buffer&lt;/td&gt;
&lt;td&gt;Flatten subreports into single dataset; verify report server execution logs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h3&gt;
  
  
  About the Author
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Al Hasib Tamim&lt;/strong&gt; is an independent Technical ERP Analyst and contractor at &lt;strong&gt;&lt;a href="https://clearcutover.com/" rel="noopener noreferrer"&gt;Clear Cutover&lt;/a&gt;&lt;/strong&gt;, specializing in &lt;a href="https://clearcutover.com/syteline" rel="noopener noreferrer"&gt;Infor SyteLine&lt;/a&gt; data migrations and white-label &lt;a href="https://clearcutover.com/epicor-kinetic" rel="noopener noreferrer"&gt;Epicor Kinetic&lt;/a&gt; report, BAQ, and DMT subcontracting.&lt;/p&gt;

&lt;p&gt;Explore the open-source diagnostic toolkit on GitHub: &lt;a href="https://github.com/tamimhasib76/syteline-kinetic-erp-diagnostics" rel="noopener noreferrer"&gt;tamimhasib76/syteline-kinetic-erp-diagnostics&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Original article published on &lt;a href="https://clearcutover.com/blog/kinetic-baq-timeout-parttran-tranglc" rel="noopener noreferrer"&gt;Clear Cutover&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>sql</category>
      <category>database</category>
      <category>performance</category>
      <category>erp</category>
    </item>
    <item>
      <title>Why SyteLine Says "Row Has Been Modified by Another User" (and How Optimistic Concurrency Works)</title>
      <dc:creator>Al hasib Tamim</dc:creator>
      <pubDate>Sun, 25 Oct 2026 14:16:09 +0000</pubDate>
      <link>https://dev.to/al_hasibtamim_7a98724a4e/why-syteline-says-row-has-been-modified-by-another-user-and-how-optimistic-concurrency-works-41nf</link>
      <guid>https://dev.to/al_hasibtamim_7a98724a4e/why-syteline-says-row-has-been-modified-by-another-user-and-how-optimistic-concurrency-works-41nf</guid>
      <description>&lt;p&gt;In Infor SyteLine (CloudSuite Industrial) and the Mongoose framework, few error messages trigger more user frustration than:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"This row has been modified by another user since you began editing it."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Users frequently submit urgent tickets insisting: &lt;em&gt;"I'm the only person working in this site today! Why is it telling me someone else changed it?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Here is an architectural breakdown of what is actually happening at the database level, why the error is by design, and how to track down the true culprit.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Optimistic Concurrency Works Under the Hood
&lt;/h2&gt;

&lt;p&gt;SyteLine uses &lt;strong&gt;optimistic concurrency control&lt;/strong&gt; rather than pessimistic locking. &lt;/p&gt;

&lt;p&gt;When a user opens a customer order, item, or PO line in a WinStudio form or IDO:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The framework loads the record along with two critical audit columns: &lt;strong&gt;&lt;code&gt;RowPointer&lt;/code&gt;&lt;/strong&gt; (a unique surrogate GUID) and &lt;strong&gt;&lt;code&gt;RecordDate&lt;/code&gt;&lt;/strong&gt; (the timestamp representing the row version).&lt;/li&gt;
&lt;li&gt;The client application keeps that specific &lt;code&gt;RecordDate&lt;/code&gt; in local memory during the editing session.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No database locks are held while the user looks at the form.&lt;/strong&gt; This is intentional: holding exclusive or shared database locks while an operator walks away for lunch would grind the entire ERP to a halt.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When the user finally clicks &lt;strong&gt;Save&lt;/strong&gt;, the IDO runtime executes a parameterized &lt;code&gt;UPDATE&lt;/code&gt; statement that looks conceptually like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;coitem_mst&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;qty_ordered&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;new_qty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;RecordDate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;GETDATE&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;UpdatedBy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="k"&gt;user&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;RowPointer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;rowpointer&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;RecordDate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;original_record_date&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the crucial predicate: &lt;strong&gt;&lt;code&gt;AND RecordDate = @original_record_date&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If the row in SQL Server still matches the timestamp loaded into memory, the &lt;code&gt;UPDATE&lt;/code&gt; affects &lt;strong&gt;1 row&lt;/strong&gt;. The save commits cleanly.&lt;/li&gt;
&lt;li&gt;If &lt;strong&gt;anything&lt;/strong&gt; changed that row in the meantime, SQL Server's &lt;code&gt;RecordDate&lt;/code&gt; has advanced. The &lt;code&gt;WHERE&lt;/code&gt; clause evaluates to false, affecting &lt;strong&gt;0 rows&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The framework detects 0 rows updated and raises the "modified by another user" error to prevent a &lt;strong&gt;lost update&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Real Culprits (Why It Happens When "Nobody Else Is Editing")
&lt;/h2&gt;

&lt;p&gt;If a single user is editing alone and hits this error, the cause is almost never another human. It is one of these four automated mechanisms:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. "Your Own Session Changed It" (The Post-Save Event Handler Trap)
&lt;/h3&gt;

&lt;p&gt;This is the most common self-inflicted defect in custom forms and IDO extension classes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A custom after-save event handler or stored procedure updates a status flag or recalculates a total on the same row immediately after the initial save.&lt;/li&gt;
&lt;li&gt;SQL Server updates &lt;code&gt;RecordDate&lt;/code&gt; to reflect the handler's change.&lt;/li&gt;
&lt;li&gt;The user's client form, however, is still holding the pre-save &lt;code&gt;RecordDate&lt;/code&gt;. On the very next click or subsequent save, the save fails immediately.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Background Tasks &amp;amp; Utilities
&lt;/h3&gt;

&lt;p&gt;Background processes running under the Infor Framework TaskMan (order posting, EDI transaction imports, shipping utilities) update records asynchronously. If an operator leaves an order open for hours, a background run can easily touch that order line.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Direct SQL Scripts or Unofficial Jobs
&lt;/h3&gt;

&lt;p&gt;SQL Agent jobs or legacy triggers written to "patch" data directly in SQL tables without routing through the Mongoose IDO layer will bump &lt;code&gt;RecordDate&lt;/code&gt; silently, catching client users off guard.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Custom IDO Round-Trip Failures
&lt;/h3&gt;

&lt;p&gt;When building custom REST integrations or WinStudio forms, failing to round-trip &lt;code&gt;RecordDate&lt;/code&gt; and &lt;code&gt;_ItemId&lt;/code&gt; properly on &lt;code&gt;UpdateCollection&lt;/code&gt; calls will cause optimistic concurrency checks to fail every time.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Find the Culprit in Production
&lt;/h2&gt;

&lt;p&gt;When a user reports this repeating on a specific form or order, don't guess:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Check the Audit Columns:&lt;/strong&gt;
Run a read-only query on the affected record:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;   &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;RowPointer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;RecordDate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;UpdatedBy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CreateDate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CreatedBy&lt;/span&gt;
   &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;co_mst&lt;/span&gt;
   &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;co_num&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'ORD12345'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inspect &lt;code&gt;UpdatedBy&lt;/code&gt; and &lt;code&gt;RecordDate&lt;/code&gt;. If &lt;code&gt;UpdatedBy&lt;/code&gt; shows a service account or background process rather than a human username, you've identified the offending job.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Check Background Task History:&lt;/strong&gt;
Match the &lt;code&gt;RecordDate&lt;/code&gt; timestamp against the completion timestamps in &lt;strong&gt;Background Task History&lt;/strong&gt; or the Windows Event log.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What NOT to Do
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Never bypass the check via direct SQL UPDATE:&lt;/strong&gt; That destroys data integrity and bypasses Mongoose business logic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never teach users to blindly re-save:&lt;/strong&gt; Re-saving without refreshing means they are overwriting changes from other processes without reviewing what changed.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published with full troubleshooting workflows at &lt;a href="https://clearcutover.com/blog/syteline-row-modified-by-another-user" rel="noopener noreferrer"&gt;Clear Cutover&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>sql</category>
      <category>database</category>
      <category>architecture</category>
      <category>enterprise</category>
    </item>
  </channel>
</rss>
