如何为需要从以前的订单计算的库存数量编写 SQL

Posted

技术标签:

【中文标题】如何为需要从以前的订单计算的库存数量编写 SQL【英文标题】:How to write SQL for stock quantity that requires calculation from previous orders 【发布时间】:2015-03-05 08:59:54 【问题描述】:

我有两张表,一张用于当前产品的总库存,一张用于产品订单。

STOCK_TB

PRODUCT_ID STOCK_QTY 
   A          20
   B          15
   C          10


ORDER_TB

ORDER_DATE PRODUCT_ID ORDER QTY
2015-03-01    A         5
2015-03-02    A         3
2015-03-02    B         4
2015-03-03    C         1
2015-03-04    C         3

我想为如下所示的月度库存数量报告选择数据。假设报告是在 3 月 5 日生成的

Stock Quantity of March:

                       Daily Stock Qty
Product ID  1   2   3   4   5  6  7 ... 28 29 30 31
    A      23  20  20  20  20  0  0      0  0  0  0
    B      19  15  15  15  15  0  0      0  0  0  0          
    C      14  14  13  10  10  0  0      0  0  0  0

前几日的库存数量以收盘日为准(即以上3月2日指3月2日23:59:99.999)

任何超出当前日期的日期的数量都是 0

我们没有用于保存每日库存的表格,只有当前库存。所以这意味着要获得以前日期的库存,我必须向后添加产品订单的数量。

您如何编写这种类型的查询?对于日期列,我可以将它们固定为 1 到 31,因为我可以根据应用程序中的月份隐藏未使用的日期。但我不太确定如何在 SQL 中编写逻辑,以便在以前的日期将订单数量添加到当前库存。

【问题讨论】:

【参考方案1】:

6天查询示例(其他25天同理:-)

DECLARE @FirstOfMonth AS DATETIME = DATEADD(month, DATEDIFF(month, 0, GETDATE()), 0) 

SELECT 
    CASE WHEN DAY(GETDATE()) < 1 THEN 0 ELSE S.STOCK_QTY + ISNULL((SELECT SUM(O.ORDER_QTY) FROM ORDER_TB O WHERE O.PRODUCT_ID = S.PRODUCT_ID AND O.ORDER_DATE > DATEADD(day, 0, @FirstOfMonth)), 0) END _1,
    CASE WHEN DAY(GETDATE()) < 2 THEN 0 ELSE S.STOCK_QTY + ISNULL((SELECT SUM(O.ORDER_QTY) FROM ORDER_TB O WHERE O.PRODUCT_ID = S.PRODUCT_ID AND O.ORDER_DATE > DATEADD(day, 1, @FirstOfMonth)), 0) END _2,
    CASE WHEN DAY(GETDATE()) < 3 THEN 0 ELSE S.STOCK_QTY + ISNULL((SELECT SUM(O.ORDER_QTY) FROM ORDER_TB O WHERE O.PRODUCT_ID = S.PRODUCT_ID AND O.ORDER_DATE > DATEADD(day, 2, @FirstOfMonth)), 0) END _3,
    CASE WHEN DAY(GETDATE()) < 4 THEN 0 ELSE S.STOCK_QTY + ISNULL((SELECT SUM(O.ORDER_QTY) FROM ORDER_TB O WHERE O.PRODUCT_ID = S.PRODUCT_ID AND O.ORDER_DATE > DATEADD(day, 3, @FirstOfMonth)), 0) END _4,
    CASE WHEN DAY(GETDATE()) < 5 THEN 0 ELSE S.STOCK_QTY + ISNULL((SELECT SUM(O.ORDER_QTY) FROM ORDER_TB O WHERE O.PRODUCT_ID = S.PRODUCT_ID AND O.ORDER_DATE > DATEADD(day, 4, @FirstOfMonth)), 0) END _5,
    CASE WHEN DAY(GETDATE()) < 6 THEN 0 ELSE S.STOCK_QTY + ISNULL((SELECT SUM(O.ORDER_QTY) FROM ORDER_TB O WHERE O.PRODUCT_ID = S.PRODUCT_ID AND O.ORDER_DATE > DATEADD(day, 5, @FirstOfMonth)), 0) END _6

    FROM STOCK_TB S

请注意,我使用的是&gt; DATEADD 而不是&gt;= DATEADD,但我不太确定...您在本月第一天下的订单是什么时候计算的?

第二种解决方案,但我认为复杂性不会有太大变化:

DECLARE @FirstOfMonth AS DATETIME = DATEADD(month, DATEDIFF(month, 0, GETDATE()), 0) 
DECLARE @Today AS DATETIME = DATEADD(dd, DATEDIFF(dd, 0, GETDATE()), 0)

;WITH Days(d, dat) AS
(
    SELECT 1, @FirstOfMonth
    UNION ALL
    SELECT d+1, DATEADD(day, 1, dat) FROM Days WHERE d < DATEPART(day, @today)
)

, Work1 AS (
    SELECT PRODUCT_ID, STOCK_QTY + ISNULL((SELECT SUM(O.ORDER_QTY) FROM ORDER_TB O WHERE O.PRODUCT_ID = S.PRODUCT_ID AND O.ORDER_DATE > dat), 0) STOCK_TB, d FROM STOCK_TB S, Days 
)

SELECT PRODUCT_ID,
    ISNULL(MAX(CASE WHEN d = 1 THEN STOCK_TB END), 0) _1,
    ISNULL(MAX(CASE WHEN d = 2 THEN STOCK_TB END), 0) _2,
    ISNULL(MAX(CASE WHEN d = 3 THEN STOCK_TB END), 0) _3,
    ISNULL(MAX(CASE WHEN d = 4 THEN STOCK_TB END), 0) _4,
    ISNULL(MAX(CASE WHEN d = 5 THEN STOCK_TB END), 0) _5,
    ISNULL(MAX(CASE WHEN d = 6 THEN STOCK_TB END), 0) _6,
    ISNULL(MAX(CASE WHEN d = 7 THEN STOCK_TB END), 0) _7,
    ISNULL(MAX(CASE WHEN d = 8 THEN STOCK_TB END), 0) _8
    FROM Work1 GROUP BY PRODUCT_ID

在这里,我使用一个花哨的递归查询来构建一个包含天数的表 1...(today),然后我构建一个 Work1 中间体,其中包含每天的所有库存数量(所以 x 产品 * y 天行),然后我将它们分组

第三种可能性:双递归查询(一个用于计算数字 1...31,一个用于计算总和),加上最终的 GROUP BY 与上一个示例几乎相同。

DECLARE @FirstOfMonth AS DATETIME = DATEADD(month, DATEDIFF(month, 0, GETDATE()), 0) 
DECLARE @Today AS DATETIME = DATEADD(dd, DATEDIFF(dd, 0, GETDATE()), 0)

;WITH Days(d, dat) AS
(
    SELECT DATEPART(day, @Today), @Today dat
    UNION ALL
    SELECT d-1, DATEADD(day, -1, dat) dat 
        FROM Days 
        WHERE d > 1
)

# Product Days x STOCK_TB with a LEFT JOIN on ORDER_TB.
, Work1 AS (
    SELECT S.PRODUCT_ID, d, dat, S.STOCK_QTY, ISNULL(O.ORDER_QTY, 0) ORDER_QTY
        FROM Days
        CROSS JOIN STOCK_TB S # Full cartesian product, JOIN without conditions
        LEFT JOIN ORDER_TB O ON dat = O.ORDER_DATE AND S.PRODUCT_ID = O.PRODUCT_ID
)

# Second recursive query to do the running total
, Days2(PRODUCT_ID, d, dat, STOCK_QTY) AS 
(
    SELECT PRODUCT_ID, d, dat, STOCK_QTY 
        FROM Work1 
        WHERE d = DATEPART(day, @Today)
    UNION ALL
    SELECT d.PRODUCT_ID, d.d - 1, w.dat, d.STOCK_QTY + w.ORDER_QTY 
        FROM Days2 d 
        INNER JOIN Work1 w ON d.PRODUCT_ID = w.PRODUCT_ID AND d.d /* - 1 */ = w.d 
        WHERE d.d > 1
)

SELECT PRODUCT_ID,
    ISNULL(MAX(CASE WHEN d = 1 THEN STOCK_QTY END), 0) _1,
    ISNULL(MAX(CASE WHEN d = 2 THEN STOCK_QTY END), 0) _2,
    ISNULL(MAX(CASE WHEN d = 3 THEN STOCK_QTY END), 0) _3,
    ISNULL(MAX(CASE WHEN d = 4 THEN STOCK_QTY END), 0) _4,
    ISNULL(MAX(CASE WHEN d = 5 THEN STOCK_QTY END), 0) _5,
    ISNULL(MAX(CASE WHEN d = 6 THEN STOCK_QTY END), 0) _6,
    ISNULL(MAX(CASE WHEN d = 7 THEN STOCK_QTY END), 0) _7,
    ISNULL(MAX(CASE WHEN d = 8 THEN STOCK_QTY END), 0) _8
    FROM Days2 GROUP BY PRODUCT_ID

注意/* - 1 */ 注释部分。取消注释它,您可以控制如何使用第一个月的值。

【讨论】:

我的脑海中明显有这个想法,但希望出现更优雅的解决方案,可能涉及连接或潜在的高级 SQL Server 功能,因为这将涉及 31 个子查询......可能会导致在糟糕的表现.. :( @I46kok 有点花哨,但我认为它不会改变任何东西。 呃,2008+ 支持PIVOT,这应该会让这个看起来好多更好。 @I46kok 最后,您正在运行总计(可能是反向总计,但这并不重要)...来自sqlperformance.com/2012/07/t-sql-queries/running-totals 他们表明只有使用 SQL 2012 我们才能使用 RANGE UNBOUNDED PRECEDING 或 ROWS UNBOUNDED PRECEDING 做一些非常有趣的事情,而不是像我所做的那样使用子查询,最好使用 groupby

以上是关于如何为需要从以前的订单计算的库存数量编写 SQL的主要内容,如果未能解决你的问题,请参考以下文章

MRP物料需求计划

微信统一下单需注意问题

库存系统:基于交易或存储数量,使用触发器更新?

[易飞]关于内部备料订单动态跟踪库存和销货量的统计

SQL的一些查询语句

[易飞]关于内部备料订单动态跟踪库存和销货量的统计