Transact SQL - 动态构建列名

时间:2012-08-10 08:50:06

标签: sql sql-server-2008 tsql

这个对我来说很棘手......

我在Microsoft SQL Server 2008上工作,并且我有一个包含人名的表。人名可能会随着时间的推移而发生变化,因此也有历史信息。

示例:

PID Sequence Name
1   0        Michael Hansen
2   0        Ulla Hansen
2   94       Ulla Margrethe Hansen
2   95       Ulla Margrethe Jensen
3   0        Daniella Oldfield
3   95       Daniella Quist

(我还没有构建这个表 - 所以我不能进入并改变数据的存储方式)。 PID 1的人称为Michael Hansen。这是他现在的名字(序列0总是指定当前名字),因为没有其他记录,他一直被命名为Michael Hansen。

人PID 2目前称为Ulla Hansen(序列0)。在此之前,她被称为Ulla Margrethe Hansen(因为这是下一个序列号),在此之前她又被称为Ulla Margrethe Jensen。

我对这个表的了解是当前名称始终是序列0.我也知道如果有两个名称,则下一个序列是95.还有三个历史名称:当前名称:序列0,在该序列之前94最古老的名字95。

我的数据库包含有关最多6个历史名称的信息(序列0,91,92,93,94,95)。

现在我被告知要在新表中列出所有名字,每人只有一行,如:

PID Name1             Name2                   Name3
1   Michael Hansen
2   Ulla Hansen       Ulla Margrethe Hansen   Ulla Margrethe Jensen
3   Daniella Oldfield Daniella Quist

到目前为止,我有以下几乎可以运行的SQL:

SELECT PID 
  ,MAX(CASE sequence WHEN 0 THEN Name ELSE '' END) AS Name1
  ,MAX(CASE sequence WHEN 91 THEN Name ELSE '' END) AS Name2
  ,MAX(CASE sequence WHEN 92 THEN Name ELSE '' END) AS Name3
  ,MAX(CASE sequence WHEN 93 THEN Name ELSE '' END) AS Name4
  ,MAX(CASE sequence WHEN 94 THEN Name ELSE '' END) AS Name5
  ,MAX(CASE sequence WHEN 95 THEN Name ELSE '' END) AS Name6
FROM tblShipTypeHistory
GROUP BY PID

它为我提供了每个PID在一行中所需的所有名称。并且当前名称也始终列在Name1下。问题是我需要第二个最新名称在Name2等列中。我的情况(当然)只有在一个人有六个名字时才有效。

所以我需要做的是根据PID实际拥有的名称动态地将Name2列命名为Name6。所以我尝试动态构建我的SQL(DECLARE @SQL AS NVARCHAR(MAX),然后将@SQL =设置为上面的SQL示例)。然后我尝试了类似

的东西
SET @SQL = 'SELECT ....
,MAX(CASE sequence WHEN 91 THEN Name ELSE '' END) AS Name' + COUNT(PID) - 4 + '
,MAX(CASE sequence WHEN 92 THEN Name ELSE '' END) AS Name' + COUNT(PID) - 3 + '
,MAX(CASE sequence WHEN 93 THEN Name ELSE '' END) AS Name' + COUNT(PID) - 2 + '
,MAX(CASE sequence WHEN 94 THEN Name ELSE '' END) AS Name' + COUNT(PID) - 1 + '
,MAX(CASE sequence WHEN 95 THEN Name ELSE '' END) AS Name' + COUNT(PID) + '

逻辑上这可以工作(它会给出正确的列名),但不幸的是语法“+ count(PID)”不起作用。

(P!)所以谁有解决方案?

提前谢谢。

4 个答案:

答案 0 :(得分:3)

将RANK()和PIVOT()结合使用。 Rank()用于计算名称的“年龄”,使用pivot来获取所有列。

create table t(pid int not null, sequence int not null, name nvarchar(50))


INSERT INTO t VALUES
(1,   0,        N'Michael Hansen'),
(2,   0,        N'Ulla Hansen'),
(2,   94,       N'Ulla Margrethe Hansen'),
(2,   95,       N'Ulla Margrethe Jensen'),
(3,   0,        N'Daniella Oldfield'),
(3,   95,       N'Daniella Quist')

SELECT pid, [1] as Name1, [2] as Name2, [3] as Name3, [4] as Name4, [5] as Name5, [6] as Name6
FROM
(
SELECT pid, name, rank() over(partition BY pid ORDER BY sequence) AS name_age
FROM t) SOURCE
PIVOT
(
  max(name)
 FOR [name_age] in ([1], [2], [3], [4], [5], [6])) as pvt

Sql fiddle:http://sqlfiddle.com/#!3/d8301/16

答案 1 :(得分:2)

您的语法错误在于您尝试使用NVARCHAR连接COUNT(*)这是一个int。我认为这样的事情对你有用:

SET @SQL = 'SELECT ....
,MAX(CASE sequence WHEN 91 THEN Name ELSE '' END) AS Name' + CAST(COUNT(PID) - 4 AS NVARCHAR) + '
,MAX(CASE sequence WHEN 92 THEN Name ELSE '' END) AS Name' + CAST(COUNT(PID) - 3 AS NVARCHAR) + '
,MAX(CASE sequence WHEN 93 THEN Name ELSE '' END) AS Name' + CAST(COUNT(PID) - 2 AS NVARCHAR) + '
,MAX(CASE sequence WHEN 94 THEN Name ELSE '' END) AS Name' + CAST(COUNT(PID) - 1 AS NVARCHAR) + '
,MAX(CASE sequence WHEN 95 THEN Name ELSE '' END) AS Name' + CAST(COUNT(PID) AS NVARCHAR) + '

然而,这似乎不是未来的证据,因为你仍在手动创建SQL,我不知道为什么你的序列从0到91,但我认为它们总是按升序排列,即使有差距所以使用ROW_NUMBER()函数来获取每个名称的实例:

DECLARE @SQL NVARCHAR(MAX) = '',
        @PVT NVARCHAR(MAX) = ''

SELECT  @SQL = @SQL + ', COALESCE(' + QUOTENAME('Name' + RowNum) + ', '''') AS ' +  QUOTENAME('Name' + RowNum),
        @PVT = @PVT + ', ' + QUOTENAME('Name' + RowNum)
FROM    (   SELECT  DISTINCT CONVERT(VARCHAR, ROW_NUMBER() OVER(PARTITION BY PID ORDER BY Sequence)) [RowNum]
            FROM    tblShipTypeHistory
        ) rn

SET @SQL = 'SELECT PID' + @SQL + '
            FROM    (   SELECT  PID, 
                                Name, 
                                ''Name'' + CONVERT(VARCHAR, ROW_NUMBER() OVER(PARTITION BY PID ORDER BY Sequence)) [NameID]
                        FROM    tblShipTypeHistory
                    ) data
                    PIVOT
                    (   MAX(Name)
                        FOR NameID IN (' + STUFF(@PVT, 1, 2, '') + ')
                    ) pvt'

EXECUTE SP_EXECUTESQL @SQL

<强> Example on SQL Fiddle

答案 2 :(得分:1)

with names(pid, name, rn) as 
(
  select pid, 
         name, 
         ROW_NUMBER() over (partition by pid order by sequence) 
  from tblShipTypeHistory
)
select pid, 
       [1] as Name1, 
       [2] as Name2, 
       [3] as Name3, 
       [4] as Name4, 
       [5] as Name5, 
       [6] as Name6
from names pivot(max(name) for rn in ([1], [2], [3], [4], [5], [6])) as a;

答案 3 :(得分:0)