SQL Query以了解项目销售的后续天数

时间:2014-01-19 10:53:02

标签: sql sql-server

我有一张桌子,上面列出了所有商品。这些记录适用于所发行文件的所有行。有些商品有日常销售而非其他商品。还有一些没有日常销售的人,有一定时期跟随销售。

我需要一个查询,根据每个项目向我显示销售的最长时间(以天数为单位)。这可能吗?

在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  

2 个答案:

答案 0 :(得分:0)

这是一个天真的实现。它的性能非常糟糕(二次时间复杂度),但它可以用来测试更好的实现。

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。或者,将逻辑移动到应用程序服务器。

答案 1 :(得分:0)

这是一种基于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替换为{{1}}。

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