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

How to flatten rows into columns and specify column names

我看到了很多关于将 1:M 关系从多行转换为多列的旋转和其他解决方案的答案,但我还没有看到解决我的特定情况的答案。我正在使用 SQL Server 2014。

我有两个表:Attendee 和 AttendeeChild。以下是每列中的相关列:

与会者

编号

参加者孩子

编号
AttendeeId
年龄
姓名
性别

参加者可以有任意数量的 "child" 行。对于每个与会者,我想将 AttendeeChild 行展平为列,以便 AttendeeId = 1;

的输出看起来像这样
AttendeeId Child1Age Child1Name Child1Gender Child2Age Child2Name Child2Gender
---------- --------- ---------- ------------ --------- ---------- ------------
1          10        Sam        Boy          9         Sally      Girl        

child 相关属性集应继续扩展每个 AttendeeChild 行的列数,列 header 应保持模式 "Child{#}{Attribute}",其中 {#} 是一个来自 AttendeeChild 和 {Attribute} 的下 child 行的递增计数器是 "Name"、"Age" 或 "Gender".

我希望这些信息足以说明问题。

以此为模板制作动态 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

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

这种方法非常动态,会以正确的顺序生成适当数量的 "groups"。

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)

Returns