如何将行展平为列并指定列名称

时间:2016-10-15 22:03:14

标签: sql sql-server pivot

我已经看到很多关于旋转和其他解决方案的答案,用于将1:M关系从多行转换为多列,但我还没有看到一个解决我的特定场景的问题。我正在使用SQL Server 2014。

我有两张桌子:参加者和AttendeeChild。以下是每个中的相关列:

与会者

编号

AttendeeChild

标识
AttendeeId
年龄
名称
性别

参加者可以拥有任意数量的孩子"行。对于每个与会者,我想将AttendeeChild行展平为列,以便输出看起来像AttendeeId = 1;

AttendeeId Child1Age Child1Name Child1Gender Child2Age Child2Name Child2Gender
---------- --------- ---------- ------------ --------- ---------- ------------
1          10        Sam        Boy          9         Sally      Girl        

子相关属性集应该继续扩展每个AttendeeChild行的列数,列标题应该保留模式" Child {#} {Attribute}"其中{#}是来自AttendeeChild的下一个子行的递增计数器,{Attribute}是" Name"," Age"或"性别"。

我希望这是传达问题的足够信息。

2 个答案:

答案 0 :(得分:0)

在制作动态SQL时使用此模板作为模板:

WITH ChildNumbered AS
(
   SELECT
      AttendeeID,
      Age,
      Name,
      Gender,
      ROW_NUMBER() OVER (PARTITION BY AttendeeID ORDER BY ID) AS RN
)
SELECT 
  A.ID as AttendeeId,
  C1.Age AS Child1Age, C1.Name AS Child1Name, C1.Gender AS Child1Gender,
  C2.Age AS Child2Age, C2.Name AS Child2Name, C2.Gender AS Child2Gender,
  -- ...
  CX.Age AS ChildXAge, CX.Name AS ChildXName, CX.Gender AS ChildXGender,
  CY.Age AS ChildYAge, CY.Name AS ChildYName, CY.Gender AS ChildYGender,
  CZ.Age AS ChildZAge, CZ.Name AS ChildZName, CZ.Gender AS ChildZGender
FROM Attendee AS A
LEFT JOIN ChildNumbered AS C1 ON A.ID = C1.AttendeeID AND C1.RN = 1
LEFT JOIN ChildNumbered AS C2 ON A.ID = C2.AttendeeID AND C2.RN = 2
-- ...
LEFT JOIN ChildNumbered AS CX ON A.ID = CX.AttendeeID AND CX.RN = X
LEFT JOIN ChildNumbered AS CY ON A.ID = CY.AttendeeID AND CY.RN = Y
LEFT JOIN ChildNumbered AS CZ ON A.ID = CZ.AttendeeID AND CZ.RN = Z

答案 1 :(得分:0)

另一种选择是使用一些XML将数据转换为EAV结构(实体属性值)

这种方法非常动态,会产生适当数量的"组"按照正确的顺序。

Declare @AttendeeChild table (Id int,AttendeeId int,Age Int,Name varchar(50),Gender varchar(25))
Insert Into @AttendeeChild values
(1,1,10,'Sam','Boy'),
(2,1,9,'Sally','Girl'),
(2,2,9,'Sue','Boy')

-- Convert Data to EAV Structure'ish
Declare @XML xml = (Select *,GrpSeq=Row_Number() over (Partition By AttendeeId Order By Age Desc) from @AttendeeChild for XML RAW)
Select AttendeeId = r.value('@AttendeeId','int')
      ,GrpSeq     = r.value('@GrpSeq','int')
      ,ColSeq     = Row_Number() over (Partition By r.value('@AttendeeId','int') Order By (Select NULL))
      --,Item     = attr.value('local-name(.)','varchar(100)')
      ,Element    = 'Child'+r.value('@GrpSeq','varchar(10)')+attr.value('local-name(.)','varchar(100)')
      ,Value      = attr.value('.','varchar(max)') 
 Into  #Temp
 From  @XML.nodes('/row') as A(r)
 Cross Apply A.r.nodes('./@*') AS B(attr)
 Where attr.value('local-name(.)','varchar(100)') not in ('ID','AttendeeId','GrpSeq')

-- Get Cols in correct Order
Declare @Cols varchar(max) = Stuff((Select ',' + QuoteName(Element) 
                                     From  (Select Distinct Top 100 Percent  ColSeq,Element From #Temp Order By ColSeq ) A
                                     For XML Path(''), Type
                                    ).value('.', 'varchar(max)'),1,1,'')

-- Execute Dynamic Pivot
Declare @SQL varchar(max) = '
Select *
 From (Select AttendeeId,Element,Value From #Temp) T
 Pivot (
        max(Value)
        For [Element] in (' + @Cols + ')
       ) P '

Exec(@SQL)

返回

enter image description here