我在数据库中有数百个具有相同结构的表:
一些Id、Pos、 不同数量的其他字段
因此,例如,一个表可能如下所示:
PersonId, Pos, Hobby, Degree
12345, 1, Basketball, Bachelor of Science
12345, 2, Baseball, Master of Science
12345, 3, Boxing, NULL
12345, 4, Tennis, NULL
22222, 1, Golf, Bachelor of Science
22222, 2, NULL, Master of Science
22222, 3, NULL, Doctorate
我想汇总每列 3-N 的值。所以这会变成:
12345, "Basketball, Baseball, Boxing, Tennis", "Bachelor of Science, Master of Science"
22222, "Golf", "Bachelor of Science, Master of Science, Doctorate"
另一个表可能如下所示:
ClientId, Pos, Location, Language, CommunicationType
33333, 1, North Carolina, English, Phone
33333, 2, New York, Spanish, Email
33333, 3, NULL, Portuguese, NULL
44444, 1, California, English, Phone
44444, 2, NULL, NULL, Email
变成这样:
33333, "North Carolina, New York", "English, Spanish, Portugeue", "Phone, Email"
44444, "California", "English", "Phone, Email"
我想做的是创建一个 TVF,我可以在其中指定表名并让函数返回其字段。理想情况下,就像我刚刚在上面演示的那样卷起来。
Solomon Rutzky 提供了一个解决方案(SQL Server:将表名作为参数传递给表值函数),他展示了如何使用 XML 和 CASE 语句在 TVF 中接受表名。
这是一个改编:
DECLARE @TableName sysname = 'objects'
/*
DECLARE @TableName sysname = 'columns'
DECLARE @TableName sysname = 'indexes'
*/
SELECT tab.BaseData.value(N'/row[1]/@name', N'VARCHAR(128)') AS [name],
tab.BaseData.value(N'/row[1]/@object_id', N'BIGINT') AS [object_id],
*
FROM (
SELECT CASE @TableName
WHEN N'objects' THEN (SELECT * FROM master.sys.tables FOR XML RAW, TYPE)
WHEN N'indexes' THEN (SELECT * FROM master.sys.indexes FOR XML RAW, TYPE)
WHEN N'columns' THEN (SELECT * FROM master.sys.columns FOR XML RAW, TYPE)
END AS [BaseData]
) tab;
如果我要创建一个怪物 CASE 语句并考虑所有可能的传入表名,有没有办法按序号位置(而不是像我上面那样的名称)引用列?更好的是,也可以汇总并划定它们的值(这是我的最终目标)?
先感谢您!
此外,这里是创建我的两个示例表的 DDL:
CREATE TABLE [dbo].[Person](
[PersonId] [int] NULL,
[Pos] [int] NULL,
[Hobby] [varchar](100) NULL,
[Degree] [varchar](50) NULL
)
GO
INSERT [dbo].[Person] ([PersonId], [Pos], [Hobby], [Degree]) VALUES (12345, 1, N'Basketball', N'Bachelor of Science')
GO
INSERT [dbo].[Person] ([PersonId], [Pos], [Hobby], [Degree]) VALUES (12345, 2, N'Baseball', N'Master of Science')
GO
INSERT [dbo].[Person] ([PersonId], [Pos], [Hobby], [Degree]) VALUES (12345, 3, N'Boxing', NULL)
GO
INSERT [dbo].[Person] ([PersonId], [Pos], [Hobby], [Degree]) VALUES (12345, 4, N'Tennis', NULL)
GO
INSERT [dbo].[Person] ([PersonId], [Pos], [Hobby], [Degree]) VALUES (22222, 1, N'Golf', N'Bachelor of Science')
GO
INSERT [dbo].[Person] ([PersonId], [Pos], [Hobby], [Degree]) VALUES (22222, 2, NULL, N'Master of Science')
GO
INSERT [dbo].[Person] ([PersonId], [Pos], [Hobby], [Degree]) VALUES (22222, 3, NULL, N'Doctorate')
GO
CREATE TABLE [dbo].[Client](
[ClientId] [int] NULL,
[Pos] [int] NULL,
[Location] [varchar](100) NULL,
[Language] [varchar](50) NULL,
[CommunicationType] [varchar](50) NULL
)
GO
INSERT [dbo].[Client] ([ClientId], [Pos], [Location], [Language], [CommunicationType]) VALUES (33333, 1, N'North Carolina', N'English', N'Phone')
GO
INSERT [dbo].[Client] ([ClientId], [Pos], [Location], [Language], [CommunicationType]) VALUES (33333, 2, N'New York', N'Spanish', N'Email')
GO
INSERT [dbo].[Client] ([ClientId], [Pos], [Location], [Language], [CommunicationType]) VALUES (33333, 3, NULL, N'Portuguese', NULL)
GO
INSERT [dbo].[Client] ([ClientId], [Pos], [Location], [Language], [CommunicationType]) VALUES (44444, 1, N'California', N'English', N'Phone')
GO
INSERT [dbo].[Client] ([ClientId], [Pos], [Location], [Language], [CommunicationType]) VALUES (44444, 2, NULL, NULL, N'Email')
GO
SELECT * FROM Person;
SELECT * FROM Client;
是的,但我不确定这将如何帮助你做你想做的事。您将序数位置放在谓词中,就像您已经为
row[1]
.改为
'/row[1]/@name'
获取第三列将如下所示'/row[1]/@*[3]'
。您应该知道空值不会创建任何属性,因此第三个属性中的数据并不总是来自第三列。要解决此问题,您可以为列值生成元素而不是属性,并用于
XSINIL
获取列中空值的空元素,例如:SELECT * FROM master.sys.indexes FOR XML RAW, ELEMENTS XSINIL, TYPE
. 然后您需要从 XML 中选择第三个元素而不是第三个属性'/row[1]/*[3]'
。您已经在“创建一个怪物 CASE 语句并考虑所有可能的传入表名”的路径上,所以为什么不创建一个怪物查询来代替您想要的东西,而不需要 XML 的东西。
如果您需要经常甚至自动更新函数,则可以对元表使用动态 SQL 来生成上述查询。
由于您使用的是 SQL Server 2016,因此
string_agg()
您不需要使用它for xml path
来进行连接。查询变大了,但原理相同,仍然可以使用动态 SQL 创建。您不能在此处使用动态 SQL,因为这在 TVF 中不起作用。不过,您可以使用动态生成下面的实际代码。
鉴于您在 SQL Server 2016 上,您没有
STRING_AGG
可用的,因此您将不得不使用FOR XML/STUFF
方法,这对于多列非常复杂。不必为每一列再次查询数据,也不必执行,您可以使用
APPLY
和的组合.value
重申一下,您需要替换
col1,col2
为实际的列名,与表名相同。您不能在函数中使用动态 SQL 来执行此操作。为了完整起见,我将展示
STRING_AGG
更简单的方法:我不相信使用函数可以完全按照您的意愿行事,因为它必须具有固定的输出形状(数字、类型和列名)。
一种可能的近似方法是返回固定数量的列(具有通用名称),每个列都包含字符串的聚合,对于不适用于列数少于最大值的源表的额外列,返回 null。
如其他答案中所述,
STRING_AGG
这是理想的选择,但在 SQL Server 2016 中不可用。如链接的问答中所述,SQL CLR 流式表值函数可以提供有效的替代品。现在,我知道你了'会说无论出于何种原因你都不能使用 SQL CLR,但是为了将来有类似要求的读者的利益,这里是一个示例实现。出于技术原因,代码使用环回连接,因此前几个参数指定服务器/实例名称和数据库名称。第三个参数是表格,预计包含一个整数id列,第二个位置是一个整数排序列。其余列均假定为字符串。
此示例实现仅限于五个这样的列。它对表进行一次有序传递,并通过一次仅将当前组保留在内存中来最小化内存使用量。它应该比解决方案快得多
XML PATH
。示例调用和结果
T-SQL
C# 源代码