将行转换为包含列名的串联字符串

rsj*_*ffe 6 sql-server pivot

我有一张几百列宽的桌子。有没有办法将每一行转换为包含列标题的单个串联字符串,而不必列出查询中的每一列?

我这样做是因为列代表事件报告中的字段。我将它们重新组合在一起,以便人们可以以合乎逻辑的方式阅读报告。

我已经通过查询完成了其中的一些工作,但是为每一列都做起来很费力,而且似乎容易出错。

这是一个简短的片段,显示以我需要的格式连接的三列,在逐列方法中完成:

SELECT 
 Concat( 
   IIf(Id IS NULL, Null, Concat('Id: ' , [Id] , '\n') )  , 
   IIf(StandardClientId IS NULL, Null, 
     Concat('StandardClientId: ' , [StandardClientId] , '\n') )  , 
   IIf(ClientName IS NULL, Null, 
     Concat('ClientName: ' , [ClientName] , '\n') ) 
 )  AS ReportLine
FROM dbo.DataDecoded; 
Run Code Online (Sandbox Code Playgroud)

谢谢

Han*_*non 6

以下是执行此操作的一种混蛋方法,但很有用,因为它展示了如何使用 SQL Server Management Studio 中包含的非常强大的“正则表达式”搜索和替换功能。一旦你掌握了它的工作原理,它就可以成为你几乎每天都在使用的东西。

将列列表从对象资源管理器窗口“拖放”到查询窗口,如下所示:

在此处输入图片说明

我以dbo.spt_fallback_usg表格为例。将它们拖放到查询窗口后,列是:

xserver_name, xdttm_ins, xdttm_last_ins_upd, xfallback_vstart, dbid, segmap, lstart, sizepg, vstart
Run Code Online (Sandbox Code Playgroud)

按 [CTRL + H] 打开“搜索和替换”对话框:

在此处输入图片说明

在上面的示例中,我输入, *了“查找内容”文本框和\n“替换为”文本框。按“全部替换”按钮会将每个列名称放在一个新行上。接下来,像这样修改“搜索和替换”项:

在此处输入图片说明

这会将每个单独的列名变成一个CASE WHEN...语句。

现在用 替换“换行符” +,如下所示:

在此处输入图片说明

这会在查询窗口中生成以下代码:

CASE WHEN xserver_name IS NULL THEN '' ELSE 'xserver_name: ' + [xserver_name] END + 
CASE WHEN xdttm_ins IS NULL THEN '' ELSE 'xdttm_ins: ' + [xdttm_ins] END + 
CASE WHEN xdttm_last_ins_upd IS NULL THEN '' ELSE 'xdttm_last_ins_upd: ' + [xdttm_last_ins_upd] END + 
CASE WHEN xfallback_vstart IS NULL THEN '' ELSE 'xfallback_vstart: ' + [xfallback_vstart] END + 
CASE WHEN dbid IS NULL THEN '' ELSE 'dbid: ' + [dbid] END + 
CASE WHEN segmap IS NULL THEN '' ELSE 'segmap: ' + [segmap] END + 
CASE WHEN lstart IS NULL THEN '' ELSE 'lstart: ' + [lstart] END + 
CASE WHEN sizepg IS NULL THEN '' ELSE 'sizepg: ' + [sizepg] END + 
CASE WHEN vstart IS NULL THEN '' ELSE 'vstart: ' + [vstart] END + 
Run Code Online (Sandbox Code Playgroud)

然后可以手动将其包装到如下SELECT语句中:

SELECT 
    CASE WHEN xserver_name IS NULL THEN '' ELSE 'xserver_name: ' + [xserver_name] END + 
    CASE WHEN xdttm_ins IS NULL THEN '' ELSE 'xdttm_ins: ' + [xdttm_ins] END + 
    CASE WHEN xdttm_last_ins_upd IS NULL THEN '' ELSE 'xdttm_last_ins_upd: ' + [xdttm_last_ins_upd] END + 
    CASE WHEN xfallback_vstart IS NULL THEN '' ELSE 'xfallback_vstart: ' + [xfallback_vstart] END + 
    CASE WHEN dbid IS NULL THEN '' ELSE 'dbid: ' + [dbid] END + 
    CASE WHEN segmap IS NULL THEN '' ELSE 'segmap: ' + [segmap] END + 
    CASE WHEN lstart IS NULL THEN '' ELSE 'lstart: ' + [lstart] END + 
    CASE WHEN sizepg IS NULL THEN '' ELSE 'sizepg: ' + [sizepg] END + 
    CASE WHEN vstart IS NULL THEN '' ELSE 'vstart: ' + [vstart] END 
