基于拆分值透视动态列
Posted
技术标签:
【中文标题】基于拆分值透视动态列【英文标题】:Pivoting a dynamic column based on split value 【发布时间】:2021-07-04 00:16:54 【问题描述】:我有几个如下表
位置表
Id | PositionName |
---|---|
1 | Developer |
2 | Analyst |
3 | Tester |
员工表
Id | Name | Positions |
---|---|---|
1 | John | 1,2 |
2 | Lisa | 3 |
3 | Smith | 1 |
4 | Willow | NULL |
5 | Burly | 2,3 |
从上面的表格中,生成如下所示的透视报告的查询是什么?
Id | Name | Developer | Analyst | Tester |
---|---|---|---|---|
1 | John | Y | Y | N |
2 | Lisa | N | N | Y |
3 | Smith | Y | N | N |
4 | Willow | N | N | N |
5 | Burly | N | Y | Y |
我坚持这样一个事实,我必须做一些拆分字符串并使用CASE
WHEN
将Y
或N
应用于枢轴。
这是我在 SQL fiddle 中的游乐场http://sqlfiddle.com/#!18/2ad8d/31
【问题讨论】:
我强烈建议您将Positions
列取消透视到单独的表中,并升级到更现代且受支持的 SQL Server 版本
ikr。这就是应该做的。但我有一项旧技术需要在短时间内进行维护。由于现有数据和在前面工作的应用程序,规范化表格不是选项。
【参考方案1】:
我现在无法让 Fiddle 工作,但代码还不错,不能完全重现。请注意,您应该注意您的 @Cols 变量,以确保您的所有位置名称在现实生活中都用作列名称,但通常情况并非如此!另请注意,您的原始示例有重复的员工 ID,我给了它们唯一的值。
CREATE table Position (
Id int,
Name varchar(10)
);
insert into Position values
(1, 'Developer'),
(2, 'Analyist'),
(3, 'Tester');
CREATE table Employee (
Id int,
Name varchar(10),
Position varchar(MAX)
);
insert into Employee values
(1, 'John', '1,3'),
(2, 'Lisa', '3'),
(3, 'Smith', '1'),
(4, 'Willow', NULL),
(5, 'Burly', '2,3');
--This is your basic working PIVOT, we'll implement as a dynamic query once we're satisfied it works
;with cteEmp as (
SELECT E.Id as EID, E.Name as EName, P.Name as PName
, CASE WHEN CHARINDEX(CONVERT(nvarchar(10), P.Id)
, CONCAT(',', E.Position, ',') ) > 0 THEN 'Y' ELSE 'N' END as HasPos
FROM Employee as E CROSS JOIN Position as P
)SELECT Piv.* FROM cteEmp as E PIVOT (max(HasPos) FOR PName in (Developer, Analyist, Tester)) as Piv;
--To make it dynamic, build the list of positions from a query
DECLARE @cols AS NVARCHAR(MAX),
@query AS NVARCHAR(MAX)
select @cols = STUFF((SELECT DISTINCT ',' + QUOTENAME(Name)
FROM Position
FOR XML PATH(''), TYPE ).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set @query = 'with cteEmp as (
SELECT E.Id as EID, E.Name as EName, P.Name as PName
, CASE WHEN CHARINDEX(CONVERT(nvarchar(10), P.Id)
--, CONCAT('','', E.Position, '','') ) > 0 --Not 2008R2!
, '','' + E.Position + '','' ) > 0
THEN ''Y'' ELSE ''N'' END as HasPos
FROM Employee as E CROSS JOIN Position as P
)SELECT Piv.* FROM cteEmp as E PIVOT (max(HasPos)
FOR PName in (' + @cols + ') ) as Piv;'
execute(@query)
编辑:修复了动态查询中缺少的右括号。
编辑:注意:这不使用拆分功能,它利用了 ID 必须是整数并且列出的位置以可预测的方式分隔的事实。我们不需要职位 ID 列表,我们只需要知道相关职位 ID 是否在列表中。我们添加和附加逗号,以便我们可以搜索“,1”,而不仅仅是“1”,因为“1”也可以匹配“21”,但“,1”只匹配单个 ID。
【讨论】:
感谢@Robert,它按预期工作,但是这个不需要额外的功能来分割字符串。一件事,动态部分在as Piv
之前缺少右括号)
感谢@yokaii,很好地抓住了缺少的括号(由于某种原因,我今天无法让 sqlFiddler 为我工作,所以测试是个问题),我添加了解释为什么我的解决方案没有使用分割功能。您当然可以使用 split 函数使其工作,但这具有较少的外部依赖性。如果您尝试两种方式,您可能会报告性能差异,我会很好奇。
另一个问题是CONCAT
在 SQL 2008 R2 中不起作用,但我知道如何编译查询。我只需要找到替换来处理CONCAT
,并且我必须避免使用额外的自定义函数来执行拆分字符串。原因是查询将在应用程序的某处进行管理以生成一份时间报告,并且可以随时更改。
噢!抱歉@yokaii,我将原来的“CONTAINS”更改为“CHARINDEX”以说明 2008R2,但我忘记了 CONCAT 也更新了!您应该能够在 2008R2 中将 CONCAT 替换为 '','' + E.Position + '',''
。这不使用 SPLIT 功能,所以你应该在那里很好。此外,不要忘记接受(有效)和/或投票(信息丰富或创新)答案,以帮助未来的搜索者确定值得详细研究的解决方案。
是的,我做了加号+
连接解决方法,它工作得很好。我没有足够的时间来运行查询分析以获得性能。认为这将用于生成一次性报告。【参考方案2】:
您只需加入Position
表即可获取职位名称
DECLARE @cols AS NVARCHAR(MAX),
@query AS NVARCHAR(MAX);
SET @cols = STUFF((SELECT DISTINCT ',' + QUOTENAME(Name)
FROM Position
FOR XML PATH(''), TYPE ).value('text()[1]', 'NVARCHAR(MAX)')
,1,1,'');
set @query = N'
SELECT Name,' + @cols + N'
from
(
SELECT e.Name, p.PositionName
FROM Employee AS e
CROSS APPLY dbo.SplitString(Position, '','') AS ep(PositionId)
JOIN Position AS p ON p.Id = ep.PositionId
) x
pivot
(
COUNT(*)
FOR p.PositionName IN (' + @cols + N')
) p
';
exec sp_executesql @query;
【讨论】:
以上是关于基于拆分值透视动态列的主要内容,如果未能解决你的问题,请参考以下文章