Question

I have a database that tracks the location of widgets (i.e. an inventory system that tracks individual items rather than simply Counts of SKUs).

There are two tables concerned:

 InventoryUpdates (
     UpdateId   bigint PRIMARY KEY,
     LocationId bigint,
     Type       tinyint, -- an enum column
     DateTime   datetimoffset
 )

 WidgetState {
     SerialNumber varchar(20) PRIMARY KEY,
     UpdateId     bigint
 }

Explanation:

Inventory Updates are either "Complete", "Additive", or "Subtractive" (this is determined by the Type column). When an Update is Complete, it means the associated set of WidgetState represents the entire inventory (i.e. if a widget is listed, then it's there; and absence of a widget is positive verification that it was not present in the inventory at that time). When an Update is Additive, it means the Inventory needs to be determined by summing up all of the subsequent Additiveupdates since the last Complete update (just like incremental backups), finally a Subtractive update means those items included in the update should be excluded from the inventory. Of course, multiple Additive and Subtractive updates can appear after a Complete update.

So my problem is:

"Given the last Complete update and the set of all subsequent Additive and Subtractive updates, get the current inventory of a location"

The data would look like this:

InventoryUpdates
----------------------------------------------
UpdateId   LocationId  Type         DateTime
1          1           Complete     2014-01-01
2          1           Additive     2014-01-02
3          1           Subtractive  2014-01-03
4          1           Additive     2014-01-04

WidgetState
----------------------------------------------
SerialNumber  UpdateId
0001          1
0004          1
008B          2
0001          3
0004          3
0004          4

In this simple example, we see that the unique widget 0004 is known to be in the inventory in Update 1, then it's removed in Update 3, then added back in Update 4.

Supposing I want to find out the current inventory, my resultset should be:

WidgetsInInventory
----------------------------------------------
SerialNumber
0004
008B

There's probably a really simple solution, I just can't think of it right now. I'm thinking it might look something like this:

SELECT
    DISTINCT SerialNumber,
    Type 
FROM
    WidgetState
    INNER JOIN InventoryUpdates ON WidgetState.UpdateId = InventoryUpdates.UpdateId
WHERE
    InventoryUpdates.Type = Complete OR
    InventoryUpdates.Type = Additive
ORDER BY
    InventoryUpdates.DateTime DESC

EXCEPT

SELECT
    DISTINCT SerialNumber,
    Type 
FROM
    WidgetState
    INNER JOIN InventoryUpdates ON WidgetState.UpdateId = InventoryUpdates.UpdateId
WHERE
    InventoryUpdates.Type = Subtractive
ORDER BY
    InventoryUpdates.DateTime DESC

...but then it falls apart when a Subtraction happens regardless of the fact it might have happened with an earlier DateTime value than a subsequent Addition.

Update

I just had a thought while on my way home that this might be a solution that works, I'll test it out tomorrow.

The code below uses DISTINCT combined with ORDER BY to get the last appearance of each WidgetState, so if the last appearance was an Addition then it would cancel-out any previous Subtraction, and vice-versa (well, if the last appearance was a Subtraction then it can then be easily excluded from future super-queries with a WHERE Type <> Subtractive predicate). It's just gone 03:00h and I haven't had a chance to try it, but does the theory sound right? Thanks!

SELECT
    WidgetState.SerialNumber,
    InventoryUpdates.Type
FROM
    WidgetState
    INNER JOIN InventoryUpdates ON WidgetState.UpdateId = InventoryUpdates.UpdateId
WHERE
    InventoryUpdates.DateTime >= @dateTimeOfLastCompleteUpdate
    AND
    InventoryUpdates.LocationId >= @locationId
GROUP BY
    SerialNumber
ORDER BY
    InventoryUpdates.DateTime DESC
Was it helpful?

Solution 2

I did some research and found out about LAST_VALUE in conjunction with OVER ... PARTITION BY, however LAST_VALUE is only supported on SQL Server 2012. I'm using 2008 R2, so I needed to find something equivalent.

Fortunately there is: You can add ROW_NUMBER to a PARTITION and then select the top value (WHERE rowNumber = 1) when ordered correctly, so here is my final query:

SELECT
    *
FROM
    (
    SELECT
        WidgetStates.TId,
        InventoryUpdates.*,
        ROW_NUMBER() OVER (PARTITION BY WidgetStates.Tid ORDER BY InventoryUpdates.DateTime DESC) AS RowN
    FROM
        WidgetStates
        INNER JOIN InventoryUpdates ON WidgetStates.UpdateId = InventoryUpdates.UpdateId
    WHERE
        InventoryUpdates.DateTime >= @lastCompleteUpdateDateTime
        AND
        InventoryUpdates.LocationId = @locationId
    ) As Results
WHERE
    Results.RowN = 1
    AND
    Results.Type >= 0

OTHER TIPS

I would approach it differently. Simply counting the widgets. Put -1 if update is Subtractive and 1 for Complete and Additive. Then simply sum the flags and filter those with no-zero (positive) sum.

declare @dateTimeOfLastCompleteUpdate Date = '20140101'
declare @locationId int  = 1
;
with cte1 as
(
  select
    ws.SerialNumber,
    ws.UpdateId,
    CASE WHEN iu.Type = 'Subtractive' THEN -1 ELSE 1 END as Flag
  from 
    InventoryUpdates iu
    inner join WidgetState ws
      on ws.UpdateId = iu.UpdateId
  where
    iu.[DateTime] >= @dateTimeOfLastCompleteUpdate
    and iu.LocationId = @locationId
)
, cte2 as
(
  select
    SerialNumber,
    Flag,
    ROW_NUMBER() OVER(partition by SerialNumber order by UpdateId) as rn
  from
    cte1
)
, cte3 as
(
  select
    SerialNumber,
    sum(flag) as WidgetCount
  from 
    cte2 a
  where
    not exists 
    (
      select * 
      from cte2 b 
      where b.serialnumber = a.serialnumber
      and b.rn = a.rn - 1
      and b.flag = a.flag
    )
  group by
    SerialNumber
)
select SerialNumber from cte3 where WidgetCount = 1

Here is the fiddle.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top