In Dynamics AX 2009 as well in Dynamics 365 for Finance and Operations (D365 FO) the Inventory, Purchase and Sales Prices are stored in InventTableModule table on separate rows, independently of the Products, stored in InventTable. Thus for each Product there are three rows that need to be joined. To get all the Prices one needs to write a query like this one:
SELECT ITM.DataAreaId , ITM.ItemId , ITM.ItemName , ILP.UnitId InventUnitId
, IPP.UnitId PurchUnitId , ISP.UnitId SalesUnitId , ILP.Price InventPrice , IPP.Price PurchPrice , ISP.Price SalesPrice FROM dbo.InventTable ITM JOIN dbo.InventTableModule ILP ON ITM.ItemId = ILP.ItemId AND ITM.DATAAREAID = ILP.DATAAREAID AND ILP.ModuleType = 0 -- Inventory JOIN dbo.InventTableModule IPP ON ITM.ItemId = IPP.ItemId AND ITM.DATAAREAID = IPP.DATAAREAID AND IPP.ModuleType = 1 -- Purchasing JOIN dbo.InventTableModule ISP ON ITM.ItemId = ISP.ItemId AND ITM.DATAAREAID = ISP.DATAAREAID AND ISP.ModuleType = 2 -- Sales WHERE ITM.DataAreaId = 'abc'
Looking at the query plan one can see that SQL Server uses three Merge Joins together with a Clustered Index Seek, and recommends using a Nonclustered Index to increase the performance:
To avoid needing to perform three joins, one can rewrite the query with the help of a GROUP BY, thus reducing the number of Merge Joins from three to one:
SELECT ITD.DataAreaID , ITD.ItemId , ITM.ItemName , ITD.InventPrice , ITD.InventPriceUnit , ITD.InventUnitId , ITD.PurchPrice , ITD.PurchPriceUnit , ITD.PurchUnitId , ITD.SalesPrice , ITD.SalesPriceUnit , ITD.SalesUnitId FROM dbo.InventTable ITM LEFT JOIN (—price details SELECT ITD.ITEMID , ITD.DATAAREAID , Max(CASE ITD.ModuleType WHEN 0 THEN ITD.Price END) InventPrice , Max(CASE ITD.ModuleType WHEN 0 THEN ITD.PriceUnit END) InventPriceUnit , Max(CASE ITD.ModuleType WHEN 0 THEN ITD.UnitId END) InventUnitId , Max(CASE ITD.ModuleType WHEN 1 THEN ITD.Price END) PurchPrice , Max(CASE ITD.ModuleType WHEN 1 THEN ITD.PriceUnit END) PurchPriceUnit , Max(CASE ITD.ModuleType WHEN 1 THEN ITD.UnitId END) PurchUnitId , Max(CASE ITD.ModuleType WHEN 2 THEN ITD.Price END) SalesPrice , Max(CASE ITD.ModuleType WHEN 2 THEN ITD.PriceUnit END) SalesPriceUnit , Max(CASE ITD.ModuleType WHEN 2 THEN ITD.UnitId END) SalesUnitId FROM dbo.InventTableModule ITD GROUP BY ITD.ITEMID , ITD.DATAAREAID ) ITD ON ITD.ITEMID = ITM.ITEMID AND ITD.DATAAREAID = ITM.DATAAREAID WHERE ITD.DataAreaID = 'abc' ORDER BY ITD.ItemId
And here’s the plan for it:
It seems that despite the overhead introduced by the GROUP BY, the second query performs better, at least when all or almost all records are performed.
In Dynamics AX there were seldom the occasions when I needed to write similar queries. Probably, most of the cases are related to the use of window functions (e.g. the first n Vendors or Vendor Prices). On the other side, a bug in previous versions of Oracle, which was limiting the number of JOINs that could be used, made me consider such queries also when was maybe not the case.
What about you? Did you had the chance to write similar queries? What made you consider the second type of query?
Happy coding!
No comments:
Post a Comment