取得日期范围内的最新值

时间:2019-10-10 17:14:53

标签: sql sql-server performance

我有一个表,该表包含以下列:DeskID *,ProductID *,Date *,Amount(其中带有*标记的列为主键)。所使用的产品会随时间变化,如下图所示。

左侧为表格格式,右侧为一张桌子(希望)直观地表示了数据

Table format on the left, (hopefully) intuitive representation on the right for one desk

目标是在一个日期范围内按办公桌和日期获取最新产品的总和,包括不再使用的产品。

例如使用所需表格上方的数据是:

Desired table

所以在1月1日,总和为产品A的​​1

1月2日,总和是A的2和B的5,所以7

1月4日,总和是A的1(已停用,因此请从3号取值),B的5和C的2,因此总计8

我尝试使用桌面上的分区和按日期排序的产品来获取最新值,并使用@date Date参数将以下代码转换为函数(下面的Function1)

select @date 'Date', t.DeskID, SUM(t.Amount) 'Sum' from (
    select @date 'Date', t.DeskID, t.ProductID, t.Amount
        , row_number() over (partition by t.DeskID, t.ProductID order by t.Date desc) as roworder
    from Table1 t
    where 1 = 1
    and t.Date <= @date
) t
where t.roworder = 1
group by t.DeskID

然后使用实用程序日历表并交叉应用以在如下所示的时间范围内获取所需的值

select * from Calendar c
cross apply Function1(c.CalendarDate)
where c.CalendarDate >= '20190101' and c.CalendarDate <= '20191009'

这具有预期的结果,但是太慢了。目前,每个办公桌使用大约50种产品,并且每个月都会滚动产品,因此,在仅5年的时间里,每个办公桌都有3000多种产品的历史,这使整个事情停滞了。 (一个月的时间范围大约为30秒)

有更好的方法吗?

2 个答案:

答案 0 :(得分:0)

将功能更改为以下速度应该更快:

select @date 'Date', t.DeskID, SUM(t.Amount) 'Sum'
FROM (SELECT m.DeskID, m.ProductID, MAX(m.[Date) AS MaxDate
  FROM Table1 m
  where m.[Date] <= @date) d
INNER JOIN Table1 t
ON d.DeskID=t.DeskID
AND d.ProductID=t.ProductID
and t.[Date] = d.MaxDate
group by t.DeskID

答案 1 :(得分:0)

TVF的性能通常会下降。以下内容将完全删除TVF:

-- DROP TABLE Table1;
CREATE TABLE Table1 (DeskID int not null, ProductID nvarchar(32) not null, [Date] Date not null, Amount int not null, PRIMARY KEY ([Date],DeskID,ProductID));

INSERT Table1(DeskID,ProductID,[Date],Amount)
VALUES (1,'A','2019-01-01',1),(1,'A','2019-01-02',2),(1,'B','2019-01-02',5),(1,'A','2019-01-03',1)
,(1,'B','2019-01-03',4),(1,'C','2019-01-03',3),(1,'B','2019-01-04',5),(1,'C','2019-01-04',2),(1,'C','2019-01-05',2)
GO
DECLARE @StartDate date=N'2019-01-01';
DECLARE @EndDate date=N'2019-01-05';
;WITH cte_p
AS
(
    SELECT DISTINCT DeskID,ProductID
    FROM Table1
    WHERE [Date] <= @EndDate
),
cte_a
AS
(
  SELECT @StartDate AS [Date], p.DeskID, p.ProductID, ISNULL(a.Amount,0) AS Amount
  FROM (
    SELECT t.DeskID, t.ProductID
        , MAX(t.Date) AS FirstDate
    FROM Table1 t
    WHERE t.Date <= @StartDate
    GROUP BY t.DeskID, t.ProductID) f
  INNER JOIN  Table1 a
  ON f.DeskID=a.DeskID
  AND f.ProductID=a.ProductID
  AND f.[FirstDate]=a.[Date]
  RIGHT JOIN cte_p p
  ON p.DeskID=a.DeskID
  AND p.ProductID=a.ProductID
  UNION ALL
  SELECT  DATEADD(DAY,1,a.[Date]) AS [Date], t.DeskID, t.ProductID, t.Amount
  FROM Table1 t
  INNER JOIN cte_a a
  ON t.DeskID=a.DeskID
  AND t.ProductID=a.ProductID
  AND t.[Date] > a.[Date]
  AND t.[Date] <= DATEADD(DAY,1,a.[Date])
  WHERE a.[Date]<@EndDate
  UNION ALL
  SELECT DATEADD(DAY,1,a.[Date]) AS [Date], a.DeskID, a.ProductID, a.Amount
  FROM cte_a a
  WHERE NOT EXISTS(SELECT 1 FROM Table1 t
      WHERE t.DeskID=a.DeskID
      AND t.ProductID=a.ProductID
      AND t.[Date] > a.[Date]
      AND t.[Date] <= DATEADD(DAY,1,a.[Date]))
  AND a.[Date]<@EndDate
 )
 SELECT [Date], DeskID, SUM(Amount)
 FROM cte_a
 GROUP BY [Date], DeskID;