Inventory Discrepancies

I'd like to start some discussion around inventory discrepancies and how to identify and correct them. I've searched around and seen various threads, but thought we might be able to aggregate some information here. We're looking at doing a periodic correction of inventory. I've been able to identify discrepancies, but have yet to determine best methods to fix them. Ideally the fix would be a stored procedure that we might run on a daily or weekly basis. 

Below is a query I've written to help identify our inventory discrepancies. It compares:

  • Cost Tier Quantities - timCostTier
  • Inventory Lot Quantities - timInvtLotBin
  • Warehouse Bin Quantities - timWhseBinInvt
  • Calculated Quantity - timInvtTran + timInvtTranCost

You can modify the 'where' clause to filter out the discrepancy types of interest. 


Declare @companyid varchar(3), @WhseId varchar(10)
Set @CompanyId = 'cbi'
Set @WhseID = 'Main' 

select
costtier.itemkey,
item.itemid,
costtier.QtyOnHand as [Cost Tier QOH],
WhseBin.PendQtyDec as [WhseBin PendQtyDec],
WhseBin.PendQtyInc as [WhseBin PendQtyInc],
WhseBin.QtyOnHand as [WhseBin QOH],
Lotbin.QtyOnHand as [LotBin QOH],
LotBin.PendQtyDec as [LotBin PendQtyDec],
LotBin.PendQtyInc as [LotBin PendQtyInc],
TranHist.QtyOnHand as [TranHist QOH],
stdcost.materialunitcost * (costtier.qtyonhand - whsebin.qtyonhand) as [Valuation Difference]
from 
	(select ctier.WhseKey, item.itemkey,
	sum(ctier.origqty - ctier.qtyused) as [QtyOnHand]
	from timcosttier ctier
	join timitem item on ctier.itemkey = item.itemkey
	where item.companyid = @CompanyId
	group by item.itemkey, ctier.WhseKey
	) costtier
left join 
	(select item.itemkey,
	sum(pendQtyDecrease) as [PendQtyDec],
	sum(pendQtyIncrease) as [PendQtyInc],
	sum(QtyOnHand) as [QtyOnHand]
	from timWhseBinInvt binInvt
	join timitem item on binInvt.itemkey = item.itemkey
	where item.companyid = @CompanyID
	group by item.itemkey
	) whsebin on costtier.itemkey = whsebin.itemkey
left join
	(select item.itemkey,
	sum(trancost.DistQty * sign(InvtTran.TranQty)) as [QtyOnHand]
	from timinvttrancost trancost
	join timinvttran invttran on trancost.invttrankey = invttran.invttrankey
	join timinvttranlog tranlog on trancost.invttrankey = tranlog.invttrankey
	join timitem item on invttran.itemkey = item.itemkey
	where item.companyid = @CompanyID and tranlog.trantype not in (709,716)-- and invttran.trandate >= '2009-1-15' --Date of Implementation (false trans prior to this date?)
	group by item.itemkey
	) TranHist on costtier.itemkey = TranHist.itemkey
left join
	(select item.itemkey,
	sum(lotbin.OrigQty - lotbin.QtyUsed) as [QtyOnHand],
	sum(pendQtyDecrease) as [PendQtyDec],
	sum(pendQtyIncrease) as [PendQtyInc]
	from timinvtlotbin lotbin
	join timinvtlot lot on lotbin.invtlotkey = lot.invtlotkey
	join timitem item on lot.itemkey = item.itemkey
	where item.companyid = @companyID
	group by item.itemkey
	) LotBin on costtier.itemkey = lotbin.itemkey
left join timWarehouse Whse on costtier.whsekey = Whse.whsekey
left join timinvtstdunitcost stdcost on costtier.itemkey = stdcost.itemkey
join timitem item on costtier.itemkey = item.itemkey
where Whse.whseid = @whseid 
and (costtier.QTYOnHand <> coalesce(WhseBin.QtyOnHand,0))
and item.status = 1 --Active Items
order by item.itemid


Any feedback on the query? (Not an SQL master, but always looking to improve)

I've also seen some built in stored procedures that are supposed to help recalculate inventory levels. Sounds like they work with varying degrees of success.