FROM dbo.spt_fallback_usg 
Run Code Online (Sandbox Code Playgroud)


AMt*_*two 6

根据您的格式要求的严格程度,您可以使用FOR XML语法。这将为您的结果生成一个 XML 文档,它将为您提供每一行的字段名称和值。如果您的示例格式有严格的要求,您可以进一步操作结果以将结果反 XML 为更自定义的格式。

有几种不同的方法可以使用 XML 来格式化结果。使用FOR XML PATH将为您提供一个 XML 文档,其中每一行都用<FieldName>和包裹</FieldName>。用 XML 来说,表中的每个字段都是它自己的节点。

SELECT *
FROM dbo.DataDecoded
FOR XML PATH;
Run Code Online (Sandbox Code Playgroud)

将给出如下 XML:

<row>
  <Id>Value</Id>
  <StandardClientId>Value</StandardClientId>
  <ClientName>Value</ClientName>
  ...etc...
</row>
<row>
  <Id>Value</Id>
  <StandardClientId>Value</StandardClientId>
  <ClientName>Value</ClientName>
  ...etc...
</row>
Run Code Online (Sandbox Code Playgroud)

使用该FOR XML RAW语法将为每一行提供一个 XML 节点,不同的字段是该节点的属性:

SELECT *
FROM dbo.DataDecoded
FOR XML RAW;
Run Code Online (Sandbox Code Playgroud)

将给出如下 XML:

<row Id="Value" StandardClientId="Value" ClientName="Value" />
<row Id="Value" StandardClientId="Value" ClientName="Value" />
Run Code Online (Sandbox Code Playgroud)

XML 和 XQuery 非常强大,但我不是专家。我相信您可以继续混合使用 XML 和字符串操作来获得您所需要的。根据这是一次性的事情还是您需要经常做的事情,您可以平衡您在编程解决方案中投入的精力与进行一些手动清理。


Sol*_*zky 5

您可以使用 XML、XQuery 和FLWOR 语句和迭代语法动态处理此问题:

;WITH blob ([data]) AS
(
      -- *** BEGIN Replace with your query ***
      SELECT so.[object_id], so.[name], so.[schema_id], so.[is_ms_shipped], so.[type],
             IIF(so.[type]='S', NULL, 'NotNull') AS [NullTest]
      FROM   master.sys.objects so
      -- *** END Replace with your query ***
      FOR XML RAW, TYPE 
)
SELECT tab.col.query('
            let $end := local-name((./@*)[last()])
            for $item in ./@*
            return concat(
                    local-name($item),
                    ": ",
                    data($item),
                    if (local-name($item) != $end) then "&#13;" else "")
') AS [ReportValues]
FROM  blob
CROSS APPLY blob.data.nodes('/row') tab(col);
Run Code Online (Sandbox Code Playgroud)

返回:

object_id: 3
 name: sysrscols
 schema_id: 4
 is_ms_shipped: 1
 type: S 

object_id: 5
 name: sysrowsets
 schema_id: 4
 is_ms_shipped: 1
 type: S 

...

object_id: 1051150790
 name: queue_messages_1035150733
 schema_id: 4
 is_ms_shipped: 1
 type: IT
 NullTest: NotNull
Run Code Online (Sandbox Code Playgroud)

FOR XML RAWCTE 查询中的部分将结果集转换为单个基于属性的 XML 文档(例如<row object_id="3" name="sysrscols" ... />)。该TYPE关键字使 的结果FOR XML作为真正的XML数据类型返回,否则默认为 return NVARCHAR

CTE 用于允许在 中使用单个 XML 文档,CROSS APPLY以便它可以通过.nodes()(但仅用于匹配您现有的查询行为,否则最终结果仍将是单个 blob)拆分回单独的行.

其中的 FLWOR 语句.query()用于迭代存在的任何属性(以前称为列)。

PS 这里使用的 FLWOR 语法是我为回答以下与生成 JSON 输出(无法访问 SQL Server 2016)相关的 StackOverflow 问题而提出的一些简化版本:

SQL Server 视图使用 JSON 提供一致的表结构

我提到这一点是因为 FLWOR 语法中有逻辑来处理在最后一行之外的每一行的末尾放置字段分隔符。在这里,我将其更改为使用换行符而不是逗号。但是,问题中的原始查询采用简单的方法,只需在每个字段(包括最后一个字段)的末尾放置一个换行符。如果这确实是此处所需的功能,则可以通过删除以 开头的行let $end...然后将该if (local-name($item)...)行更改为 just来进一步减少上面显示的 FLWOR 语法"&#13;")