更新 1 注意:我在您发布数据之前写了这篇文章。我相信这仍然成立,但当然,库存水平是不同的。鉴于您的 NFP 数据是按天计算的,而您的报告是按月计算的,我建议添加一些东西来将该数据预处理为月份,例如 NPS 值的总和,按月份分组。
更新 2(次日)注意:从下面的 OPs cmets 中,我尝试将其与所写内容相结合,并更直接地回答问题,例如创建报告表 #tmp。
鉴于 OP 还提到了数百万行,我想每一行代表一个特定的部分/项目 - 我已将其包含为一个名为 StockNum 的字段。
我做了一些可能无法正确进行计算的事情,但演示了该方法,应该可以帮助您克服当前的障碍。事实上,如果您以前没有使用过这些,那么使用您自己的计算更新此代码将帮助您了解它是如何工作的,以便您维护它。
我假设这里计算的关键问题是本月的库存是基于上个月的库存,然后是新库存减去本月的旧库存。
可以在 18 个单独的语句中进行计算(更新表集 col2 = col1 的某个函数,然后更新表集 col3 = col2 的某个函数,等等)。但是,多次更新同一个表通常是一种反模式,会导致性能下降——尤其是当您需要一次又一次地读取基础数据时。
相反,通常最好使用 Recusive CTE (here's an example description) 来计算此类数据,它会根据之前的结果“构建”一组数据。
这种方法的主要区别在于它
- 创建报告表(不输入任何数据/计算)
- 将数据计算为单独的步骤 - 但包含可用于链接到报表的列/字段
- 将计算中的数据作为单个插入语句插入到报表中。
我大量使用临时表/等来帮助演示该过程。
您还没有解释什么是安全库存,也没有解释如何衡量即将到来的产品,因此对于下面的示例,我假设安全库存是生产量,并且是每月 5 个。然后我假设 NFP 是每个月流出的金额(例如,对销售额的前瞻性估计)。关键结果将是月底的库存(例如,您可以查看它是过高还是过低)。
由于您希望将其存储在以每个月为列的表中,因此第一步是创建一个包含相关存储桶(月)的列表。这些包括在以后的计算/等中用于匹配的字段。注意我已经包含了一些日期字段(开始日期和结束日期),这在您自定义代码时可能很有用。这部分 SQL 设计得尽可能简单。
然后我们创建包含库存变动参考数据的临时表,替换您的 SELECT * FROM NFP WHERE date = getdate()
/* SET UP BUCKET LIST TO HELP CALCULATION */
CREATE TABLE #RepBuckets (BucketNum int, BucketName nvarchar(30), BucketStartDate datetime, BucketEndDate datetime)
INSERT INTO #RepBuckets (BucketNum) VALUES
(0),(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),
(11),(12),(13),(14),(15),(16),(17),(18)
DECLARE @CurrentBucketStart date
SET @CurrentBucketStart = DATEFROMPARTS(YEAR(getdate()), MONTH(getdate()), 1)
UPDATE #RepBuckets
SET BucketName = 'StockAtEnd_' + FORMAT(DATEADD(month, BucketNum, @CurrentBucketStart), 'MMM_yy'),
BucketStartDate = DATEADD(month, BucketNum, @CurrentBucketStart),
BucketEndDate = DATEADD(month, BucketNum + 1, @CurrentBucketStart)
/* CREATE BASE DATA */
-- Current stock
CREATE TABLE #Stock (StockNum int, MonthNum int, StockAtStart int, SafetyStock int, NFP int, StockAtEnd int, PRIMARY KEY(StockNum, MonthNum))
INSERT INTO #Stock (StockNum, MonthNum, StockAtStart, SafetyStock, NFP, StockAtEnd) VALUES
(12422, 0, NULL, NULL, NULL, 10)
-- Simulates SELECT * FROM NFP WHERE date = getdate()
CREATE TABLE #NFP_by_month (StockNum int, MonthNum int, StockNFP int, PRIMARY KEY(StockNum, MonthNum))
INSERT INTO #NFP_by_month (StockNum, MonthNum, StockNFP) VALUES
(12422, 1, 4), (12422, 7, 4), (12422, 13, 4),
(12422, 2, 5), (12422, 8, 5), (12422, 14, 5),
(12422, 3, 2), (12422, 9, 2), (12422, 15, 2),
(12422, 4, 7), (12422, 10, 7), (12422, 16, 7),
(12422, 5, 9), (12422, 11, 9), (12422, 17, 9),
(12422, 6, 3), (12422, 12, 3), (12422, 18, 3)
然后我们使用递归 CTE 来计算我们的数据。它将这些存储在表#StockProjections 中。
这是做什么的
- 从您当前的库存开始(#Stock 表中的最后一行)。请注意,唯一重要的是月末的库存。
- 使用上月底的库存水平,作为新月初的库存水平
- 添加安全库存,减去 NFP,最后计算您的库存。
请注意,在 CTE 的递归部分中,“SBM”(StockByMonth)指的是上个月的数据)。然后将其与任何外部数据(例如#NFP)一起用于计算新数据。
这些计算创建了一个表格
- StockNum(相关库存商品的 ID 号 - 在本示例中,我使用了一个库存商品 12422)
- MonthNum(为了清楚/简单起见,我使用了整数而不是日期)
- BucketName(代表月份的 nvarchar,用于列名)
- 月初库存
- 安全库存(我假设是进货,每月 5 个)
- NFP(我认为这是库存,按月变化,来自此处的临时表 - 您需要根据您的选择进行调整)
- 月末库存
/* CALCULATE PROJECTIONS */
CREATE TABLE #StockProjections (StockNum int, BucketName nvarchar(30), MonthNum int, StockAtStart int, SafetyStock int, NFP int, StockAtEnd int, PRIMARY KEY (StockNum, BucketName))
; WITH StockByMonth AS
(-- Anchor
SELECT TOP 1 StockNum, MonthNum, StockAtStart, SafetyStock, NFP, StockAtEnd
FROM #Stock S
ORDER BY MonthNum DESC
-- Recursion
UNION ALL
SELECT NFP.StockNum,
SBM.MonthNum + 1 AS MonthNum,
SBM.StockAtEnd AS NewStockAtStart,
5 AS Safety_Stock,
NFP.StockNFP,
SBM.StockAtEnd + 5 - NFP.StockNFP AS NewStockAtEnd
FROM StockByMonth SBM
INNER JOIN #NFP_by_month NFP ON NFP.MonthNum = SBM.MonthNum + 1
WHERE NFP.MonthNum <= 18
)
INSERT INTO #StockProjections (StockNum, BucketName, MonthNum, StockAtStart, SafetyStock, NFP, StockAtEnd)
SELECT StockNum, BucketName, MonthNum, StockAtStart, SafetyStock, NFP, StockAtEnd
FROM StockByMonth
INNER JOIN #RepBuckets ON StockByMonth.MonthNum = #RepBuckets.BucketNum
现在我们有了数据,我们为报告目的设置了一个表格。请注意,此表将月份名称嵌入到列名中(例如,StockAtEnd_Jun_21)。使用通用名称(例如,StockAtEnd_Month4)会更容易,但我在这里使用稍微复杂一点的情况进行演示。
/* SET UP TABLE FOR REPORTING */
DECLARE @cols int = 18
DECLARE @iteration int = 0
DECLARE @colname nvarchar(30)
DECLARE @sql2 as nvarchar(max)
CREATE TABLE #tmp (StockNum int PRIMARY KEY)
WHILE @iteration <= @cols
BEGIN
SET @colname = (SELECT TOP 1 BucketName FROM #RepBuckets WHERE BucketNum = @iteration)
SET @sql2 = 'ALTER TABLE #tmp ADD ' + QUOTENAME(@colname) + ' int'
EXEC (@sql2)
SET @iteration = @iteration + 1
END
最后一步是将数据添加到报表中。我在这里使用了一个支点,但你可以随意使用任何你喜欢的东西。
/* POPULATE TABLE */
DECLARE @columnList nvarchar(max) = N'';
SELECT @columnList += QUOTENAME(BucketName) + N' ' FROM #RepBuckets
SET @columnList = REPLACE(RTRIM(@columnList), ' ', ', ')
DECLARE @sql3 nvarchar(max)
SET @sql3 = N'
;WITH StockPivotCTE AS
(SELECT *
FROM (SELECT StockNum, BucketName, StockAtEnd
FROM #StockProjections
) StockSummary
PIVOT
(SUM(StockAtEnd)
FOR [BucketName]
IN (' + @columnList + N')
) AS StockPivot
)
INSERT INTO #tmp (StockNum, ' + @columnList + N')
SELECT StockNum, ' + @columnList + N'
FROM StockPivotCTE'
EXEC (@sql3)
这是一个DB<>fiddle,显示它运行每个子步骤的结果。