Please share any tools or practices that your organization has adopted.

  • I use a similar query (see below) to compare timWhseBinInvt, timInvtLotBin, and timCostTier (I hadn't noticed timInvtTranCost before). I run the query through sp_send_dbmail nightly so I'll get an email if things are off.

    SELECT  WhseID, ItemID, ISNULL((
                                     SELECT SUM(QtyOnHand)
                                     FROM   dbo.timWhseBinInvt
                                            JOIN dbo.timWhseBin
                                                ON dbo.timWhseBinInvt.WhseBinKey = dbo.timWhseBin.WhseBinKey
                                     WHERE  dbo.timWhseBin.WhseKey = dbo.timInventory.WhseKey
                                            AND dbo.timWhseBinInvt.ItemKey = dbo.timInventory.ItemKey
                                   ), 0) [timWhseBinInvt], ISNULL((
                                                                    SELECT  SUM(OrigQty - QtyUsed)
                                                                    FROM    dbo.timInvtLotBin
                                                                            JOIN dbo.timInvtLot
                                                                                ON dbo.timInvtLotBin.InvtLotKey = dbo.timInvtLot.InvtLotKey
                                                                    WHERE   dbo.timInvtLot.WhseKey = dbo.timInventory.WhseKey
                                                                            AND dbo.timInvtLot.ItemKey = dbo.timInventory.ItemKey
                                                                  ), 0) [timInvtLotBin], ISNULL((
                                                                                                  SELECT    SUM(OrigQty - QtyUsed)
                                                                                                  FROM      dbo.timCostTier
                                                                                                  WHERE     dbo.timCostTier.WhseKey = dbo.timInventory.WhseKey
                                                                                                            AND dbo.timCostTier.ItemKey = dbo.timInventory.ItemKey
                                                                                                ), 0) [timCostTier], '', '', '', '', '', '', '', '', '', '', '', '',
            '', '', '', ''
    FROM    dbo.timInventory
            JOIN dbo.timWarehouse
                ON dbo.timInventory.WhseKey = dbo.timWarehouse.WhseKey
            JOIN dbo.timItem
                ON dbo.timInventory.ItemKey = dbo.timItem.ItemKey
    WHERE   ISNULL((
                     SELECT SUM(QtyOnHand)
                     FROM   dbo.timWhseBinInvt
                            JOIN dbo.timWhseBin
                                ON dbo.timWhseBinInvt.WhseBinKey = dbo.timWhseBin.WhseBinKey
                     WHERE  dbo.timWhseBin.WhseKey = dbo.timInventory.WhseKey
                            AND dbo.timWhseBinInvt.ItemKey = dbo.timInventory.ItemKey
                   ), 0) <> ISNULL((
                                     SELECT SUM(OrigQty - QtyUsed)
                                     FROM   dbo.timInvtLotBin
                                            JOIN dbo.timInvtLot
                                                ON dbo.timInvtLotBin.InvtLotKey = dbo.timInvtLot.InvtLotKey
                                     WHERE  dbo.timInvtLot.WhseKey = dbo.timInventory.WhseKey
                                            AND dbo.timInvtLot.ItemKey = dbo.timInventory.ItemKey
                                   ), 0)
            OR ISNULL((
                        SELECT  SUM(QtyOnHand)
                        FROM    dbo.timWhseBinInvt
                                JOIN dbo.timWhseBin
                                    ON dbo.timWhseBinInvt.WhseBinKey = dbo.timWhseBin.WhseBinKey
                        WHERE   dbo.timWhseBin.WhseKey = dbo.timInventory.WhseKey
                                AND dbo.timWhseBinInvt.ItemKey = dbo.timInventory.ItemKey
                      ), 0) <> ISNULL((
                                        SELECT  SUM(OrigQty - QtyUsed)
                                        FROM    dbo.timCostTier
                                        WHERE   dbo.timCostTier.WhseKey = dbo.timInventory.WhseKey
                                                AND dbo.timCostTier.ItemKey = dbo.timInventory.ItemKey
                                      ), 0)
            OR ISNULL((
                        SELECT  SUM(OrigQty - QtyUsed)
                        FROM    dbo.timInvtLotBin
                                JOIN dbo.timInvtLot
                                    ON dbo.timInvtLotBin.InvtLotKey = dbo.timInvtLot.InvtLotKey
                        WHERE   dbo.timInvtLot.WhseKey = dbo.timInventory.WhseKey
                                AND dbo.timInvtLot.ItemKey = dbo.timInventory.ItemKey
                      ), 0) <> ISNULL((
                                        SELECT  SUM(OrigQty - QtyUsed)
                                        FROM    dbo.timCostTier
                                        WHERE   dbo.timCostTier.WhseKey = dbo.timInventory.WhseKey
                                                AND dbo.timCostTier.ItemKey = dbo.timInventory.ItemKey
                                      ), 0)
    ORDER BY WhseID, ItemID
  • in reply to Tim Rodman

    How about corrective actions? What are your experiences with the stored procedures spimRecalcInvtHist and spimRecalcPeriodHist?

    I ran those two queries in our test environment and didn't notice any corrections.

    Our organization has never investigated discrepancies before, so over our 6 years of use we have a large number of discrepancies. In some cases the CostTier Qty or WhseBin Qty will match our Tran History, and it's as simple as updating the odd one out. In other cases neither match. In some cases I'm wondering if the best action is to perform a physical count.

  • in reply to elliottN

    Investigating the history for something obvious or performing a physical count are the only two solutions that I'm aware of as well. But you still have to go one by one to resolve.

    The stored procedures that you mention only update the history tables, not the "on hand" tables. This is useful though for the Inventory Valuation report because it reads from the history tables for transactions before the beginning date that you select.

    Also, you might want to schedule spimRecalcInvenQtyOnForAll to update some of the timInventory quantities.

    The

  • in reply to Tim Rodman

    I had seen a script that attempted to correct quantities via timInvtTran history, but it seems to be an unreliable method. I do like the idea of the running the discrepancy report on a nightly basis and emailing results. I believe we will adopt that strategy as well.