SQL 服务器 window 功能:构建历史字符串

SQL Server window functions: building up a history string

我有一个 table 的纵向数据,如下所示:

其中id为分区变量,period为时间维度,val为观测值

我想为 id 的每个面板建立 val 的历史记录,如下所示:

我正在尝试使用 SQL window 函数而不是游标来执行此操作,但我保留 运行 的问题是 hist 列定义。似乎我必须每个周期创建一个 row/column。例如,我能想到的最接近的是:

IF OBJECT_ID('dbo.my_try', 'U') IS NOT NULL 
    DROP TABLE dbo.my_try; 
GO
SELECT
    id, period, val, 
    CASE
        WHEN (
            period = MIN(period) 
                OVER (PARTITION by id order by period ROWS 
                BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)
        ) THEN CAST (val AS VARCHAR(60))
        ELSE NULL           
    END AS hist  
INTO my_try
FROM my_test

SELECT
    id, period, val, 
    CASE
        WHEN (
            period = MIN(period) OVER 
            (PARTITION by id order by period ROWS 
            BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)
        ) THEN hist 
        ELSE (
            CONCAT(
                val, ' | ', LAG(hist, 1) OVER (PARTITION by id order by period)
            )
        )

    END AS hist2
FROM my_try

我必须假脱机进行迭代并执行 hist3 等,这样它才能最终工作。

是否可以使用 SQL window 函数来完成此操作,或者游标是否是唯一的路径?


示例数据

这里是一些生成原始代码的代码 table:

CREATE TABLE my_test (
    id INT,
    period INT,
    val INT 
)
BEGIN
    DECLARE @id INT = 1;
    DECLARE @period INT = 1;

    WHILE @id <= 3
    BEGIN
        SET @period = 1 
        WHILE @period <= 3
        BEGIN
            INSERT INTO my_test VALUES (@id, @period, @period * POWER(10, @id))
            SET @period = @period + 1
        END

        SET @id = @id + 1
    END
END

其实这里不需要递归。你可以很容易地利用 STUFF。当然,如果你在 2017 年,你可以按照上面的建议使用 string_agg。但如果你像我一样,而你的公司不是最快采用最新最好的,你可以使用这个。

select t1.id
    , t1.period
    , t1.val
    , STUFF((select ' | ' + convert(varchar(10), val)
            from my_test t2
            where t2.id = t1.id
                and t2.period <= t1.period
            order by t1.period
            FOR XML PATH('')), 1, 3,'')
from my_test t1
order by t1.id
    , t1.period

如评论中所述,尝试使用递归查询

with cte as(
select id, [period], val, convert(varchar(max), val) as agg from my_try where [period] = 1
union all
select t.id, t.[period], t.val, CONCAT(c.agg, ' | ', t.val) from my_try t join cte c on c.[period] +1 = t.[period] and c.id = t.id
)
select * from cte order by id, [period]