【问题标题】:Dynamic columns depend on previous dynamic columns - TSQL动态列依赖于以前的动态列 - SQL
【发布时间】:2020-10-04 08:43:05
【问题描述】:

我尝试根据之前的列(月)创建 18(!)个月的动态预测,但我被卡住了:

我有三列:

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

我需要达到的目标: 索引库存-当月、SafetyStock-当月、生产需求(从 Nfp where date 中选择 * = getdate()),库存 - 本月 + 1,Safetystock - 本月 + 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

提前致谢!

【问题讨论】:

  • 请提供样本数据和期望的结果。我发现很难弄清楚你真正想要达到的目标。
  • 听起来你想做一个动态数据透视——但是你从创建一个非规范化表开始,这只会使你的逻辑更难实现。我建议您忘记您想要的演示文稿,并专注于检索/计算您需要的信息。一旦你有了这个逻辑,你就可以处理该信息的呈现。通常在表示层中会这样做 - 大多数报告工具可以“更好”地做到这一点。
  • @GordonLinoff 不幸的是我没有样本数据——我在工作计算机上工作。但我准备了 excel 文件来更具体地向您展示我想要实现的目标:sample
  • @GordonLinoff 如果你不想下载我的文件,这里有一张图片:sample_img我希望它有效
  • @SMor,最终结果将在 PowerBI 中,但我认为在 18 个月内实现该计算更难 - DAX 在有大量数据和计算的情况下效率不高跨度>

标签: sql-server tsql


【解决方案1】:

更新 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,显示它运行每个子步骤的结果。

【讨论】:

  • 嗨,谢谢你的回答 - 它可以帮助我,但我的数据有大约 200 万条记录(200 万条索引),所以如果我创建联合 200 万 x 18 个月,我会得到 3600 万行。这将是非常巨大的,我认为慢表。所以这就是为什么我的方法是创建动态列,而不是行;)抱歉,我之前没有告诉你——我不擅长展示
  • 不管是 1 列(x 3600 万行)还是 18 列(x 200 万行),数据量不一样吗?因此,您可以将上述方法用作计算相关数据的一种相当有效的方法 - 仅检索 MonthNum 和 StockAtEnd (例如)而不是所有列,然后您可以旋转它或类似的方法来获取您的输出。这与问题本身的 cmets 中 @Smor 的方法相匹配:首先获取正确的数据,然后 1 列与 18 列是显示该数据的问题。
  • 好吧,也许你是对的。我会在星期一试试这个。谢谢你的解决方案:)
  • 它看起来很棒。对不起,我缺席了几天。我一定会试试这个我让你知道。谢谢!
猜你喜欢
  • 2020-05-14
  • 1970-01-01
  • 2017-11-22
  • 1970-01-01
  • 2012-04-23
  • 2020-11-23
  • 1970-01-01
  • 1970-01-01
  • 2015-12-16
相关资源
最近更新 更多