SQL Server窗口函数:构建历史字符串

时间:2017-12-19 19:03:31

标签: sql sql-server window-functions

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

enter image description here

其中id是分区变量,period是时间维度,val是观察值。

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

enter image description here

我正在尝试使用SQL窗口函数而不是游标,但我一直遇到的问题是hist列定义的自引用性质。几乎看起来我必须每个周期创建一行/列。例如,我最接近的是:

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

enter image description here

我必须假设迭代并执行hist3等等,以便最终起作用。

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

示例数据

以下是生成原始表的一些代码:

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

2 个答案:

答案 0 :(得分:2)

其实你不需要递归。您可以非常轻松地利用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

答案 1 :(得分:1)

正如评论中所讨论的那样尝试使用递归查询

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]