如何将行展平为列并指定列名
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
我看到了很多关于将 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