动态列取决于先前的动态列-TSQL

时间:2020-10-03 11:16:07

标签: sql-server tsql

我尝试根据之前的列(月)创建18(!)个月的动态预测,但我陷入了困境:

我有三列:

  1. 库存
  2. SafetyStock
  3. 需要生产-带有子句WHERE date = getdate()的另一个选择

我需要实现的目标: 索引库存-当月,安全库存-当月,生产需要(从Nfp中选择*,日期为= getdate()),库存-本月+1,安全库存-本月+1,生产需要-本月+1。 ..等到18个月

计算: 库存-当月+ 1 =上个月的库存+上个月的SafetyStock-当月的生产需求

是否有可能创建类似的东西?它必须是动态的并获得当前日期和未来18个月的计算结果。所以现在我必须计算从2020-10到2022-04

我尝试过的事情:

  1. 我准备了18 cte并加入了所有内容。然后我进行计算-它可以工作,但是速度很慢,我认为它不是专业的。

  2. 我尝试做动态sql,在下面您可以看到我的代码,但是当我想做计算列取决于先前的计算列时,我却卡住了:

-------------------代码-------------------------

if object_id('tempdb..#tmp') is not null

drop table #tmp

 

if object_id('tempdb..#tmp2') is not null

drop table #tmp2

 

declare @cols as int

declare @iteration as int

declare @Mth as nvarchar(30)

declare @data as date

declare @sql as nvarchar(max)

declare @sql2 as nvarchar(max)

 

set @cols = 18

set @iteration = 0

set @Mth = month(getdate())

 

set @data = cast(getdate() as date)

 

 

 

select

10 as SS,

12 as Stock

into #tmp

 

WHILE @iteration < @cols

 

begin

 

set @iteration = @iteration + 1

 
set @sql =

'

alter table #tmp

add [StockUwzgledniajacSS - ' + cast(concat(year(DATEADD(Month, @Iteration, @data)),'-', month(DATEADD(Month, @Iteration, @data))) as nvarchar(max)) +'] as (Stock - SS)

'

exec (@sql)

 

set @Mth= @Mth+ 1

 

set @sql2 =

'

alter table #tmp

add [StockUwzgledniajacSS - ' + @Mth +'] as ([StockUwzgledniajacSS - ' + @Mth +'])

'

end


select * from #tmp

提前谢谢!

1 个答案:

答案 0 :(得分:0)


更新1条注释:在您发布数据之前,我已经写了这篇。我相信这仍然成立,但是,当然,存货水平会有所不同。鉴于您的NFP数据是按天计算的,而您的报告是按月计算的,我建议添加一些东西以将该数据预处理为几个月,例如,按月分组的NPS值之和。


更新2(第二天)注意:从下面的操作注释中,我试图将其与编写的内容集成在一起,并更直接地回答问题,例如,创建报告表#tmp。 鉴于OP还提到了数百万行,因此我想每行代表一个特定的部件/项目-我将其包含在一个称为StockNum的字段中。


我做了一些可能无法正确计算的事情,但是演示了这种方法,应该使您摆脱目前的障碍。的确,如果您以前从未使用过这些代码,那么使用自己的计算方法更新此代码将有助于您了解其工作方式,从而可以对其进行维护。

我假设此处要计算的关键问题是,本月的库存基于上个月的库存,然后是本月的新库存减去旧库存。

可以在18个单独的语句中进行计算(更新表集col2 = col1的某些功能,然后更新表集col3 = col2的某些功能,等等)。但是,多次更新同一张表通常是一种反模式,导致性能下降-尤其是在您需要一次又一次地读取基本数据的情况下。

相反,通常最好使用Recusive CTE(here's an example description)来计算出类似的结果,在此基础上,它会基于先前的结果“构建”一组数据。

此方法的主要区别在于

  • 创建报告表(不包含任何数据/计算结果)
  • 将数据作为一个单独的步骤进行计算-但具有可用于链接到报表的列/字段
  • 将计算中的数据作为单个插入语句插入到报表中。

我广泛使用了临时表/ etc,以帮助演示该过程。

您没有解释什么是安全库存,也没有说明如何测量安全库存,因此在下面的示例中,我假设安全库存是生产的数量,每月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,其中显示了该子程序的运行以及每个子步骤的结果。