当没有动态列时,将多列和多行的列连接成一个 varchar 值

Posted

技术标签:

【中文标题】当没有动态列时,将多列和多行的列连接成一个 varchar 值【英文标题】:Concatenate columns of multiple columns and multiple rows into one varchar value, when no of columns is dynamic 【发布时间】:2017-05-14 19:38:14 【问题描述】:

所以我有这个动态查询,它返回一个具有动态列数的结果集,如下所示:

在这个结果集中,我们总是有 ID、FacilityName 和 cycleNum 列,但任务列的数量可以变化 Task1、Task2、Task3 ..... 直到 Taskn。

我需要的最终结果集如下:

为此,我尝试了以下查询:

        Select distinct FacilityName, 
          substring(
          (
            Select ',' + a   
            From (SELECT ID, FacilityName, 'Cycle-'+ cast(CycleNum as                        varchar)+'::' + 'Task1~' + cast(Task1 as varchar) + ',Task2~' + cast(Task2 as varchar) + ',Task3~' + cast(Task3 as varchar) + ';' as a FROM #tempTable) ST1
            Where ST1.FacilityName = ST2.FacilityName
            ORDER BY ST1.FacilityName
            For XML PATH ('')
        ), 2, 1000) CycleData
From (SELECT ID, FacilityName, 'Cycle-'+ cast(CycleNum as varchar)+'::' + 'Task1~' + cast(Task1 as varchar) + ',Task2~' + cast(Task2 as varchar) + ',Task3~' + cast(Task3 as varchar)+ ';' as a FROM #tempTable) ST2

这将适用于以下测试数据:

    create table #tempTable
    (
     ID int,
     FacilityName varchar(50),
     CycleNum int,
     Task1 datetime,
     Task2 datetime,
     Task3 datetime
    )

    Insert into #tempTable values 
    (1, 'A', 1, convert(varchar(10), getdate(), 126), convert(varchar(10),      dateadd(day,1,getdate()), 126), convert(varchar(10), dateadd(day,2,getdate()),  126)),
    (2, 'A', 2, convert(varchar(10), getdate(), 126), convert(varchar(10),    dateadd(day,1,getdate()), 126), convert(varchar(10), dateadd(day,2,getdate()), 126)),
    (3, 'B', 1, convert(varchar(10), getdate(), 126), convert(varchar(10),   dateadd(day,1,getdate()), 126), convert(varchar(10), dateadd(day,2,getdate()), 126)),
    (4, 'B', 2, convert(varchar(10), getdate(), 126), convert(varchar(10),   dateadd(day,1,getdate()), 126), convert(varchar(10), dateadd(day,2,getdate()), 126))

但我想不出任何方法来扩展它以使用动态列。所有列的列表都保存在主表中,因此如果需要,我们可以从那里获取以逗号分隔的列列表。

【问题讨论】:

【参考方案1】:

这里是动态方式

与您的静态查询略有不同的方法。要使其动态化,请使用while 循环或CURSOR 生成Task1 + Task2 + ..TaskN。然后在Select 查询中使用它。

DECLARE @columns VARCHAR(50)='Task1,Task2,Task3', -- Pass the list of column names 
        @int     INT = 1,
        @sql     VARCHAR(8000)

SET @sql = '    ;WITH cte
         AS (SELECT *,
                    ''Cycle-'' + Cast(CycleNum AS VARCHAR(10)) + ''::'' '

WHILE @int <= Len(@columns) - Len(Replace(@columns, ',', '')) -- To find the number of Tasks in list
  BEGIN
      SET @sql += + '+''Task' + Cast(@int AS VARCHAR(10))
                  + '~''+' + 'Cast(Task'
                  + Cast(@int AS VARCHAR(10))
                  + ' AS VARCHAR(50)) + '','''
      SET @int += 1
  END

SET @sql += '   AS concat_dates
             FROM   #tempTable)
    SELECT DISTINCT FacilityName,
                    LEFT(CycleData, Len(CycleData) - 1)
    FROM   cte a
           CROSS apply(SELECT b.concat_dates + '',''
                       FROM   cte b
                       WHERE  a.FacilityName = b.FacilityName
                       FOR xml path('''')) cs (CycleData) 

                       '
--print @sql -- uncomment it to debug if you have any error when executing dynamic code
EXEC (@sql) 

不用担心While Loop/CURSOR 的使用,因为我们没有在循环内执行任何资源密集型操作。

静态查询看起来像这样

;WITH cte
     AS (SELECT *,
                'Cycle-' + Cast(CycleNum AS VARCHAR(10))
                + '::' + 'Task1~' + Cast(Task1 AS VARCHAR(50))
                + ',' + 'Task2~' + Cast(Task2 AS VARCHAR(50))
                + ',' AS concat_dates
         FROM   #tempTable)
SELECT DISTINCT FacilityName,
                LEFT(CycleData, Len(CycleData) - 1)
FROM   cte a
       CROSS apply(SELECT b.concat_dates + ','
                   FROM   cte b
                   WHERE  a.FacilityName = b.FacilityName
                   FOR xml path('')) cs (CycleData) 

【讨论】:

【参考方案2】:

使用系统表。我更喜欢简短有效的解决方案:)。这是指南,我相信你可以用集中字符串自己完成它(如果你不确定,添加评论,我会添加整个代码或更多描述)。

declare @columns varchar(max)
SELECT @columns = isnull(@columns + '+',  '') + '''' + COLUMN_NAME + '~'' + Cast(' + COLUMN_NAME + ' as varchar(50))'
FROM tempdb.INFORMATION_SCHEMA.COLUMNS c
WHERE c.TABLE_NAME like '#tempTable%'
    AND c.COLUMN_NAME LIKE 'Task%' -- filter columns you are interested in

declare @sql varchar(4000) = 'select *, ' + @columns + ' from #tempTable'
print @sql
exec (@sql)

【讨论】:

以上是关于当没有动态列时,将多列和多行的列连接成一个 varchar 值的主要内容,如果未能解决你的问题,请参考以下文章

在 SQL Server 中将多行动态组合成多列

动态连接R中的列

怎么将excel中两列转换成多行多列

Match()和data.table的列的动态选择

多行成一行多列

如何将Oracle中同一列的多行记录拼接成一个字符串