如何创建 SQL Server 函数来“连接” 将子查询中的多行放入单个分隔字段中?
为了说明这一点,假设我有两个表,如下所示:
VehicleID Name
1 Chuck
2 Larry
LocationID VehicleID City
1 1 New York
2 1 Seattle
3 1 Vancouver
4 2 Los Angeles
5 2 Houston
我想编写一个查询来返回以下结果:
VehicleID Name Locations
1 Chuck New York, Seattle, Vancouver
2 Larry Los Angeles, Houston
我知道这可以使用服务器端游标来完成,即:
DECLARE @VehicleID int
DECLARE @VehicleName varchar(100)
DECLARE @LocationCity varchar(100)
DECLARE @Locations varchar(4000)
DECLARE @Results TABLE
(
VehicleID int
Name varchar(100)
Locations varchar(4000)
)
DECLARE VehiclesCursor CURSOR FOR
SELECT
[VehicleID]
, [Name]
FROM [Vehicles]
OPEN VehiclesCursor
FETCH NEXT FROM VehiclesCursor INTO
@VehicleID
, @VehicleName
WHILE @@FETCH_STATUS = 0
BEGIN
SET @Locations = ''
DECLARE LocationsCursor CURSOR FOR
SELECT
[City]
FROM [Locations]
WHERE [VehicleID] = @VehicleID
OPEN LocationsCursor
FETCH NEXT FROM LocationsCursor INTO
@LocationCity
WHILE @@FETCH_STATUS = 0
BEGIN
SET @Locations = @Locations + @LocationCity
FETCH NEXT FROM LocationsCursor INTO
@LocationCity
END
CLOSE LocationsCursor
DEALLOCATE LocationsCursor
INSERT INTO @Results (VehicleID, Name, Locations) SELECT @VehicleID, @Name, @Locations
END
CLOSE VehiclesCursor
DEALLOCATE VehiclesCursor
SELECT * FROM @Results
但是,正如您所看到的,这需要大量的工作的代码。 我想要的是一个通用函数,它允许我做这样的事情:
SELECT VehicleID
, Name
, JOIN(SELECT City FROM Locations WHERE VehicleID = Vehicles.VehicleID, ', ') AS Locations
FROM Vehicles
这可能吗? 或者类似的东西?
To illustrate, assume that I have two tables as follows:
VehicleID Name
1 Chuck
2 Larry
LocationID VehicleID City
1 1 New York
2 1 Seattle
3 1 Vancouver
4 2 Los Angeles
5 2 Houston
I want to write a query to return the following results:
VehicleID Name Locations
1 Chuck New York, Seattle, Vancouver
2 Larry Los Angeles, Houston
I know that this can be done using server side cursors, ie:
DECLARE @VehicleID int
DECLARE @VehicleName varchar(100)
DECLARE @LocationCity varchar(100)
DECLARE @Locations varchar(4000)
DECLARE @Results TABLE
(
VehicleID int
Name varchar(100)
Locations varchar(4000)
)
DECLARE VehiclesCursor CURSOR FOR
SELECT
[VehicleID]
, [Name]
FROM [Vehicles]
OPEN VehiclesCursor
FETCH NEXT FROM VehiclesCursor INTO
@VehicleID
, @VehicleName
WHILE @@FETCH_STATUS = 0
BEGIN
SET @Locations = ''
DECLARE LocationsCursor CURSOR FOR
SELECT
[City]
FROM [Locations]
WHERE [VehicleID] = @VehicleID
OPEN LocationsCursor
FETCH NEXT FROM LocationsCursor INTO
@LocationCity
WHILE @@FETCH_STATUS = 0
BEGIN
SET @Locations = @Locations + @LocationCity
FETCH NEXT FROM LocationsCursor INTO
@LocationCity
END
CLOSE LocationsCursor
DEALLOCATE LocationsCursor
INSERT INTO @Results (VehicleID, Name, Locations) SELECT @VehicleID, @Name, @Locations
END
CLOSE VehiclesCursor
DEALLOCATE VehiclesCursor
SELECT * FROM @Results
However, as you can see, this requires a great deal of code. What I would like is a generic function that would allow me to do something like this:
SELECT VehicleID
, Name
, JOIN(SELECT City FROM Locations WHERE VehicleID = Vehicles.VehicleID, ', ') AS Locations
FROM Vehicles
Is this possible? Or something similar?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(13)
请注意,Matt 的代码 会导致字符串末尾出现一个额外的逗号; 使用 COALESCE(或 ISNULL)(如 Lance 帖子中的链接所示)使用类似的方法,但不会留下额外的逗号需要删除。 为了完整起见,以下是 sqlteam.com 上 Lance 链接中的相关代码:
Note that Matt's code will result in an extra comma at the end of the string; using COALESCE (or ISNULL for that matter) as shown in the link in Lance's post uses a similar method but doesn't leave you with an extra comma to remove. For the sake of completeness, here's the relevant code from Lance's link on sqlteam.com:
如果您使用的是 SQL Server 2005,则可以使用 FOR XML PATH 命令。
它比使用光标容易得多,而且似乎工作得相当好。
更新
对于仍在较新版本的 SQL Server 中使用此方法的任何人,还有另一种方法,使用
< code>STRING_AGG 方法自 SQL Server 2017 起可用。
这还允许将不同的分隔符指定为第二个参数,比前一种方法提供了更多的灵活性。
If you're using SQL Server 2005, you could use the FOR XML PATH command.
It's a lot easier than using a cursor, and seems to work fairly well.
Update
For anyone still using this method with newer versions of SQL Server, there is another way of doing it which is a bit easier and more performant using the
STRING_AGG
method that has been available since SQL Server 2017.This also allows a different separator to be specified as the second parameter, providing a little more flexibility over the former method.
据我所知,如果您还想像OP那样选择其他列(我猜大多数人都会这样做),那么
FOR XML
(如之前发布的)是唯一的方法。使用
COALESCE(@var...
不允许包含其他列。更新:
感谢 programmingsolutions.net 有一种方法可以删除“尾随”逗号。
通过将其设置为前导逗号并使用 MSSQL 的
STUFF
函数,您可以将第一个字符(前导逗号)替换为空字符串,如下所示:From what I can see
FOR XML
(as posted earlier) is the only way to do it if you want to also select other columns (which I'd guess most would) as the OP does.Using
COALESCE(@var...
does not allow inclusion of other columns.Update:
Thanks to programmingsolutions.net there is a way to remove the "trailing" comma to.
By making it into a leading comma and using the
STUFF
function of MSSQL you can replace the first character (leading comma) with an empty string as below:我不相信有一种方法可以在一个查询中做到这一点,但是您可以使用临时变量来玩这样的技巧:
它肯定比遍历光标更少的代码,而且可能更有效。
I don't belive there's a way to do it within one query, but you can play tricks like this with a temporary variable:
It's definitely less code than walking over a cursor, and probably more efficient.
在单个 SQL 查询中,无需使用 FOR XML 子句。
公共表表达式用于递归连接结果。
In a single SQL query, without using the FOR XML clause.
A Common Table Expression is used to recursively concatenate the results.
在 SQL Server 2005 中,
在 SQL Server 2016 中,
您可以使用 FOR JSON 语法
即
结果将变成
这样即使您的数据包含无效的 XML 字符,
'"},{"":"' 也是安全的,因为如果您的数据包含 '"},{"":"',它将被转义为"},{\"_\":\"
您可以将 ', ' 替换为任何字符串分隔
符 在 SQL Server 2017、Azure SQL 数据库中
您可以使用新的 STRING_AGG 函数
In SQL Server 2005
In SQL Server 2016
you can use the FOR JSON syntax
i.e.
And the result will become
This will work even your data contains invalid XML characters
the '"},{"":"' is safe because if you data contain '"},{"":"', it will be escaped to "},{\"_\":\"
You can replace ', ' with any string separator
And in SQL Server 2017, Azure SQL Database
You can use the new STRING_AGG function
以下代码适用于 Sql Server 2000/2005/2008
The below code will work for Sql Server 2000/2005/2008
Mun 的答案对我不起作用,所以我对该答案做了一些更改以使其发挥作用。 希望这对某人有帮助。
使用 SQL Server 2012:
Mun's answer didn't work for me so I made some changes to that answer to get it to work. Hope this helps someone.
Using SQL Server 2012:
我通过创建以下函数找到了解决方案:
用法:
I've found a solution by creating the following function:
Usage:
版本注意:对于此解决方案,您必须使用 SQL Server 2005 或更高版本,并且兼容性级别设置为 90 或更高版本。
请参阅此MSDN 文章,了解创建连接一组字符串的用户定义聚合函数的第一个示例从表中的列中获取的值。
我的谦虚建议是省略附加的逗号,以便您可以使用自己的临时分隔符(如果有)。
参考示例 1 的 C# 版本:
这样,
当您使用自定义聚合时,您可以选择使用自己的分隔符,或者根本不使用分隔符,例如:
注意: 注意数量您尝试在聚合中处理的数据。 如果您尝试连接数千行或许多非常大的数据类型,您可能会收到 .NET Framework 错误,指出“缓冲区不足”。
VERSION NOTE: You must be using SQL Server 2005 or greater with Compatibility Level set to 90 or greater for this solution.
See this MSDN article for the first example of creating a user-defined aggregate function that concatenates a set of string values taken from a column in a table.
My humble recommendation would be to leave out the appended comma so you can use your own ad-hoc delimiter, if any.
Referring to the C# version of Example 1:
And
That way when you use your custom aggregate, you can opt to use your own delimiter, or none at all, such as:
NOTE: Be careful about the amount of the data you attempt to process in your aggregate. If you try to concatenate thousands of rows or many very large datatypes you may get a .NET Framework error stating "[t]he buffer is insufficient."
对于其他答案,阅读答案的人必须了解车辆表并创建车辆表和数据来测试解决方案。
下面是使用 SQL Server“Information_Schema.Columns”表的示例。 通过使用此解决方案,无需创建表或添加数据。 此示例为数据库中的所有表创建一个以逗号分隔的列名称列表。
With the other answers, the person reading the answer must be aware of the vehicle table and create the vehicle table and data to test a solution.
Below is an example that uses SQL Server "Information_Schema.Columns" table. By using this solution, no tables need to be created or data added. This example creates a comma separated list of column names for all tables in the database.
尝试这个查询
Try this query
如果您运行的是 SQL Server 2005,则可以编写 自定义 CLR 聚合函数 来处理此问题。
C# 版本:
If you're running SQL Server 2005, you can write a custom CLR aggregate function to handle this.
C# version: