SQL 查询以了解商品销售的后续天数

Posted

技术标签:

【中文标题】SQL 查询以了解商品销售的后续天数【英文标题】:SQL Query to know followed days of item sales 【发布时间】:2014-01-19 10:53:01 【问题描述】:

我有一张桌子,上面放着所有售出的物品。这些记录适用于已签发文件的所有行。有些商品每天都有销售,而其他商品则没有。还有一些没有每日销售的人,有一定的销售期。

我需要一个查询来向我显示每件商品的最大销售期(以天数计)。这可能吗?

在 Excel 中可以这样做,使用按项目/天/数量分组的表并应用以下公式:“如果天数 = 0,则返回 0,否则,如果前一天的金额 = 0,则返回 1,否则返回前一天的金额 + 1"。最后它只是检查该列的最大值。

在 SQL 中执行此操作有什么帮助吗?谢谢!

这是一个小样本(1 月 1 日至 10 日期间):

原表:

SalesDate   Doc ItemID  Qty  
  01-jan    156 123456  10  
  01-jan    156 654321  5  
  01-jan    157 123456  3  
  02-jan    158 654321  4  
  02-jan    158 123456  7  
  03-jan    159 123456  8  
  04-jan    160 654321  3  
  04-jan    161 654321  8  
  05-jan    162 654321  3  
  06-jan    163 123456  7  
  06-jan    163 654321  2  
  06-jan    164 123456  9  
  07-jan    165 654321  4  
  08-jan    166 123456  5  
  09-jan    167 123456  6  
  10-jan    168 123456  3  
  10-jan    168 654321  5  
  10-jan    169 654321  1  

中间表:

CalendarDate ItemID SumQty FollowedSalesDays  
    01-jan   123456 13     1  
    02-jan   123456 7      2  
    03-jan   123456 8      3  
    04-jan   123456 0      0  
    05-jan   123456 0      0  
    06-jan   123456 16     1  
    07-jan   123456 0      0  
    08-jan   123456 5      1  
    09-jan   123456 6      2  
    10-jan   123456 3      3  
    01-jan   654321 5      1  
    02-jan   654321 4      2  
    03-jan   654321 0      0  
    04-jan   654321 11     1  
    05-jan   654321 3      2  
    06-jan   654321 2      3  
    07-jan   654321 4      4  
    08-jan   654321 0      0  
    09-jan   654321 0      0  
    10-jan   654321 6      1  

最终结果:

ItemID  MaxFollowedSalesDays  
123456  3  
654321  4  

【问题讨论】:

您使用的是哪个 DBMS?后格雷斯?甲骨文? 对不起:MS SQL Server。 你能展示一些示例数据和所需的输出吗?? @M.Ali:问题已编辑 - 添加了小样本 【参考方案1】:

这是一种基于cursor 的方法。它应该以线性时间运行,比简单的实现要快得多。

DECLARE @ItemID varchar(10) = NULL
DECLARE @SalesDate date
DECLARE @NextItemID varchar(10)
DECLARE @NextSalesDate date
DECLARE @StartDate date
DECLARE @Contiguity int
DECLARE @MaxContiguity int = 1

DECLARE @MaxSalesDateContiguityPerItem TABLE (
    ItemID varchar(10) NOT NULL,
    MaxSalesDateContiguity int NOT NULL
)
DECLARE SalesCursor CURSOR FOR
    SELECT DISTINCT ItemID, SalesDate FROM ItemsSold ORDER BY ItemID, SalesDate

OPEN SalesCursor

DECLARE @more int = 3
WHILE @more > 0
BEGIN
    FETCH NEXT FROM SalesCursor INTO @NextItemID, @NextSalesDate
    SET @more = CASE
        WHEN @@FETCH_STATUS <> 0 THEN 0      -- reached end of result set
        WHEN @ItemID IS NULL OR @ItemID <> @NextItemID THEN 1      -- next item
        WHEN DATEDIFF(day, @SalesDate, @NextSalesDate) > 1 THEN 2  -- date hole
        ELSE 3
    END

    -- Calculate the length of the contiguity we just passed.
    -- Compare with earlier contiguities; keep whatever is longest.
    IF @more <= 2 AND @ItemID IS NOT NULL
    BEGIN
        SET @Contiguity = DATEDIFF(day, @StartDate, @SalesDate) + 1
        IF @Contiguity > @MaxContiguity SET @MaxContiguity = @Contiguity
        SET @StartDate = @NextSalesDate    -- begin another contiguity
    END

    -- Flush the item we just passed to the temporary table.
    IF @more <= 1 AND @ItemID IS NOT NULL
    BEGIN
        INSERT INTO @MaxSalesDateContiguityPerItem VALUES (@ItemID, @MaxContiguity)
        SET @MaxContiguity = 1             -- start over with another item
    END

    SET @ItemID = @NextItemID
    SET @SalesDate = @NextSalesDate
END

CLOSE SalesCursor
DEALLOCATE SalesCursor

SELECT * FROM @MaxSalesDateContiguityPerItem ORDER BY ItemID

注意:请将所有出现的varchar(10) 替换为您的ItemID 列的任何类型。

编辑:进一步的性能考虑... 如果您正在处理甚至使这种线性时间解决方案变得太慢的记录集,那么请注意还有其他选择。在您的问题中,您已经提出了一个“中间表”;您可以将其设为永久表,通过预定作业定期更新,或通过销售交易表上的trigger 立即更新。后者保证了实时信息,但它会使交易变慢。

【讨论】:

感谢@Ruud 的努力。这是一项伟大的工作!我尝试了一些样品,结果总是完美的。然而应用于生产数据库有一些奇怪的东西 - 游标的查询返回超过 42000 条记录,(有超过 42000 种不同的销售项目),但最终结果只出现 18612 ......知道它可能是什么吗? 对不起@Ruud - 我忘记了你的笔记。当我将 varchar(10) 更改为 nvarchar(25)(我的 ItemID)时,每条记录都会产生结果!再次感谢! @PJLG:很高兴它成功了!我对建议进行了编辑,以防这种线性时间解决方案变得太慢。 感谢您的关心@Ruud。毕竟我对这个查询的性能感到惊讶——在几秒钟内返回完整的结果。无论如何,您创建表格并使用 Job 更新表格的建议很有趣。另一方面,对我来说,使用触发器是最后的解决方案,因为我不想影响事务性能。再次感谢您!【参考方案2】:

这是一个幼稚的实现。它的性能很糟糕(二次时间复杂度),但它可能在测试更好的实现时派上用场。

SELECT ItemID, MAX(Filled)
FROM (
    SELECT i1.ItemID,
           DATEDIFF(day, i1.SalesDate, i2.SalesDate) AS Distance,
           (
               SELECT COUNT(DISTINCT i3.SalesDate)
               FROM ItemsSold i3
               WHERE i3.ItemID = i1.ItemID
               AND i3.SalesDate BETWEEN i1.SalesDate AND i2.SalesDate
           ) AS Filled
    FROM ItemsSold i1
    INNER JOIN ItemsSold i2 ON i2.ItemID = i1.ItemID AND i2.SalesDate >= i1.SalesDate
) AS CartesianProduct
WHERE Distance + 1 = Filled
GROUP BY ItemID

要实现线性时间复杂度,您可以使用cursor。或者,将逻辑移至您的应用程序服务器。

【讨论】:

谢谢@Ruud。它真的很慢(它必须分析数百万行中的 50,000 多个项目),但这不是最大的问题。问题是查询不返回真实结果。我试了一个有 180 天销售天数的项目,这个查询只返回 21。 连续销售 180 天?这让我很怀疑;你从来没有提到我们应该“跳过”公共假期。无论如何,我愿意接受挑战;你能告诉我一个我的查询不足的示例记录集(如果可能的话,是一个小记录集)?

以上是关于SQL 查询以了解商品销售的后续天数的主要内容,如果未能解决你的问题,请参考以下文章

SQL查询问题

用select语句将两个表合成为一个表

设计题 有一个MySQL数据库store,在store数据库中含有一个销售表sale,用于存放商品的销售记录。

08_SQL Server之简单查询

如何使用 sql 中的单个查询获取项目计数和总计数?

SQL - 我需要显示 2012 年销售的商品名称、商品价格和数量