【问题标题】:Build SQL query with dynamic columns使用动态列构建 SQL 查询
【发布时间】:2013-01-30 04:10:55
【问题描述】:

我的表格如下:

患者表

PatientId   Name
1           James
...

访问表

Date    PatientID_FK    Weight
1/1     1               220
2/1     1               210 
...

如何构建返回的查询

PatientId    Name    Visit1Date    Visit1Weight    Visit2Date    Visit2Weight    ...
1            James   1/1           220             2/1           210
2            ...

我们如何以这种方式添加更多列? SELECT 怎么写?请帮忙。


StackExchange 上的一些帖子说 SQL 语句不可能处理它。真的是这样吗?

【问题讨论】:

  • 您使用的是什么 RDBMS 和版本?
  • 只是不知道该怎么做。我们使用的是 SQL Server 2008R2。
  • @njk,我只是不明白你为什么不赞成我的问题。这是一个合理的问题。我认为许多其他人将从讨论中受益。我搜索了很长一段时间没有成功。这就是我在这里输入这个问题的原因。如果您知道有一个有用的链接,请在此处添加。我们中的许多人会很感激。谢谢。
  • 我想说,如果他/她知道如何提出问题,任何人都可以找到答案。您的专家立即知道这是关于PIVOT。请对像我这样不知道如何描述寻求帮助的无知者宽容一点。

标签: sql sql-server pivot unpivot


【解决方案1】:

这种类型的数据转换需要使用pivot 和unpivot 函数来完成。由于您的访问将是未知的,因此您将需要使用动态 sql。但首先,我将向您展示如何使用硬编码的值构建查询,以便更容易理解该过程的工作原理。

首先,您需要 UNPIVOT date 和 weight 列,以便值在同一列中。这可以使用UNION ALL 查询或 unpivot 函数来完成:

反透视:

select patientid, name, rn, col, value
from
(
  select p.patientid, p.name, convert(char(5), v.date, 110) date, 
    cast(v.weight as char(5)) weight,
    row_number() over(partition by PatientID_FK order by date) rn
  from patients p
  left join visits v
    on p.patientid = v.PatientID_FK
) src
unpivot
(
  value
  for col in (date, weight)
) unpiv

见SQL Fiddle with Demo。此查询的结果将 date 和 weight 列的值放入具有多行的单个列中。请注意,我对记录应用了row_number(),这样您就可以知道每次访问的值:

| PATIENTID |  NAME | RN |    COL | VALUE |
-------------------------------------------
|         1 | James |  1 |   date | 01-01 |
|         1 | James |  1 | weight | 220   |
|         1 | James |  2 |   date | 02-01 |
|         1 | James |  2 | weight | 210   |

枢轴:

下一步是将PIVOT 函数应用于col 列中的项目,但首先我们需要更改名称,以便它为您提供所需的名称。

为此,我稍微更改了SELECT 语句以将行号添加到列名:

select patientid, name, 'Visit'+col + cast(rn as varchar(10)) new_col, 
  value
from ...

这将为您提供新名称,这些名称是您想要作为列的名称:

Visitdate1 
Visitweight1
Visitdate2
Visitweight2

到PIVOT,如果您对值进行硬编码,您的查询将如下所示:

select *
from
(
  select patientid, name, 'Visit'+col + cast(rn as varchar(10)) new_col, 
    value
  from
  (
    select p.patientid, p.name, convert(char(5), v.date, 110) date, 
      cast(v.weight as char(5)) weight,
      row_number() over(partition by PatientID_FK order by date) rn
    from patients p
    left join visits v
      on p.patientid = v.PatientID_FK
  ) src
  unpivot
  (
    value
    for col in (date, weight)
  ) unpiv
) s1
pivot
(
  max(value)
  for new_col in (Visitdate1,Visitweight1,
                  Visitdate2,Visitweight2)
) piv

见SQL Fiddle with Demo。

动态枢轴:

现在我已经解释了如何设置背后的逻辑,您将希望使用动态 sql 实现相同的过程。您的动态 sql 版本将是:

DECLARE @colsUnpivot AS NVARCHAR(MAX),
    @query  AS NVARCHAR(MAX),
    @colsPivot as  NVARCHAR(MAX)

select @colsUnpivot = stuff((select ', '+quotename(C.name)
         from sys.columns as C
         where C.object_id = object_id('visits') and
               C.name not in ('PatientID_FK')
         for xml path('')), 1, 1, '')

select @colsPivot = STUFF((SELECT  ',' + quotename('Visit'+c.name 
                                          + cast(v.rn as varchar(10)))
                    from
                    (
                       select row_number() over(partition by PatientID_FK order by date) rn
                       from visits
                    ) v
                    cross apply sys.columns as C
                   where C.object_id = object_id('visits') and
                     C.name not in ('PatientID_FK')
                   group by c.name, v.rn
                   order by v.rn
            FOR XML PATH(''), TYPE
            ).value('.', 'NVARCHAR(MAX)') 
        ,1,1,'')

set @query 
  = 'select *
      from
      (
        select patientid, name, ''Visit''+col + cast(rn as varchar(10)) new_col,
          value
        from 
        (
          select p.patientid, p.name, convert(char(5), v.date, 110) date, 
            cast(v.weight as char(5)) weight,
            row_number() over(partition by PatientID_FK order by date) rn
          from patients p
          left join visits v
            on p.patientid = v.PatientID_FK
        ) x
        unpivot
        (
          value
          for col in ('+ @colsunpivot +')
        ) u
      ) x1
      pivot
      (
        max(value)
        for new_col in ('+ @colspivot +')
      ) p'

exec(@query)

见SQL Fiddle with Demo

两个版本的结果是:

| PATIENTID |  NAME | VISITDATE1 | VISITWEIGHT1 | VISITDATE2 | VISITWEIGHT2 |
-----------------------------------------------------------------------------
|         1 | James |      01-01 |        220   |      02-01 |        210   |

【讨论】:

    猜你喜欢
    • 2018-08-04
    • 1970-01-01
    • 1970-01-01
    • 2018-02-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-09-26
    • 2015-01-30
    相关资源
    最近更新 更多