Sql server 基于列中的值将单行转换为多行
我有一个表,其中每个记录代表一个人,有许多列用于指示他们参加了哪些活动:Sql server 基于列中的值将单行转换为多行,sql-server,unpivot,Sql Server,Unpivot,我有一个表,其中每个记录代表一个人,有许多列用于指示他们参加了哪些活动: CREATE TABLE EventAttendees ( Person VARCHAR(100), [Event A] VARCHAR(1), [Event B] VARCHAR(1), [Event C] VARCHAR(1) ) INSERT INTO EventAttendees SELECT 'John Smith','x',NULL,NULL UNION SELECT 'Jan
CREATE TABLE EventAttendees
(
Person VARCHAR(100),
[Event A] VARCHAR(1),
[Event B] VARCHAR(1),
[Event C] VARCHAR(1)
)
INSERT INTO EventAttendees
SELECT 'John Smith','x',NULL,NULL
UNION
SELECT 'Jane Doe',NULL,'x','x'
UNION
SELECT 'Phil White','x',NULL,'x'
UNION
SELECT 'Sarah Jenkins','x','x','x'
例如,它看起来像这样:
SELECT * FROM Event Attendees
/---------------|---------|---------|---------\
| Person | Event A | Event B | Event C |
|---------------|---------|---------|---------|
| John Smith | x | NULL | NULL |
| Jane Doe | NULL | x | x |
| Phil White | x | NULL | x |
| Sarah Jenkins | x | x | x |
\---------------|---------|---------|---------/
我想生成一个参加哪些活动的人员列表,因此我希望的输出是:
/---------------|---------|
| Person | Event |
|---------------|---------|
| John Smith | Event A |
| Jane Doe | Event B |
| Jane Doe | Event C |
| Phil White | Event A |
| Phil White | Event C |
| Sarah Jenkins | Event A |
| Sarah Jenkins | Event B |
| Sarah Jenkins | Event C |
\---------------|---------/
事实上,我有3个以上的事件,但以上是为了便于解释(顺便说一句,这不是一个家庭作业问题)。由于事件将来可能会更改,并且我无法控制正在传递的数据,因此我确实需要一个能够处理任意数量的可能事件列的动态解决方案
我假设我可以用
UNPIVOT
做一些事情,但我就是想不出来,或者找不到一个好的例子,或者其他地方可以从中工作-有人能帮忙吗?我使用外部应用来做这件事:
select ea.person, v.EventName
from EventAttendees ea outer apply
(values ('Event A', [Event A]),
('Event B', [Event B]),
('Event C', [Event C])
) v(EventName, EventFlag)
where v.EventFlag = 'x'
我使用外部应用执行此操作
:
select ea.person, v.EventName
from EventAttendees ea outer apply
(values ('Event A', [Event A]),
('Event B', [Event B]),
('Event C', [Event C])
) v(EventName, EventFlag)
where v.EventFlag = 'x'
试试像这样的东西
SELECT * FROM (
SELECT Person, CASE WHEN [Event A] = 'x' THEN 'Event A' END AS [Event] FROM EventAttendees
UNION
SELECT Person, CASE WHEN [Event B] = 'x' THEN 'Event B' END AS [Event] FROM EventAttendees
UNION
SELECT Person, CASE WHEN [Event C] = 'x' THEN 'Event C' END AS [Event] FROM EventAttendees
) AS EventAttendees
WHERE Event is not null
order by Person
对于动态sql,您可以尝试以下方法:
DECLARE @name varchar(30)
DECLARE @sql varchar(1000) = 'SELECT * FROM (';
DECLARE NameCursor CURSOR
FOR select name from sys.all_columns where object_id = (select object_id from sys.tables where name='EventAttendees') and name!='Person'
OPEN NameCursor
FETCH NEXT FROM NameCursor INTO @name
WHILE @@FETCH_STATUS = 0
BEGIN
SET @sql += 'SELECT Person, CASE WHEN [' + @name+'] = ''x'' THEN ''' + @name +''' END AS [Event] FROM EventAttendees'
FETCH NEXT FROM NameCursor INTO @name
IF(@@FETCH_STATUS = 0)
BEGIN
SET @sql += ' UNION ';
END
END;
CLOSE NameCursor;
DEALLOCATE NameCursor;
SET @sql += ') AS EventAttendees
WHERE Event is not null
order by Person';
execute (@sql);
试试像这样的东西
SELECT * FROM (
SELECT Person, CASE WHEN [Event A] = 'x' THEN 'Event A' END AS [Event] FROM EventAttendees
UNION
SELECT Person, CASE WHEN [Event B] = 'x' THEN 'Event B' END AS [Event] FROM EventAttendees
UNION
SELECT Person, CASE WHEN [Event C] = 'x' THEN 'Event C' END AS [Event] FROM EventAttendees
) AS EventAttendees
WHERE Event is not null
order by Person
对于动态sql,您可以尝试以下方法:
DECLARE @name varchar(30)
DECLARE @sql varchar(1000) = 'SELECT * FROM (';
DECLARE NameCursor CURSOR
FOR select name from sys.all_columns where object_id = (select object_id from sys.tables where name='EventAttendees') and name!='Person'
OPEN NameCursor
FETCH NEXT FROM NameCursor INTO @name
WHILE @@FETCH_STATUS = 0
BEGIN
SET @sql += 'SELECT Person, CASE WHEN [' + @name+'] = ''x'' THEN ''' + @name +''' END AS [Event] FROM EventAttendees'
FETCH NEXT FROM NameCursor INTO @name
IF(@@FETCH_STATUS = 0)
BEGIN
SET @sql += ' UNION ';
END
END;
CLOSE NameCursor;
DEALLOCATE NameCursor;
SET @sql += ') AS EventAttendees
WHERE Event is not null
order by Person';
execute (@sql);
您可以使用unpivot来完成,正如您所说,您只需要确保您告诉它它是为了什么活动,否则您只需要得到一个X:
CREATE TABLE #tmpEventAttendees
(
Person VARCHAR(100),
[Event A] VARCHAR(1),
[Event B] VARCHAR(1),
[Event C] VARCHAR(1)
)
INSERT INTO #tmpEventAttendees
SELECT 'John Smith','x',NULL,NULL
UNION
SELECT 'Jane Doe',NULL,'x','x'
UNION
SELECT 'Phil White','x',NULL,'x'
UNION
SELECT 'Sarah Jenkins','x','x','x'
SELECT Person, [Event]
FROM
(
SELECT Person ,
CASE WHEN [Event A] IS NOT NULL THEN 'Event A' END AS [Event A] ,
CASE WHEN [Event B] IS NOT NULL THEN 'Event B' END AS [Event B] ,
CASE WHEN [Event C] IS NOT NULL THEN 'Event C' END AS [Event C]
FROM #tmpEventAttendees
) AS cp
UNPIVOT
(
[Event] FOR [Events] IN ([Event A], [Event B], [Event C])
) AS up;
DROP TABLE #tmpEventAttendees
您可以使用unpivot来完成,正如您所说,您只需要确保您告诉它它是为了什么活动,否则您只需要得到一个X:
CREATE TABLE #tmpEventAttendees
(
Person VARCHAR(100),
[Event A] VARCHAR(1),
[Event B] VARCHAR(1),
[Event C] VARCHAR(1)
)
INSERT INTO #tmpEventAttendees
SELECT 'John Smith','x',NULL,NULL
UNION
SELECT 'Jane Doe',NULL,'x','x'
UNION
SELECT 'Phil White','x',NULL,'x'
UNION
SELECT 'Sarah Jenkins','x','x','x'
SELECT Person, [Event]
FROM
(
SELECT Person ,
CASE WHEN [Event A] IS NOT NULL THEN 'Event A' END AS [Event A] ,
CASE WHEN [Event B] IS NOT NULL THEN 'Event B' END AS [Event B] ,
CASE WHEN [Event C] IS NOT NULL THEN 'Event C' END AS [Event C]
FROM #tmpEventAttendees
) AS cp
UNPIVOT
(
[Event] FOR [Events] IN ([Event A], [Event B], [Event C])
) AS up;
DROP TABLE #tmpEventAttendees
我找到了我所想到的解决方案,但是是的,它确实需要动态SQL来获取相关的列名,以便输入到UNPIVOT
:
declare @sql varchar(max)
set @sql =
'select Person, EventName
from EventAttendees
unpivot
(
Attended for EventName in (' + (select
stuff((
select ',' + QUOTENAME(c.[name])
from sys.columns c
join sys.objects o on c.object_id = o.object_id
where o.[name] = 'EventAttendees'
and c.column_id > 1
order by c.[name]
for xml path('')
),1,1,'') as colList) + ')
) unpiv
where unpiv.Attended = ''x''
order by Person, EventName'
exec (@sql)
在本例中,我假设事件列是从表中的第二列开始的,但显然,如果需要,我可以在子查询中使用一些不同的逻辑来标识相关列
在我的示例数据中,这给出了期望的结果:
/---------------------------\
| Person | EventName |
|---------------|-----------|
| Jane Doe | Event B |
| Jane Doe | Event C |
| John Smith | Event A |
| Phil White | Event A |
| Phil White | Event C |
| Sarah Jenkins | Event A |
| Sarah Jenkins | Event B |
| Sarah Jenkins | Event C |
\---------------------------/
我认为我更喜欢使用游标,尽管我还没有确认这两种动态方法之间的性能差异(如果有的话)
谢谢大家在这个问题上的帮助和建议,非常感谢 找到了我所想到的解决方案,但是是的,它确实需要动态SQL来获取相关的列名,以便输入到UNPIVOT
:
declare @sql varchar(max)
set @sql =
'select Person, EventName
from EventAttendees
unpivot
(
Attended for EventName in (' + (select
stuff((
select ',' + QUOTENAME(c.[name])
from sys.columns c
join sys.objects o on c.object_id = o.object_id
where o.[name] = 'EventAttendees'
and c.column_id > 1
order by c.[name]
for xml path('')
),1,1,'') as colList) + ')
) unpiv
where unpiv.Attended = ''x''
order by Person, EventName'
exec (@sql)
在本例中,我假设事件列是从表中的第二列开始的,但显然,如果需要,我可以在子查询中使用一些不同的逻辑来标识相关列
在我的示例数据中,这给出了期望的结果:
/---------------------------\
| Person | EventName |
|---------------|-----------|
| Jane Doe | Event B |
| Jane Doe | Event C |
| John Smith | Event A |
| Phil White | Event A |
| Phil White | Event C |
| Sarah Jenkins | Event A |
| Sarah Jenkins | Event B |
| Sarah Jenkins | Event C |
\---------------------------/
我认为我更喜欢使用游标,尽管我还没有确认这两种动态方法之间的性能差异(如果有的话)
谢谢大家在这个问题上的帮助和建议,非常感谢 但是如果我有200个事件列,我需要200个子查询UNION
ed在一起?如果可能的话,我正在寻找一种动态执行此操作的方法。是否为每个新事件生成新列?我不是个人,但传递给我的数据集就是这样做的。在这种情况下,您需要创建动态sqlReally?没有动态SQL就无法做到这一点?我以前肯定见过类似的东西,但现在找不到了。但是如果我有200个事件列,我需要200个子查询UNION
ed在一起?如果可能的话,我正在寻找一种动态执行此操作的方法。是否为每个新事件生成新列?我不是个人,但传递给我的数据集就是这样做的。在这种情况下,您需要创建动态sqlReally?没有动态SQL就无法做到这一点?我以前肯定看到过类似的东西,但现在找不到了。按照per的回答,这将需要在每个事件的值中有一个单独的行-我有数百个事件,所以如果可能的话,我宁愿不把那么大的语句放在一起。此外,事件在未来可能会发生变化,因此它确实需要动态。我会把问题更新得更清楚。@3N1GM4。示例数据有三个事件列。如果您经常向表中添加列,您的应用程序就会出现问题。我同意@GordonLinoff,但不幸的是,这超出了我的控制范围。按照的回答,这将需要在每个事件的值中有一个单独的行-我有数百个事件,因此,如果可能的话,我宁愿不必写那么大的声明。此外,事件在未来可能会发生变化,因此它确实需要动态。我会把问题更新得更清楚。@3N1GM4。示例数据有三个事件列。如果您经常向表中添加列,您的应用程序就会出现问题。我同意@GordonLinoff,但不幸的是,这超出了我的控制范围。与其他答案一样,这要求我在每次事件列更改(添加新事件、重命名事件或删除事件)时重新编写查询,所以这对我没有帮助。明白了,这不是你最初的问题,因此上面的代码。我会想一想。与其他答案一样,这要求我在每次事件列更改(添加新事件、重命名事件或删除事件)时重新编写查询,因此这对我没有帮助。请理解,这不是您最初的问题,因此上面的代码是这样的。我想一想。