Search code examples
sqlsql-serversetboolean-logicinventory-management

EXCEPT considering DateTime?


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

Solution

  • 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