{"id":892,"date":"2010-05-25T00:00:00","date_gmt":"2010-05-25T00:00:00","guid":{"rendered":"https:\/\/test.simple-talk.com\/uncategorized\/set-based-speed-phreakery-the-fifo-stock-inventory-sql-problem\/"},"modified":"2021-06-03T16:44:17","modified_gmt":"2021-06-03T16:44:17","slug":"set-based-speed-phreakery-the-fifo-stock-inventory-sql-problem","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/performance-sql-server\/set-based-speed-phreakery-the-fifo-stock-inventory-sql-problem\/","title":{"rendered":"FIFO Stock Inventory in SQL Server: Set-Based Solution vs Cursor-Based (with Benchmarks and the Winning Query)"},"content":{"rendered":"<p><b>FIFO (first-in, first-out) inventory accounting matches outgoing stock movements against the earliest incoming batches to determine cost of goods sold and remaining stock value &#8211; a classic problem in accounting and supply-chain software. In SQL, a naive implementation uses a cursor to walk stock movements in chronological order, matching each withdrawal against the oldest available receipts until the withdrawal is fully sourced. The set-based alternative is harder to design but typically 10-100x faster on production-scale data. This article presents the problem as a contest, compares the cursor-based solution to the winning set-based solution from Dave Ballantyne, and explains how the set-based approach works: a combination of running totals (implemented with window functions in SQL Server 2012+ or recursive CTEs on earlier versions), CTEs to attribute each receipt to the withdrawals it covers, and careful index design to support the aggregate queries. The specific problem in the article uses a Stock table tracking receipt and withdrawal transactions; the technique generalises to any running-balance-and-match accounting problem. Benchmarks show the set-based query completing in seconds where the cursor solution takes minutes on the same data.<\/b><\/p>\n<div id=\"pretty\">\n<p class=\"start\">Sometimes, solutions to the simplest sounding requests can be very difficult to implement. A case in point is Phil Factor&#8217;s second Speed Phreak Competition: <a href=\"http:\/\/ask.sqlservercentral.com\/questions\/826\/the-fifo-stock-inventory-sql-problem\">The &#8216;FIFO Stock Inventory&#8217; SQL Problem<\/a>. The competition presented a somewhat-simplified, but still realistic inventory accounting problem, where approximately 1500 different stock items are either purchased, sold, or returned. The mission was to calculate the remaining count, and value, of each item after all stock transactions are performed, using only T-SQL. This is a classic FIFO (First In, First Out) problem, or a queue, where the first items purchased or returned must be the first items sold.<\/p>\n<p>In my previous article, based around the <a href=\"http:\/\/www.simple-talk.com\/sql\/performance\/writing-efficient-sql-set-based-speed-phreakery\/\">Running Total Speed Phreak challenge<\/a>, I commented that, in my experience, there is usually &#8220;an easy solution and a fast solution&#8221;. The easy solution, often iterative and cursor-based, is simple to implement, easy to maintain, and straightforward. However, performance often becomes painfully unacceptable as the volume of data grows. The fast solution, usually set-based, is often harder to understand, and therefore maintain, but will perform well and scale gracefully.<\/p>\n<p>The motivation behind this series of articles was to dissect and analyze the cleverest and fastest SQL solutions to the challenges posed by the Speed Phreak competitions, expose and explain the core set-based techniques on which they rely, and so make these techniques more widely accessible to developers who need a faster and more scalable solution than the cursor can offer.<\/p>\n<p>This FIFO challenge is considerably more complex that the previous Running Total challenge, and my motto, &#8220;there&#8217;s an easy way and a fast way&#8221;, doesn&#8217;t quite hold true. Here, it turns out that there&#8217;s a &#8220;slow and difficult&#8221; way, using a cursor, and a &#8220;fast and even more difficult&#8221; way, using set-based techniques. This time it helps to not only think outside the box but to turn the box inside out.<\/p>\n<h1>The FIFO Stock Inventory Challenge<\/h1>\n<p>In this challenge, we have a table, <span class=\"STCodeinTextChar\">Stock<\/span>, which we use to track the track movements of stock in and out of our imaginary stock warehouse. Our warehouse is initially empty, and stock then moves into the warehouse as a result of a stock purchase (<span class=\"STCodeinTextChar\">tranCode = &#8216;IN&#8217;<\/span>), or due to a subsequent return (<span class=\"STCodeinTextChar\">tranCode = &#8216;RET&#8217;<\/span>), and stock moves out of the warehouse when it is sold (<span class=\"STCodeinTextChar\">tranCode = &#8216;OUT&#8217;<\/span>). Each type of stock tem is indentified by an <span class=\"STCodeinTextChar\">ArticleID<\/span>. Each movement of stock in or out of the warehouse, due to a purchase, sale or return of a given item, results in a row being added to the <span class=\"STCodeinTextChar\">Stock<\/span> table, uniquely identified by the value in the <span class=\"STCodeinTextChar\">StockID<\/span> identity column, and describing how many items were added or removed, the price for purchases, the date of the transaction, and so on.<\/p>\n<p>Stock movements always occur on a first-in, first-out (FIFO) basis, so the stock initially purchased or returned is the stock that is first to be sold, and the current stock inventory for a given <span class=\"STCodeinTextChar\">ArticleID<\/span> will consist of those items that were the last to be added to the inventory. The challenge, in essence, was to find the most efficient way to calculate the value of the remaining stock for a given item, given that:<\/p>\n<ul>\n<li>There is a price for each purchase<\/li>\n<li>Items are sold in the order that they were purchased or returned<\/li>\n<li>The price of each item sold is based the price based on when the item was acquired<\/li>\n<li>Each return should be priced at the same price as the most recent purchase before the return<\/li>\n<\/ul>\n<p>Included with the challenge description was an abbreviated example, as follows in Table 1:<\/p>\n<table class=\"LightShading-Accent11\">\n<tbody>\n<tr>\n<td valign=\"top\">\n<p class=\"MsoNormal\">StockID<\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"MsoNormal\">ArticleID<\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"MsoNormal\">TranDate<\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"MsoNormal\">TranCode<\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"MsoNormal\">Items<\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"MsoNormal\">Price<\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"MsoNormal\">CurrentItems<\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"MsoNormal\">CurrentValue<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\">\n<p class=\"MsoNormal\"><b>4567<\/b><\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"MsoNormal\">10000<\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"MsoNormal\">10:45:07<\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"MsoNormal\">IN<\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"MsoNormal\">738<\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"MsoNormal\">245.94<\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"MsoNormal\">738<\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"MsoNormal\">181,503.72<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\">\n<p class=\"MsoNormal\"><b>21628<\/b><\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"MsoNormal\">10000<\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"MsoNormal\">12:05:25<\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"MsoNormal\">OUT<\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"MsoNormal\">600<\/p>\n<\/td>\n<td valign=\"top\">\u00a0<\/td>\n<td valign=\"top\">\n<p class=\"MsoNormal\">138<\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"MsoNormal\">33,939.72<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\">\n<p class=\"MsoNormal\"><b>22571<\/b><\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"MsoNormal\">10000<\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"MsoNormal\">14:39:27<\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"MsoNormal\">IN<\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"MsoNormal\">62<\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"MsoNormal\">199.95<\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"MsoNormal\">200<\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"MsoNormal\">46,336.62<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\">\n<p class=\"MsoNormal\"><b>30263<\/b><\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"MsoNormal\">10000<\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"MsoNormal\">16:14:13<\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"MsoNormal\">OUT<\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"MsoNormal\">165<\/p>\n<\/td>\n<td valign=\"top\">\u00a0<\/td>\n<td valign=\"top\">\n<p class=\"MsoNormal\">35<\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"MsoNormal\">6,998.25<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\">\n<p class=\"MsoNormal\"><b>42090<\/b><\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"MsoNormal\">10000<\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"MsoNormal\">18:18:58<\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"MsoNormal\">RET<\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"MsoNormal\">5<\/p>\n<\/td>\n<td valign=\"top\">\u00a0<\/td>\n<td valign=\"top\">\n<p class=\"MsoNormal\">40<\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"MsoNormal\">7,998.00<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\">\n<p class=\"MsoNormal\"><b>53143<\/b><\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"MsoNormal\">10000<\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"MsoNormal\">20:18:54<\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"MsoNormal\">IN<\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"MsoNormal\">500<\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"MsoNormal\">135.91<\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"MsoNormal\">540<\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"MsoNormal\">75,953.00<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p class=\"caption\">Table 1: Example of stock movement<\/p>\n<ul>\n<li>First we add 738 items (each $245.94) to the stock, for a total of $181,503.72<\/li>\n<li>Then we take out 600 items (each 245.94) from the stock, leaving a total of $33,939.72<\/li>\n<li>Then we insert 62 items (each 199.95) to the stock, for a total of $46,336.62<\/li>\n<li>Then we take out 165 items (138 each 245.94 and 27 each 199.95), leaving a total of $6,998.25<\/li>\n<li>Then we return 5 items. We can&#8217;t track at which price we took them out; so all returns are priced at the price of the latest ones inserted before the return. Even if there should be items left for the price of 245.94, the returned items are valued for 199.95. After the return, the current stock value is $7,998.00<\/li>\n<li>The final purchase (each $135.91) adds $67,995.00 to the stock value, for a total of $75,953.00<\/li>\n<\/ul>\n<p>A sample cursor solution was provided with the original challenge and entrants were asked to come up with something better, given the following rules:<\/p>\n<ul>\n<li>Calculate the number and value of each <span class=\"STCodeinTextChar\">ArticleID<\/span> after all the transactions are processed<\/li>\n<li>The results must be in<\/li>\n<\/ul>\n<p><span class=\"STCodeinTextChar\">ArticleID<\/span> order You can use any method you prefer as long as it involves only T-SQL You can use a tally, or numbers, table called <span class=\"STCodeinTextChar\">TallyNumbers<\/span>, beginning with 0 Running totals are provided in the sample data to help validate your results, but you can&#8217;t use them in the solution You cannot alter the design of the <span class=\"STCodeinTextChar\">Stock<\/span> table, but you can alter the provided non-clustered indexes The <span class=\"STCodeinTextChar\">CurrentItems<\/span> and <span class=\"STCodeinTextChar\">CurrentValue<\/span> columns are provided to allow you to validate your algorithms only; you cannot use them in your solution. Once again, several gifted T-SQL developers came up with very fast solutions to the challenge. The winner was <span class=\"STBold\">Dave Ballantyne <\/span>(<a href=\"http:\/\/dataidol.com\/davebally\/\">blog<\/a> | <a href=\"http:\/\/twitter.com\/DaveBally\">twitter<\/a>), whose solution processed a million rows in 3 seconds.<\/p>\n<h1>Setting Up<\/h1>\n<p>To test the winning solution, your SQL Server instance must be running SQL Server 2008. To set up this challenge perform these steps:<\/p>\n<ul>\n<li>Create a new database on a development or test SQL Server 2008 instance<\/li>\n<li>Run the code from Listing 1 to create the <span class=\"STCodeinTextChar\">Stock<\/span> table, associated indexes, and the <span class=\"STCodeinTextChar\">TallyNumbers<\/span> table.<\/li>\n<\/ul>\n<pre class=\"theme:ssms2012 lang:tsql\">CREATE TABLE dbo.Stock (\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 StockID INT IDENTITY(1, 1)\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0NOT NULL ,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 ArticleID SMALLINT NOT NULL ,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 TranDate DATETIME NOT NULL ,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 TranCode VARCHAR(3) NOT NULL ,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 Items INT NOT NULL ,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 Price MONEY NULL ,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 CONSTRAINT [PK_Stock] PRIMARY KEY CLUSTERED ( StockID ASC )\r\n\u00a0\u00a0\u00a0 )\r\n\u00a0\r\nCREATE NONCLUSTERED INDEX IX_Input\r\nON dbo.Stock (TranCode, ArticleID)\r\nINCLUDE (TranDate, Items, Price) \r\nWHERE TranCode IN ('IN', 'RET')\r\n\u00a0\r\nCREATE NONCLUSTERED INDEX IX_Output ON dbo.Stock (TranCode, ArticleID)\r\nINCLUDE (TranDate, Items)\r\nWHERE TranCode = 'OUT'\r\n\u00a0\r\nIF OBJECT_ID('dbo.TallyNumbers') IS NULL \r\n\u00a0\u00a0\u00a0 BEGIN\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 CREATE TABLE dbo.TallyNumbers ( Number INT NOT NULL )\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 INSERT\u00a0 INTO dbo.TallyNumbers\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ( Number\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0)\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0SELECT TOP ( 1000000 )\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0ROW_NUMBER() OVER ( ORDER BY A.OBJECT_ID ) - 1 AS Number\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0FROM\u00a0\u00a0\u00a0 master.sys.objects AS A\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0CROSS JOIN master.sys.objects AS B\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0CROSS JOIN master.sys.objects AS C\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0CROSS JOIN master.sys.objects AS D \r\n\u00a0\u00a0\u00a0 END\r\n<\/pre>\n<p class=\"caption\">Listing 1: The code to create the tables, indexes and helper table<\/p>\n<ul>\n<li>Download the test data from the http:\/\/sql.developerworkshop.net\/fifo.zip<\/li>\n<li>Unzip the file and import the data (consisting of over a million rows) into the <span class=\"STCodeinTextChar\">Stock<\/span> table using your method of choice (I used the SQL Server Import Wizard).<\/li>\n<\/ul>\n<p>Run this query <span class=\"STCodeinTextChar\">SELECT<\/span> <span class=\"STCodeinTextChar\">*<\/span> <span class=\"STCodeinTextChar\">FROM<\/span> <span class=\"STCodeinTextChar\">Stock <\/span>to view the data. Figure 1 shows some sample data.<\/p>\n<p class=\"illustration\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1038-KK1.JPG\" alt=\"1038-KK1.JPG\" \/><\/p>\n<p class=\"caption\">Figure 1: The FIFO data<\/p>\n<p>Remember that you can use the <span class=\"STCodeinTextChar\">CurrentItems<\/span> and <span class=\"STCodeinTextChar\">CurrentValue<\/span> column values to validate the algorithm, but you can&#8217;t just query them to return the answer.<\/p>\n<h1>The Cursor Solution<\/h1>\n<p>Most of us come to T-SQL initially from a developer background so creating cursor-based solutions is generally easy. With a bit of thought, we often find more efficient set-based solutions without difficulty. This particular puzzle is tricky regardless of which technique you may choose. Because the cursor-based <a href=\"http:\/\/ask.sqlservercentral.com\/questions\/826\/the-fifo-stock-inventory-sql-problem\">solution<\/a> may be found along with the original challenge, it will not be reproduced here in its entirety, though is available with the code download bundle for this article (see the downloads at the bottom of the article).<\/p>\n<p>The cursor-based solution calculates the <span class=\"STCodeinTextChar\">CurrentItems<\/span> and <span class=\"STCodeinTextChar\">CurrentValue<\/span> for each transaction and repopulates those columns in the <span class=\"STCodeinTextChar\">Stock<\/span> table. If you set the value of the two columns to zero or <span class=\"STCodeinTextChar\">NULL<\/span> in every row of the <span class=\"STCodeinTextChar\">Stock<\/span> table, running the cursor-based solution will repopulate the two columns with the correct values. Once completing all the calculations, a query returns the final results.<\/p>\n<p>The code begins by creating a temp table called <span class=\"STCodeinTextChar\">#work <\/span>with an <span class=\"STCodeinTextChar\">Identity<\/span> column and a column to hold a price. The purpose of the cursor is to populate <span class=\"STCodeinTextChar\">#work<\/span> with one row for each individual item received or returned along with the price that applies to that transaction. Because of the <span class=\"STCodeinTextChar\">Identity<\/span> column, the rows can easily be sorted in the order in which they were inserted. The items sold are removed from the #work table in order and the remaining rows are used later to calculate the final count of items and the value.<\/p>\n<p>The <span class=\"STCodeinTextChar\">SELECT<\/span> statement used for the cursor is ordered by <span class=\"STCodeinTextChar\">ArticleID<\/span> and <span class=\"STCodeinTextChar\">TranDate<\/span>. Once inside the cursor loop, the code truncates the <span class=\"STCodeinTextChar\">#work<\/span> table and resets the <span class=\"STCodeinTextChar\">@LastPrice<\/span> variable to <span class=\"STCodeinTextChar\">NULL<\/span> whenever the loop processes a new <span class=\"STCodeinTextChar\">ArticleID<\/span>. It is very important to the solution to complete all work on one <span class=\"STCodeinTextChar\">ArticleID<\/span> before starting on the next.<\/p>\n<p>Each transaction type requires a different action. The extract in Listing 2 shows how the <span class=\"STCodeinTextChar\">IN<\/span> and <span class=\"STCodeinTextChar\">RET<\/span> rows are processed.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">IF @TranCode = 'IN' \r\n\u00a0\u00a0\u00a0\u00a0BEGIN \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0INSERT\u00a0 #Work\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ( Price\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0)\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0SELECT\u00a0 @Price\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0FROM\u00a0\u00a0\u00a0 dbo.TallyNumbers\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0WHERE\u00a0\u00a0 Number &lt; @Items \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0SET @LatestPrice = @Price \r\n\u00a0\u00a0\u00a0\u00a0END \r\nIF @TranCode = 'RET' \r\n\u00a0\u00a0\u00a0\u00a0INSERT\u00a0 #Work\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ( Price \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0)\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0SELECT\u00a0 @LatestPrice\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0FROM\u00a0\u00a0\u00a0 dbo.TallyNumbers\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0WHERE\u00a0\u00a0 Number &lt; @Items\r\n<\/pre>\n<p class=\"caption\">Listing 2: Processing IN and RET rows<\/p>\n<p>If the <span class=\"STCodeinTextChar\">TranCode = &#8216;IN&#8217;<\/span>, then a statement inserts rows saving the current price into the <span class=\"STCodeinTextChar\">#work<\/span> table. By using the <span class=\"STCodeinTextChar\">TallyNumbers<\/span> table, we ensure that the number of rows inserted is equal to the number of items purchased. The <span class=\"STCodeinTextChar\">@LatestPrice<\/span> value is set equal to the current price in case it is needed for the next row. If the <span class=\"STCodeinTextChar\">TranCode = &#8216;RET&#8217;<\/span> then several rows are inserted into <span class=\"STCodeinTextChar\">#work<\/span>. This is similar to the <span class=\"STCodeinTextChar\">&#8216;IN&#8217;<\/span> transaction, except that it uses the price from the last IN row.\u00a0 Since the <span class=\"STCodeinTextChar\">#work<\/span> table contains an identity column (<span class=\"STCodeinTextChar\">RowID<\/span>), the order of the inserts can easily be determined.<\/p>\n<p>After processing some <span class=\"STCodeinTextChar\">IN<\/span> and <span class=\"STCodeinTextChar\">RET<\/span> rows, the <span class=\"STCodeinTextChar\">#work<\/span> table will contain one row for each item in stock along with the price for that item. Figure 2 shows an example of how the <span class=\"STCodeinTextChar\">#work<\/span> table would look, if you actually stopped the processing to take a look at it, after processing two <span class=\"STCodeinTextChar\">IN<\/span> rows that added three items at $258.30 and two items at $260.<\/p>\n<p class=\"illustration\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1038-KK2.JPG\" alt=\"1038-KK2.JPG\" width=\"115\" height=\"103\" \/><\/p>\n<p class=\"caption\">Figure 2: The <span class=\"STCodeinTextChar\">#work<\/span> table with some sample data<\/p>\n<p>The next possibility is the movement of items out of stock, or <span class=\"STCodeinTextChar\">OUT<\/span> rows. Listing 3 shows the code processes these <span class=\"STCodeinTextChar\">OUT<\/span> rows.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">IF @TranCode = 'OUT' \r\n\u00a0\u00a0\u00a0\u00a0DELETE\u00a0 w\r\n\u00a0\u00a0\u00a0\u00a0FROM\u00a0\u00a0\u00a0 ( SELECT TOP ( @Items )\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0RowID\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0FROM\u00a0\u00a0\u00a0\u00a0\u00a0 #Work\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0ORDER BY\u00a0 RowID\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0) AS w\r\n<\/pre>\n<p class=\"caption\">Listing 3: Processing the <span class=\"STCodeinTextChar\">OUT<\/span> rows<\/p>\n<p class=\"MsoNormal\">In this case, the code deletes a number of rows from <span class=\"STCodeinTextChar\">#work<\/span> equal to the number of items taken out of stock. In order to delete the correct number of rows, the code uses <span class=\"STCodeinTextChar\">TOP<\/span> along with the variable <span class=\"STCodeinTextChar\">@Items<\/span>. The ability to use a variable along with <span class=\"STCodeinTextChar\">TOP<\/span> was introduced with SQL Server 2005 along with the ability to use <span class=\"STCodeinTextChar\">TOP<\/span> with a <span class=\"STCodeinTextChar\">DELETE<\/span> statement. Unfortunately, you cannot use <span class=\"STCodeinTextChar\">ORDER<\/span> <span class=\"STCodeinTextChar\">BY<\/span> with a <span class=\"STCodeinTextChar\">DELETE<\/span> statement so you can&#8217;t guarantee that the correct rows would be deleted. It is critical that the code deletes the rows inserted first, so the deletions are made by using a derived table sorted in the correct order.<\/p>\n<p class=\"MsoNormal\">After the rows are inserted or deleted from <span class=\"STCodeinTextChar\">#work<\/span>, for a particular row from the <span class=\"STCodeinTextChar\">Stock<\/span> table, the <span class=\"STCodeinTextChar\">#work<\/span> table contains one row for each item remaining in stock for that <span class=\"STCodeinTextChar\">ArticleID<\/span>, along the price for each item. The next step is to update the <span class=\"STCodeinTextChar\">CurrentItems<\/span> and <span class=\"STCodeinTextChar\">CurrentValue<\/span> columns of the <span class=\"STCodeinTextChar\">stock<\/span> table for the row currently being processed. Listing 4 shows the section of code used to update <span class=\"STCodeinTextChar\">Stock<\/span>. Everything left in the <span class=\"STCodeinTextChar\">#work<\/span> table is used to calculate the count and value for the current transaction.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">UPDATE\u00a0 s\r\nSET\u00a0\u00a0\u00a0\u00a0 s.CurrentItems = w.CurrentItems ,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0s.CurrentValue = COALESCE(w.CurrentValue, 0)\r\nFROM\u00a0\u00a0\u00a0 dbo.Stock AS s\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0INNER JOIN ( SELECT COUNT(*) AS CurrentItems ,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0SUM(Price) AS CurrentValue\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0FROM\u00a0\u00a0 #Work\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0) AS w ON s.StockID = @StockID\r\n<\/pre>\n<p class=\"caption\">Listing 4: Updating the <span class=\"STCodeinTextChar\">Stock<\/span> table<\/p>\n<p>The <span class=\"STCodeinTextChar\">UPDATE<\/span> statement uses a derived table in the form of an aggregate query that calculates the current number of items multiplied by the current value. The derived table is necessary because you can&#8217;t directly use an aggregate query in an <span class=\"STCodeinTextChar\">UPDATE<\/span> statement. A common table expression (CTE) could also have been used.<\/p>\n<p>The original data actually contains the correct values for <span class=\"STCodeinTextChar\">CurrentItems<\/span> and <span class=\"STCodeinTextChar\">CurrentValue<\/span>, and the <span class=\"STCodeinTextChar\">UPDATE<\/span> statement overwrites these values. If you set all the <span class=\"STCodeinTextChar\">CurrentItems<\/span> and <span class=\"STCodeinTextChar\">CurrentValue<\/span> columns to <span class=\"STCodeinTextChar\">NULL<\/span> first, the code from Listing 4 sets each row back to the correct values.<\/p>\n<p>Since the cursor-based solution calculated the current stock level and value for every transaction, the answer to the challenge may be found in the row for each <span class=\"STCodeinTextChar\">ArticleID<\/span> with the latest<span class=\"STCodeinTextChar\"> TranDate<\/span>. Listing 5 contains the code to display the final answer.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">SELECT\u00a0 ArticleID ,\r\nCurrentItems ,\r\nCurrentValue\r\nFROM\u00a0\u00a0\u00a0 ( SELECT\u00a0\u00a0\u00a0 ArticleID ,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0CurrentItems ,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0CurrentValue ,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0ROW_NUMBER() OVER ( PARTITION BY ArticleID \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0ORDER BY TranDate DESC ) AS recID\r\n\u00a0\u00a0FROM\u00a0\u00a0\u00a0\u00a0\u00a0 dbo.Stock\r\n) AS d\r\nWHERE\u00a0\u00a0 recID = 1\r\nORDER BY ArticleID\r\n<\/pre>\n<p class=\"caption\">Listing 5: Producing the final answer<\/p>\n<p>This query uses a derived table and the <span class=\"STCodeinTextChar\">ROW_NUMBER()<\/span> function to list the rows, along with a row number (<span class=\"STCodeinTextChar\">recID<\/span>) for each row. The row numbers are applied in descending order of <span class=\"STCodeinTextChar\">TranDate<\/span>, partitioned by <span class=\"STCodeinTextChar\">ArticleID<\/span>. This ensures that the last stock transaction for each <span class=\"STCodeinTextChar\">ArticleID<\/span> will have a <span class=\"STCodeinTextChar\">recID<\/span> value of one. The query is filtered by <span class=\"STCodeinTextChar\">recID<\/span> to ensure that only the rows with last <span class=\"STCodeinTextChar\">tranDate<\/span> are selected, i.e. those with a <span class=\"STCodeinTextChar\">recID<\/span> equal to one. The reason for using a derived table is that you can&#8217;t directly filter on the results of the <span class=\"STCodeinTextChar\">ROW_NUMBER()<\/span> function. This is another example that would work equally as well with a CTE. Figure 3 shows a sample of the answer.<\/p>\n<p class=\"illustration\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1038-KK3.JPG\" alt=\"1038-KK3.JPG\" width=\"196\" height=\"125\" \/><\/p>\n<p class=\"caption\">Figure 3: A sample of the answer<\/p>\n<p>This solution performs one update to each row in the <span class=\"STCodeinTextChar\">Stock<\/span> table. That&#8217;s over 1 million single row updates in addition to either an insertion into, or deletion from, the <span class=\"STCodeinTextChar\">#work<\/span> table for each row processed, for a total of 2 million transactions! It does, however, allow you to see the current value at any transaction if you need to do so. The winning solution only calculates the final values for each <span class=\"STCodeinTextChar\">ArticleID<\/span>, so it provides much less information. However, if the only requirement is to know the very final values, then this cursor solution is doing much more work than it needs to do.<\/p>\n<h1>The Winning Solution<\/h1>\n<p>The winning solution, from Dave Ballantyne, takes advantage of many of the new features introduced with SQL Server 2005 and 2008, such as common table expressions (CTEs) and filtered indexes, and manages to solve this problem without a cursor. It returns the answer in an amazing 3 seconds.<\/p>\n<p>Aside from its clever use of use of SQL Server features, it relies on a very important insight into the nature of the problem being solved. The previous cursor solution kept track of the running total, one transaction at a time, as items were added and removed. David realized that in a FIFO problem, this is not necessary: we just need to focus on what is left after all processing is complete.<\/p>\n<p>In order to understand why his is true, let&#8217;s consider the age-old practice of emptying our pockets of loose change, at the end of each day, into a &#8220;money jar&#8221;, but instead of a simple jar we add our coins to a money &#8220;queue&#8221;, and that we only we removed coins from the front of the queue, meaning that the first coins added are the first coins removed.<\/p>\n<p class=\"illustration\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1038-KK4.JPG\" alt=\"1038-KK4.JPG\" \/><\/p>\n<p class=\"caption\">Thanks to <a href=\"http:\/\/www.flickr.com\/photos\/dmclear\/\">Dave Mclear<\/a> for reuse of this photo<\/p>\n<p>If we wanted to calculate the value of the remaining coins on any particular day, would we be concerned about the value of the coins that had already been removed from the front of the queue? Would we have to work through all the additions and removals of coins that had happened over time? No, we could easily calculate the current value just by looking at the remaining coins in the queue.<\/p>\n<p>For example, say I start out by adding a nickel (worth 5 cents each), two pennies (worth 1 cent each) and five dimes (worth 10 cents each). The next day, I add a quarter (worth 25 cents). The day after that, I remove seven coins. What is the value of the coins left in the queue? I only need to look at the remaining coins and their values to know that I have two coins remaining worth a total of 35 cents. The same idea applies to our challenge. We do not need to know what has been removed from stock; we only need to know what remains.<\/p>\n<p>Let&#8217;s now take look at David&#8217;s solution, step by step.<\/p>\n<h2>Adding Indexes<\/h2>\n<p>The contest does not prohibit the addition of indexes, and the script adds three non-clustered indexes, as shown in Listing 6.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">CREATE NONCLUSTERED INDEX [IX_Dave_General]\r\n\u00a0\u00a0\u00a0 ON [dbo].[Stock]\r\n\u00a0\u00a0\u00a0\u00a0 (\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 [ArticleID] ASC, \r\n\u00a0\u00a0\u00a0\u00a0\u00a0 [TranDate] DESC, \r\n\u00a0\u00a0\u00a0\u00a0\u00a0 [TranCode] ASC\r\n\u00a0\u00a0\u00a0\u00a0 )\u00a0\u00a0\u00a0 \r\nINCLUDE ( [Items], [Price]) \r\n\u00a0\u00a0\u00a0 WITH (\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 PAD_INDEX\u00a0 = OFF,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 STATISTICS_NORECOMPUTE\u00a0 = OFF,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 SORT_IN_TEMPDB = OFF,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 IGNORE_DUP_KEY = OFF,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 DROP_EXISTING = OFF,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 ONLINE = OFF, ALLOW_ROW_LOCKS\u00a0 = ON, \r\n\u00a0\u00a0\u00a0\u00a0\u00a0 ALLOW_PAGE_LOCKS\u00a0 = ON)\r\n\u00a0\u00a0\u00a0 ON [PRIMARY]\u00a0\u00a0\u00a0 \r\n\u00a0GO\r\n\u00a0\r\nCREATE NONCLUSTERED INDEX [IX_Dave_Items]\r\n\u00a0\u00a0\u00a0 ON [dbo].[Stock]\r\n\u00a0\u00a0\u00a0\u00a0 (\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \r\n[ArticleID] ASC,\r\n[TranDate] ASC\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 )\r\n\u00a0\u00a0\u00a0 INCLUDE ( [Items])\u00a0\u00a0\u00a0 \r\n\u00a0\u00a0\u00a0 WHERE ([TranCode] IN ('IN', 'RET'))\u00a0\u00a0 \r\n\u00a0\u00a0\u00a0 WITH (\r\n\u00a0\u00a0PAD_INDEX\u00a0 = OFF,\r\n\u00a0\u00a0STATISTICS_NORECOMPUTE\u00a0 = OFF, \r\n\u00a0\u00a0SORT_IN_TEMPDB = OFF,\r\n\u00a0\u00a0IGNORE_DUP_KEY = OFF, \r\n\u00a0\u00a0DROP_EXISTING = OFF,\r\n\u00a0\u00a0ONLINE = OFF,\r\n\u00a0\u00a0ALLOW_ROW_LOCKS\u00a0 = ON, \r\n\u00a0\u00a0ALLOW_PAGE_LOCKS\u00a0 = ON\r\n\u00a0)\r\n\u00a0\u00a0 ON [PRIMARY]\u00a0\u00a0\u00a0 \r\nGO \r\n\u00a0\u00a0 \r\nCREATE NONCLUSTERED INDEX [IX_Dave_Price]\r\n\u00a0\u00a0 ON [dbo].[Stock]\r\n\u00a0\u00a0\u00a0 (\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \r\n\u00a0\u00a0\u00a0\u00a0\u00a0 [ArticleID] ASC, \r\n\u00a0\u00a0\u00a0\u00a0\u00a0 [TranDate] ASC\r\n\u00a0\u00a0\u00a0\u00a0 )\u00a0\u00a0\u00a0 \r\n\u00a0\u00a0\u00a0 INCLUDE ( [Price])\u00a0\u00a0\u00a0 \r\n\u00a0\u00a0\u00a0 WHERE ([TranCode]='IN')\u00a0\u00a0\u00a0 \r\n\u00a0\u00a0\u00a0 WITH (\r\n\u00a0\u00a0\u00a0PAD_INDEX\u00a0 = OFF,\r\n\u00a0\u00a0\u00a0STATISTICS_NORECOMPUTE\u00a0 = OFF,\r\n\u00a0\u00a0\u00a0SORT_IN_TEMPDB = OFF, \r\n\u00a0\u00a0\u00a0IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF,\r\n\u00a0\u00a0\u00a0ONLINE = OFF, ALLOW_ROW_LOCKS\u00a0 = ON,\r\n\u00a0\u00a0\u00a0ALLOW_PAGE_LOCKS\u00a0 = ON, FILLFACTOR = 100) \r\n\u00a0\u00a0\u00a0 ON [PRIMARY]\u00a0\u00a0\u00a0 \r\nGO\u00a0\u00a0 \r\n<\/pre>\n<p class=\"caption\">Listing 6: New indexes<\/p>\n<p>The first index, <span class=\"STCodeinTextChar\">IX_Dave_General<\/span>, contains three columns: <span class=\"STCodeinTextChar\">ArticleID<\/span>, <span class=\"STCodeinTextChar\">TranDate<\/span>, and <span class=\"STCodeinTextChar\">TranCode<\/span>. It also utilizes a new feature introduced with SQL Server 2005: <span class=\"STBold\">included columns<\/span>. The index includes the <span class=\"STCodeinTextChar\">Items<\/span> and <span class=\"STCodeinTextChar\">Price<\/span> columns. Including these columns will prevent bookmark lookups to the <span class=\"STCodeinTextChar\">Stock<\/span> table when these columns are required in the results and the query is filtered or joined on the index key columns. In other words, the database engine can read the <span class=\"STCodeinTextChar\">Items<\/span> and <span class=\"STCodeinTextChar\">Price<\/span> values from the index even though these columns are not part of the index key.<\/p>\n<p>The second and third indexes, <span class=\"STCodeinTextChar\">IX_Dave_Items<\/span> and <span class=\"STCodeinTextChar\">IX_Dave_Price<\/span>, contain a new feature introduced with SQL Server 2008: <span class=\"STBold\">filtered indexes<\/span>. A filtered indexes utilizes a <span class=\"STCodeinTextChar\">WHERE<\/span> clause, and, therefore, only indexes a subset of the rows from a table. Filtered indexes are usually much smaller than the traditional non-clustered indexes on the entire table and are more efficient. They are especially useful for columns that contain many <span class=\"STCodeinTextChar\">NULL<\/span> values allowing you to filter out the rows containing <span class=\"STCodeinTextChar\">NULL<\/span>.<\/p>\n<h2>Set-Based Stock Inventory<\/h2>\n<p>The cursor-based solution processes every transaction to come up with the answer; it starts at the front of the queue and works its way back. The focus of the set-based solution is the back of the queue; i.e. the items left in stock at the end of all the transactions. This solution contains the following steps:<\/p>\n<ol>\n<li>Calculate the number of items remaining in stock for each <span class=\"STCodeinTextChar\">ArticleID<\/span>, at the end of the transactions. This is done by adding up all the <span class=\"STCodeinTextChar\">IN<\/span> and <span class=\"STCodeinTextChar\">RET<\/span> quantities and subtracting the <span class=\"STCodeinTextChar\">OUT<\/span> quantities.<\/li>\n<li>Calculate a running total for each<\/li>\n<\/ol>\n<p><span class=\"STCodeinTextChar\">ArticleID<\/span> for the rows adding items to stock &#8211; the <span class=\"STCodeinTextChar\">IN<\/span> and <span class=\"STCodeinTextChar\">RET<\/span> rows in reverse order. Table 2 shows a simple example for one fictitious <span class=\"STCodeinTextChar\">ArticleID<\/span>:<\/p>\n<p class=\"STNumberedList1CxSpMiddle\">\u00a0<\/p>\n<table class=\"MsoTableClassic2\">\n<tbody>\n<tr>\n<td valign=\"top\">\n<p class=\"STNumberedList1CxSpMiddle\">Transaction Date<\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"STNumberedList1CxSpMiddle\"><b>Stock Transaction ID<\/b><\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"STNumberedList1CxSpMiddle\"><b>Quantity Added to Stock<\/b><\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"STNumberedList1CxSpLast\"><b>Running Total<\/b><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\">\n<p class=\"STNumberedList1CxSpFirst\"><b>Jan-06-2009<\/b><\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"STNumberedList1CxSpMiddle\">700<\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"STNumberedList1CxSpMiddle\">50<\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"STNumberedList1CxSpLast\">50<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\">\n<p class=\"STNumberedList1CxSpFirst\"><b>Jan-05-2009<\/b><\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"STNumberedList1CxSpMiddle\">699<\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"STNumberedList1CxSpMiddle\">50<\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"STNumberedList1CxSpLast\">100<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\">\n<p class=\"STNumberedList1CxSpFirst\"><b>Jan-04-2009<\/b><\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"STNumberedList1CxSpMiddle\">698<\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"STNumberedList1CxSpMiddle\">150<\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"STNumberedList1CxSpLast\">250<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\">\n<p class=\"STNumberedList1CxSpFirst\"><b>Jan-03-2009<\/b><\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"STNumberedList1CxSpMiddle\">697<\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"STNumberedList1CxSpMiddle\">25<\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"STNumberedList1CxSpLast\">275<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\">\n<p class=\"STNumberedList1CxSpFirst\"><b>Jan-02-2009<\/b><\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"STNumberedList1CxSpMiddle\">696<\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"STNumberedList1CxSpMiddle\">10<\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"STNumberedList1CxSpLast\">285<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p class=\"caption\">Table 2: The reverse running totals<\/p>\n<p>\nFind the first row from the reverse running total for each <span class=\"STCodeinTextChar\">ArticleID<\/span> that contains at least as many items as the items left in stock for that <span class=\"STCodeinTextChar\">ArticleID<\/span>. For example, if this <span class=\"STCodeinTextChar\">ArticleID <\/span>has 200 items remaining in the queue after all processing is complete, then the row with 250 is found. Figure out how many items from the row found in step 3 row will be needed. Continuing with the same example, 100 items will be needed from the stock added on Jan-04-2009. (200 total items needed &#8211; 100 from the previous rows) Find the price for the <span class=\"STCodeinTextChar\">IN<\/span> rows and the previous price for the <span class=\"STCodeinTextChar\">RET<\/span> rows that will be needed to calculate the value of the remaining items. In this example, the price for the rows with StockID 700, 699, and 698 are needed. If any of the rows are returns, then the price for the previous purchase will be used. We don&#8217;t care about the price for any row past <span class=\"STCodeinTextChar\">StockID<\/span> 698 because the items from those transactions have been already removed from the queue. Multiply the number of items by the price for each transaction and add up the result. The number of items to multiply will be the number of items purchased or returned except for the transaction found in step 3. Table 3 shows an example: <\/p>\n<table class=\"MsoTableClassic2\">\n<tbody>\n<tr>\n<td valign=\"top\">\n<p class=\"STNumberedList1CxSpMiddle\"><b>Stock ID<\/b><\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"STNumberedList1CxSpMiddle\">Transaction Quantity<\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"STNumberedList1CxSpMiddle\">Required Quantity<\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"STNumberedList1CxSpMiddle\">Price<\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"STNumberedList1CxSpLast\">Extended Price<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\">\n<p class=\"STNumberedList1CxSpFirst\"><b>700<\/b><\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"STNumberedList1CxSpMiddle\">50<\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"STNumberedList1CxSpMiddle\">50<\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"STNumberedList1CxSpMiddle\">$1<\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"STNumberedList1CxSpLast\">$50<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\">\n<p class=\"STNumberedList1CxSpFirst\"><b>699<\/b><\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"STNumberedList1CxSpMiddle\">50<\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"STNumberedList1CxSpMiddle\">50<\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"STNumberedList1CxSpMiddle\">$2<\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"STNumberedList1CxSpLast\">$100<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\">\n<p class=\"STNumberedList1CxSpFirst\"><b>698<\/b><\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"STNumberedList1CxSpMiddle\">150<\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"STNumberedList1CxSpMiddle\">100<\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"STNumberedList1CxSpMiddle\">$3<\/p>\n<\/td>\n<td valign=\"top\">\n<p class=\"STNumberedList1CxSpLast\">$300<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p class=\"caption\">Table 3: Calculate the extended price<\/p>\n<p class=\"MsoNormal\">There is more than one way to accomplish these steps. One way is to use temporary tables to save the intermediary information. Another method, which David used in the winning solution, is to use common table expressions (CTEs). The advantage of CTEs over temp tables is that the CTEs reside in memory while temp tables live in <span class=\"STCodeinTextChar\">tempdb<\/span>. Listing 7 contains the brilliant script.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\/* Sum up the ins and outs to calculate the remaining stock level *\/\r\nWITH\u00a0\u00a0\u00a0 cteStockSum\r\n\u00a0\u00a0AS ( SELECT\u00a0\u00a0 ArticleID ,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0SUM(CASE WHEN TranCode = 'OUT' THEN 0 - Items\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0ELSE Items\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0END) AS TotalStock\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0FROM\u00a0\u00a0\u00a0\u00a0 dbo.Stock\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0GROUP BY ArticleID\r\n\u00a0\u00a0\u00a0\u00a0\u00a0),\r\ncteReverseInSum\r\n\u00a0\u00a0AS ( SELECT\u00a0 s.ArticleID ,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0s.TranDate ,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ( SELECT SUM(i.Items)\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0FROM\u00a0\u00a0 dbo.Stock AS i WITH ( INDEX ( IX_Dave_Items ) )\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0WHERE\u00a0\u00a0\u00a0\u00a0 i.ArticleID = s.ArticleID\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0AND i.TranCode IN ( 'IN', 'RET' )\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0AND i.TranDate &gt;= s.TranDate\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0) AS RollingStock ,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0s.Items AS ThisStock\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0FROM\u00a0\u00a0\u00a0 dbo.Stock AS s\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0WHERE\u00a0\u00a0 s.TranCode IN ( 'IN', 'RET' )\r\n\u00a0\u00a0\u00a0\u00a0\u00a0), \r\n\/* Using the rolling balance above find the first stock movement in that meets \r\n\u00a0\u00a0 (or exceeds) our required stock level *\/\r\n\/* and calculate how much stock is required from the earliest stock in *\/\r\ncteWithLastTranDate\r\n\u00a0\u00a0AS ( SELECT\u00a0\u00a0 w.ArticleID ,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0w.TotalStock ,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0LastPartialStock.TranDate ,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0LastPartialStock.StockToUse ,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0LastPartialStock.RunningTotal ,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0w.TotalStock - LastPartialStock.RunningTotal\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0+ LastPartialStock.StockToUse AS UseThisStock\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0FROM\u00a0\u00a0\u00a0\u00a0 cteStockSum AS w\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0CROSS APPLY ( SELECT TOP ( 1 )\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0z.TranDate ,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0z.ThisStock AS StockToUse ,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0z.RollingStock AS RunningTotal\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0FROM\u00a0 cteReverseInSum AS z\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0WHERE z.ArticleID = w.ArticleID\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0AND z.RollingStock &gt;= w.TotalStock\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0ORDER BY\u00a0 z.TranDate DESC\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0) AS LastPartialStock\r\n\u00a0\u00a0\u00a0\u00a0\u00a0)\r\n\/*\u00a0 Sum up the cost of 100% of the stock movements in after the returned stockid and for that stockid we need 'UseThisStock' items' *\/\r\nSELECT\u00a0 y.ArticleID ,\r\ny.TotalStock AS CurrentItems ,\r\nSUM(CASE WHEN e.TranDate = y.TranDate THEN y.UseThisStock\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0ELSE e.Items\r\n\u00a0\u00a0\u00a0\u00a0END * Price.Price) AS CurrentValue\r\nFROM\u00a0\u00a0\u00a0 cteWithLastTranDate AS y\r\nINNER JOIN dbo.Stock AS e WITH ( INDEX ( IX_Dave_Items ) )\r\n\u00a0\u00a0\u00a0\u00a0ON e.ArticleID = y.ArticleID\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0AND e.TranDate &gt;= y.TranDate\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0AND e.TranCode IN ('IN', 'RET' )\r\nCROSS APPLY ( \r\n\/* Find the Price of the item in *\/ SELECT TOP ( 1 )\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0p.Price\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0FROM dbo.Stock AS p \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0WITH ( INDEX ( IX_Dave_Price ) )\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0WHERE\u00a0\u00a0 p.ArticleID = e.ArticleID\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0AND p.TranDate &lt;= e.TranDate\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0AND p.TranCode = 'IN'\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0ORDER BY p.TranDate DESC\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0) AS Price\r\nGROUP BY y.ArticleID ,y.TotalStock\r\nORDER BY y.ArticleID\r\n<\/pre>\n<p class=\"caption\">Listing 7: The winning entry<\/p>\n<p>In order to keep better track of the data, the <span class=\"STCodeinTextChar\">Stock<\/span> table has been populated with just three items, represented by <span class=\"STCodeinTextChar\">ArticleID<\/span>s (10001, 10002, 10009). Listing 8 contains the script I used to create a new <span class=\"STCodeinTextChar\">Stock<\/span> table with just this subset of data.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">--Rename the Stock Table\r\nEXEC dbo.sp_rename @objname = N'[dbo].[Stock]', @newname = N'Stock_SAVE', @objtype = N'OBJECT'\r\n--Create a new Stock Table\r\nCREATE TABLE dbo.Stock (\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 StockID INT IDENTITY(1, 1)\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0NOT NULL ,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 ArticleID SMALLINT NOT NULL ,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 TranDate DATETIME NOT NULL ,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 TranCode VARCHAR(3) NOT NULL ,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 Items INT NOT NULL ,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 Price MONEY NULL ,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 CONSTRAINT [PK_Stock1] PRIMARY KEY CLUSTERED ( StockID ASC )\r\n\u00a0\u00a0\u00a0 )\r\nCREATE NONCLUSTERED INDEX IX_Input\r\nON dbo.Stock (TranCode, ArticleID)\r\nINCLUDE (TranDate, Items, Price) \r\nWHERE TranCode IN ('IN', 'RET')\r\n\u00a0\r\nCREATE NONCLUSTERED INDEX IX_Output ON dbo.Stock (TranCode, ArticleID)\r\nINCLUDE (TranDate, Items)\r\nWHERE TranCode = 'OUT'\r\n\u00a0\r\n--Add back in Dave's indexes\r\nCREATE NONCLUSTERED INDEX [IX_Dave_General]\r\n\u00a0\u00a0\u00a0 ON [dbo].[Stock]\r\n\u00a0\u00a0\u00a0\u00a0 (\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 [ArticleID] ASC, \r\n\u00a0\u00a0\u00a0\u00a0\u00a0 [TranDate] DESC, \r\n\u00a0\u00a0\u00a0\u00a0\u00a0 [TranCode] ASC\r\n\u00a0\u00a0\u00a0\u00a0 )\u00a0\u00a0\u00a0 \r\nINCLUDE ( [Items], [Price]) \r\n\u00a0\u00a0\u00a0 WITH (\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 PAD_INDEX\u00a0 = OFF,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 STATISTICS_NORECOMPUTE\u00a0 = OFF,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 SORT_IN_TEMPDB = OFF,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 IGNORE_DUP_KEY = OFF,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 DROP_EXISTING = OFF,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 ONLINE = OFF, ALLOW_ROW_LOCKS\u00a0 = ON, \r\n\u00a0\u00a0\u00a0\u00a0\u00a0 ALLOW_PAGE_LOCKS\u00a0 = ON)\r\n\u00a0\u00a0\u00a0 ON [PRIMARY]\u00a0\u00a0\u00a0 \r\n\u00a0GO\r\n\u00a0\r\nCREATE NONCLUSTERED INDEX [IX_Dave_Items]\r\n\u00a0\u00a0\u00a0 ON [dbo].[Stock]\r\n\u00a0\u00a0\u00a0\u00a0 (\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \r\n[ArticleID] ASC,\r\n[TranDate] ASC\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 )\r\n\u00a0\u00a0\u00a0 INCLUDE ( [Items])\u00a0\u00a0\u00a0 \r\n\u00a0\u00a0\u00a0 WHERE ([TranCode] IN ('IN', 'RET'))\u00a0\u00a0 \r\n\u00a0\u00a0\u00a0 WITH (\r\n\u00a0\u00a0PAD_INDEX\u00a0 = OFF,\r\n\u00a0\u00a0STATISTICS_NORECOMPUTE\u00a0 = OFF, \r\n\u00a0\u00a0SORT_IN_TEMPDB = OFF,\r\n\u00a0\u00a0IGNORE_DUP_KEY = OFF, \r\n\u00a0\u00a0DROP_EXISTING = OFF,\r\n\u00a0\u00a0ONLINE = OFF,\r\n\u00a0\u00a0ALLOW_ROW_LOCKS\u00a0 = ON, \r\n\u00a0\u00a0ALLOW_PAGE_LOCKS\u00a0 = ON\r\n\u00a0)\r\n\u00a0\u00a0 ON [PRIMARY]\u00a0\u00a0\u00a0 \r\nGO \r\n\u00a0\u00a0 \r\nCREATE NONCLUSTERED INDEX [IX_Dave_Price]\r\n\u00a0\u00a0 ON [dbo].[Stock]\r\n\u00a0\u00a0\u00a0 (\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \r\n\u00a0\u00a0\u00a0\u00a0\u00a0 [ArticleID] ASC, \r\n\u00a0\u00a0\u00a0\u00a0\u00a0 [TranDate] ASC\r\n\u00a0\u00a0\u00a0\u00a0 )\u00a0\u00a0\u00a0 \r\n\u00a0\u00a0\u00a0 INCLUDE ( [Price])\u00a0\u00a0\u00a0 \r\n\u00a0\u00a0\u00a0 WHERE ([TranCode]='IN')\u00a0\u00a0\u00a0 \r\n\u00a0\u00a0\u00a0 WITH (\r\n\u00a0\u00a0\u00a0PAD_INDEX\u00a0 = OFF,\r\n\u00a0\u00a0\u00a0STATISTICS_NORECOMPUTE\u00a0 = OFF,\r\n\u00a0\u00a0\u00a0SORT_IN_TEMPDB = OFF, \r\n\u00a0\u00a0\u00a0IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF,\r\n\u00a0\u00a0\u00a0ONLINE = OFF, ALLOW_ROW_LOCKS\u00a0 = ON,\r\n\u00a0\u00a0\u00a0ALLOW_PAGE_LOCKS\u00a0 = ON, FILLFACTOR = 100) \r\n\u00a0\u00a0\u00a0 ON [PRIMARY]\u00a0\u00a0\u00a0 \r\nGO \r\nSET IDENTITY_INSERT Stock ON\r\n\u00a0\r\n--Insert the rows\r\nINSERT INTO Stock (StockID, ArticleID, TranDate, TranCode, Items, Price)\r\nSELECT StockID, ArticleID, TranDate, TranCode, Items, Price\r\nFROM Stock_SAVE \r\nWHERE ArticleID IN ('10001','10002','10003')\r\n<\/pre>\n<p class=\"caption\">Listing 8: The script to create a new <span class=\"STCodeinTextChar\">Stock<\/span><b> table<\/b><\/p>\n<p>The code in the set-based solution contains just one actual statement. This is surprising since the statement itself is very complex. It is composed of several CTEs and sub-queries. One nice feature of CTEs that is used in this solution is the ability to base one CTE on a previously defined CTE.<\/p>\n<p>In order to understand what the code actually does, I decided to translate the solution into the temp table method in order to decompose the one complex statement into several sections. That way, we can take a peek at the data from each CTE as it is created. Listing 9 shows how the data from the first two CTEs is saved into temp tables, and then displays this data.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">SELECT\u00a0 ArticleID ,\r\nSUM(CASE WHEN TranCode = 'OUT' THEN 0 - Items\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0ELSE Items\r\n\u00a0\u00a0\u00a0\u00a0END) AS TotalStock\r\nINTO\u00a0\u00a0\u00a0 #cteStockSum\r\nFROM\u00a0\u00a0\u00a0 dbo.Stock\r\nGROUP BY ArticleID\r\n\u00a0\u00a0\u00a0 \r\nSELECT\u00a0 s.ArticleID ,\r\ns.TranDate ,\r\n( SELECT\u00a0\u00a0\u00a0 SUM(i.Items)\r\n\u00a0\u00a0FROM\u00a0\u00a0\u00a0\u00a0\u00a0 dbo.Stock AS i WITH ( INDEX ( IX_Dave_Items ) )\r\n\u00a0\u00a0WHERE\u00a0\u00a0\u00a0\u00a0 i.ArticleID = s.ArticleID\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0AND i.TranCode IN ( 'IN', 'RET' )\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0AND i.TranDate &gt;= s.TranDate\r\n) AS RollingStock ,\r\ns.Items AS ThisStock\r\nINTO\u00a0\u00a0\u00a0 #cteReverseInSum\r\nFROM\u00a0\u00a0\u00a0 dbo.Stock AS s\r\nWHERE\u00a0\u00a0 s.TranCode IN ( 'IN', 'RET' )\r\n\u00a0\r\n--Display the results\r\nSELECT\u00a0 *\r\nFROM\u00a0\u00a0\u00a0 #cteStockSum\r\n\u00a0\r\nSELECT\u00a0 *\r\nFROM\u00a0\u00a0\u00a0 #cteReverseInSum\r\nWHERE\u00a0\u00a0 ArticleID = 10009\r\nORDER BY TranDate DESC\r\n<\/pre>\n<p class=\"caption\">Listing 9: Saving the first two temp tables<\/p>\n<p>The first statement creates a table, <span class=\"STCodeinTextChar\">#cteStockSum<\/span>, containing each <span class=\"STCodeinTextChar\">ArticleID<\/span> and the final total of items (Figure 4). Remember, that the number of items left in stock is what is important here.<\/p>\n<p class=\"illustration\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1038-KK5.JPG\" alt=\"1038-KK5.JPG\" width=\"135\" height=\"68\" \/><\/p>\n<p class=\"caption\">Figure 4: The <span class=\"STCodeinTextChar\">#cteStockSum<\/span> table<\/p>\n<p>The second statement creates a table, <span class=\"STCodeinTextChar\">#cteReverseInSum<\/span>. The first interesting thing to notice is that Dave used an index hint to make sure that the special index he created was used. I&#8217;ll discuss more about index hints later in the article. The results of this statement is a list of all the <span class=\"STCodeinTextChar\">IN<\/span> and <span class=\"STCodeinTextChar\">RET<\/span> rows along with a backwards running total; in other words, how many items have been added to <span class=\"STCodeinTextChar\">Stock<\/span>, when looking at the back of the queue. Since we are concerned about the items left after all the processing is complete, we are concerned about the items added last.<\/p>\n<p class=\"MsoNormal\">The statement joins the <span class=\"STCodeinTextChar\">Stock<\/span> table to itself, using a correlated sub-query. A correlated sub-query cannot run independently because it references the outer query within the <span class=\"STCodeinTextChar\">WHERE<\/span> clause. In this case, after matching on <span class=\"STCodeinTextChar\">ArticleID<\/span>, all rows in the sub-query with a <span class=\"STCodeinTextChar\">TranDate<\/span> greater than or equal to the <span class=\"STCodeinTextChar\">TranDate<\/span> in the outer query are used to calculate the sum (this is referred to as a triangular join). The <span class=\"STCodeinTextChar\">RollingStock<\/span> column comes from the sub-query, and is a sum of the items added on the <span class=\"STCodeinTextChar\">TranDate<\/span> and later. The <span class=\"STCodeinTextChar\">RollingStock<\/span> values will be used to figure out which items are still in the queue at the end of the stock transactions. In other words, this query determines which of the transactions at the end of the queue actually added the items that are left once all transactions have completed. Figure 5 shows the reverse running totals for some of the rows for <span class=\"STCodeinTextChar\">ArticleID<\/span> 10009. The <span class=\"STCodeinTextChar\">ThisStock<\/span> column is the original number of items added during the transaction.<\/p>\n<p class=\"illustration\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1038-KK6.JPG\" alt=\"1038-KK6.JPG\" width=\"299\" height=\"129\" \/><\/p>\n<p class=\"caption\">Figure 5: The #cteReverseInSum table rows for Article 10009<\/p>\n<p>The next section of code creates the <span class=\"STCodeinTextChar\">#cteWithLastTranDate<\/span> table using the two temp tables created in the previous step. In the original code, the <span class=\"STCodeinTextChar\">cteWithLastTranDate<\/span> definition contained the two previously defined CTEs. Listing 10 shows the code. This statement also contains a correlated sub-query, but with a twist.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">SELECT\u00a0 w.ArticleID ,\r\nw.TotalStock ,\r\nLastPartialStock.TranDate ,\r\nLastPartialStock.StockToUse ,\r\nLastPartialStock.RunningTotal ,\r\nw.TotalStock - LastPartialStock.RunningTotal\r\n+ LastPartialStock.StockToUse AS UseThisStock\r\nINTO\u00a0\u00a0\u00a0 #cteWithLastTranDate\r\nFROM\u00a0\u00a0\u00a0 #cteStockSum AS w\r\nCROSS APPLY ( SELECT TOP ( 1 )\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0z.TranDate ,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0z.ThisStock AS StockToUse ,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0z.RollingStock AS RunningTotal\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0FROM\u00a0\u00a0\u00a0\u00a0\u00a0 #cteReverseInSum AS z\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0WHERE\u00a0\u00a0\u00a0\u00a0 z.ArticleID = w.ArticleID\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0AND z.RollingStock &gt;= w.TotalStock\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0ORDER BY\u00a0 z.TranDate DESC\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0) AS LastPartialStock\r\n\u00a0\r\n--Display the results\r\nSELECT\u00a0 *\r\nFROM\u00a0\u00a0\u00a0 #cteWithLastTranDate\r\n<\/pre>\n<p class=\"caption\">Listing 10: Creating #cteWithLastTranDate<\/p>\n<p>The results of running Listing 10 can be seen in Figure 6. Once again, my code saves the results of\u00a0 David&#8217;s CTE into a temp table. The inner query in this section finds the first row from <span class=\"STCodeinTextChar\">#cteReverseInSum<\/span> that is at least the <span class=\"STCodeinTextChar\">TotalStock<\/span> value stored in the <span class=\"STCodeinTextChar\">#cteStockSum<\/span> table for each <span class=\"STCodeinTextChar\">ArticleID<\/span>. By using <span class=\"STCodeinTextChar\">CROSS APPLY<\/span>, this correlated sub-query looks like a derived table by appearing in the <span class=\"STCodeinTextChar\">FROM<\/span> clause and making more than one column available to the outer query. It is not a derived table, however, because a derived table does not reference the outer query. The <span class=\"STCodeinTextChar\">CROSS APPLY<\/span> operator allows the inner query to be applied once for each row of the outer query. You might wonder why the code doesn&#8217;t just join the two tables. In this case, we need to find one row from the inner query for every row of the outer query. If we use <span class=\"STCodeinTextChar\">TOP(1)<\/span> in a regular join, only one total row would be returned instead of one row for each <span class=\"STCodeinTextChar\">ArticleID<\/span>.<\/p>\n<p class=\"illustration\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1038-KK7.JPG\" alt=\"1038-KK7.JPG\" width=\"427\" height=\"75\" \/><\/p>\n<p class=\"caption\">Figure 6: The #cteWithLastTranDate results<\/p>\n<p>The <span class=\"STCodeinTextChar\">SELECT<\/span> list of the outer query contains the <span class=\"STCodeinTextChar\">ArticleID<\/span> and <span class=\"STCodeinTextChar\">TotalStock<\/span> from the <span class=\"STCodeinTextChar\">#cteStockSum<\/span> table. The next three columns (<span class=\"STCodeinTextChar\">TranDate<\/span>, <span class=\"STCodeinTextChar\">StockToUse<\/span>, and <span class=\"STCodeinTextChar\">RunningTotal<\/span>) are from the inner query based on <span class=\"STCodeinTextChar\">#cteReverseInSum<\/span>. Next in the list is a calculation, <span class=\"STCodeinTextChar\">UseThisStock<\/span>:<\/p>\n<pre>w.TotalStock - LastPartialStock.RunningTotal + \r\nLastPartialStock.StockToUse AS UseThisStock\r\n<\/pre>\n<p>The calculation is used to determine how many items from the oldest row are required to cover the remaining items that will be needed. There are 3555 items of <span class=\"STCodeinTextChar\">ArticleID<\/span> 10009 left in stock. If you take a look at Figure 5 again, you will see that we have to go back to the seventh row containing 4212 in the <span class=\"STCodeinTextChar\">RollingStock<\/span> column to cover that amount. We need all the items in the first six rows and some of them from the seventh row. The most straightforward way to calculate this would be to subtract the <span class=\"STCodeinTextChar\">RollingStock<\/span> in the sixth row from the items needed, 3555 &#8211; 3524. Thirty-one items from the seventh row plus all the items added to stock from rows one through six remain in stock.<\/p>\n<p>David figured out the difference between the covering row (row 7 in Figure 5) and the previous row is the number of items added. Now the calculation becomes 3555 &#8211; (4212 &#8211; 688), still equaling 31. Instead of looking back at row 6, he just subtracts the items added at row 7 from the running total. \u00a0Now we have the number of items remaining from the earliest qualifying row without visiting the previous row to do the calculation. If you take a look at Figure 6 again, you will see a row for each <span class=\"STCodeinTextChar\">ArticleID<\/span>, showing the earliest transaction date required to cover the items left. The <span class=\"STCodeinTextChar\">StockToUse<\/span> column contains the number of items added at that transaction, and the <span class=\"STCodeinTextChar\">UseThisStock<\/span> column contains the number of items needed in addition to the transaction rows after this transaction.<\/p>\n<p>The final statement provides the answer to the challenge. See Listing 11 for the code.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">SELECT\u00a0 y.ArticleID ,\r\ny.TotalStock AS CurrentItems ,\r\nSUM(CASE WHEN e.TranDate = y.TranDate THEN y.UseThisStock\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0ELSE e.Items\r\n\u00a0\u00a0\u00a0\u00a0END * Price.Price) AS CurrentValue\r\nFROM\u00a0\u00a0\u00a0 #cteWithLastTranDate AS y\r\nINNER JOIN dbo.Stock AS e WITH ( INDEX ( IX_Dave_Items ) )\r\n\u00a0\u00a0\u00a0\u00a0\u00a0ON e.ArticleID = y.ArticleID\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0AND e.TranDate &gt;= y.TranDate\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0AND e.TranCode IN ('IN', 'RET' )\r\nCROSS APPLY ( SELECT TOP ( 1 )\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0p.Price\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0FROM\u00a0\u00a0\u00a0\u00a0\u00a0 dbo.Stock AS p WITH ( INDEX ( IX_Dave_Price ) )\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0WHERE\u00a0\u00a0\u00a0\u00a0 p.ArticleID = e.ArticleID\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0AND p.TranDate &lt;= e.TranDate\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0AND p.TranCode = 'IN'\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0ORDER BY\u00a0 p.TranDate DESC\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0) AS Price\r\nGROUP BY y.ArticleID ,\r\ny.TotalStock\r\nORDER BY y.ArticleID\r\n<\/pre>\n<p class=\"caption\">Listing 11: The final answer<\/p>\n<p>The first interesting feature of this statement is the query hints specifying which indexes the query should use. Even though this is called &#8220;hint&#8221;, the database engine is forced to follow whatever the hint specifies, even if the performance suffers. Caution is required when using hints, because the database engine usually does a better job optimizing the query without the hint. In this case, the indexes were strategically designed with this query in mind. Just to see what would happen, I removed the hints and found by viewing the execution plan that the database engine used the <span class=\"STCodeinTextChar\">IX_Dave_Items<\/span> index but not the <span class=\"STCodeinTextChar\">IX_Dave_Price<\/span> index. When comparing the execution times for each method against the original data, the original solution was just a couple of seconds faster with the hints.<\/p>\n<p>At this point, just running the query in Listing 11 will give the answer. However, to take a closer look and figure out what is going on here, I removed the <span class=\"STCodeinTextChar\">SUM<\/span> function and the <span class=\"STCodeinTextChar\">GROUP<\/span> <span class=\"STCodeinTextChar\">BY<\/span> clause and expanded the column list (Listing 12).<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">SELECT\u00a0 y.ArticleID ,\r\ny.TotalStock AS CurrentItems ,\r\ne.TranDate ,\r\ny.TranDate ,\r\ny.UseThisStock ,\r\ne.Items ,\r\nPrice.Price ,\r\nCASE WHEN e.TranDate = y.TranDate THEN y.UseThisStock\r\n\u00a0\u00a0\u00a0\u00a0\u00a0ELSE e.Items\r\nEND * Price.Price AS CurrentValue\r\nFROM\u00a0\u00a0\u00a0 #cteWithLastTranDate AS y\r\nINNER JOIN dbo.Stock AS e WITH ( INDEX ( IX_Dave_Items ) )\r\n\u00a0\u00a0\u00a0\u00a0 \u00a0ON e.ArticleID = y.ArticleID\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0AND e.TranDate &gt;= y.TranDate\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0AND e.TranCode IN ('IN', 'RET' )\r\nCROSS APPLY ( \r\n\/* Find the Price of the item in *\/ SELECT TOP ( 1 )\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0p.Price\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0FROM\u00a0\u00a0\u00a0 dbo.Stock AS p\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0WITH ( INDEX ( IX_Dave_Price ) )\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0WHERE\u00a0\u00a0 p.ArticleID = e.ArticleID\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0AND p.TranDate &lt;= e.TranDate\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0AND p.TranCode = 'IN'\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0ORDER BY p.TranDate DESC\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0) AS Price\r\nORDER BY y.ArticleID\r\n<\/pre>\n<p class=\"caption\">Listing 12: The final query without aggregation<\/p>\n<p>Figure 7 displays a sample of the results. This query joins the <span class=\"STCodeinTextChar\">#cteWithLastTranDate<\/span> table to the original <span class=\"STCodeinTextChar\">Stock<\/span> table. It also contains a correlated sub-query with <span class=\"STCodeinTextChar\">CROSS APPLY<\/span>, using <span class=\"STCodeinTextChar\">Stock<\/span>. The <span class=\"STCodeinTextChar\">#cteWithLastTranDate<\/span> table specifies the earliest row to use for each <span class=\"STCodeinTextChar\">ItemID<\/span>. By joining <span class=\"STCodeinTextChar\">Stock<\/span>, all the rows adding items to stock that are required for the solution are retrieved. Finally, the sub-query provides the prices. If the <span class=\"STCodeinTextChar\">RET<\/span> rows contained a price, this would not have been necessary. By using the sub-query, the query retrieves the price for <span class=\"STCodeinTextChar\">IN<\/span> rows and the previous price for <span class=\"STCodeinTextChar\">RET<\/span> rows.<\/p>\n<p class=\"MsoNormal\">Finally, the code contains a <span class=\"STCodeinTextChar\">CASE<\/span> statement in the <span class=\"STCodeinTextChar\">SELECT<\/span> list. If the <span class=\"STCodeinTextChar\">TranDate<\/span> in the row from <span class=\"STCodeinTextChar\">Stock<\/span> matches the <span class=\"STCodeinTextChar\">TranDate<\/span> in the earliest row &#8211; the one we where we need just some of the items &#8211; then use the calculated value. Otherwise, use the all the stock items for that row, the <span class=\"STCodeinTextChar\">Items<\/span> value.<\/p>\n<p class=\"illustration\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1038-KK8.JPG\" alt=\"1038-KK8.JPG\" \/><\/p>\n<p class=\"caption\">Figure 7: The unaggregated results<\/p>\n<p>By reverting back to the query found in Listing 11, you will see the results of running David&#8217;s original script intact (Figure 8).<\/p>\n<p class=\"illustration\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1038-KK9.JPG\" alt=\"1038-KK9.JPG\" width=\"209\" height=\"70\" \/><\/p>\n<p class=\"caption\">Figure 8: The final results for three ArticleIDs<\/p>\n<h2>Solution Highlights<\/h2>\n<p>The important thing to remember when solving this problem is that you need to focus on the items remaining in stock. You don&#8217;t have to calculate the current items and values for each transaction. Here are some other important things to learn from this solution:<\/p>\n<ul>\n<li>CTEs can be used to store intermediary results instead of temp tables<\/li>\n<li>CTEs can be based on other CTEs<\/li>\n<li>Use <span class=\"STCodeinTextChar\">CROSS APPLY<\/span> with a correlated sub-query when the sub-query must be applied once for each outer row and the normal rules for correlated sub-queries must be broken<\/li>\n<li>Use<\/li>\n<\/ul>\n<p><span class=\"STCodeinTextChar\">TOP(1)<\/span> and <span class=\"STCodeinTextChar\">ORDER BY<\/span> in a sub-query to return the first matching row Query hints can improve performance, but use caution since the database engine will follow the hint even if it hurts performance<\/p>\n<p>Table 4 contains the results for each of the three methods.<\/p>\n<div>\n<table class=\"MsoTableGrid\">\n<tbody>\n<tr>\n<td>\n<p class=\"MsoNormal\"><b>Method<\/b><\/p>\n<\/td>\n<td>\n<p class=\"MsoNormal\"><b>Time<\/b><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p class=\"MsoNormal\">Cursor<\/p>\n<\/td>\n<td>\n<p class=\"MsoNormal\">40 minutes<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p class=\"MsoNormal\">Solution with CTEs<\/p>\n<\/td>\n<td>\n<p class=\"MsoNormal\">3 seconds<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p class=\"MsoNormal\">Solution with temp tables using indexes<\/p>\n<\/td>\n<td>\n<p class=\"MsoNormal\">18 seconds<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<p class=\"caption\">Table 4: The comparison<\/p>\n<h1>Conclusion<\/h1>\n<p>Sometimes the most seemingly straightforward problem is difficult to figure out if you haven&#8217;t seen it before. The description for this problem focuses on adding and removing items one transaction at a time. The solution, instead, requires that you focus on what is left after all processing is complete. Focusing on the information provided in the initial challenge will lead you down the wrong path; make sure you understand exactly what you need to do and do not waste time providing information outside the scope of the problem.<\/p>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Solving the FIFO (first-in, first-out) stock inventory problem in SQL Server with a set-based query instead of a cursor. Compares the performance of both approaches, with the winning set-based solution from Dave Ballantyne using window functions and running totals &#8211; and benchmarks showing the speedup.&hellip;<\/p>\n","protected":false},"author":110218,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[143529],"tags":[4168,5197,5113,4206,4150,4151,5196],"coauthors":[11292],"class_list":["post-892","post","type-post","status-publish","format-standard","hentry","category-performance-sql-server","tag-database","tag-fifo","tag-kathi-kellenberger","tag-performance","tag-sql","tag-sql-server","tag-sql-speed"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/892","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/110218"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=892"}],"version-history":[{"count":7,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/892\/revisions"}],"predecessor-version":[{"id":74763,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/892\/revisions\/74763"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=892"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=892"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=892"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=892"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}