我真的想学习并理解如何使用游标方法连接字符串。
这是我的表:
declare @t table (id int, city varchar(15))
insert into @t values
(1, 'Rome')
,(1, 'Dallas')
,(2, 'Berlin')
,(2, 'Rome')
,(2, 'Tokyo')
,(3, 'Miami')
,(3, 'Bergen')
我正在尝试创建一个表,其中一行中每个ID的所有城市按字母顺序排序。
ID City
1 Dallas, Rome
2 Berlin, Rome, Tokyo
3 Bergen, Miami
到目前为止,这是我的代码,但它不起作用,如果有人能指导我完成每一步,我会非常高兴并渴望学习它!
set nocount on
declare @tid int
declare @tcity varchar(15)
declare CityCursor CURSOR FOR
select * from @t
order by id, city
open CityCursor
fetch next from CityCursor into @tid, @tcity
while ( @@FETCH_STATUS = 0)
begin
if @tid = @tid -- my idea add all cities in one line within each id
print cast(@tid as varchar(2)) + ', '+ @tcity
else if @tid <> @tid --when it reaches a new id and we went through all cities it starts over for the next line
fetch next from CityCursor into @tid, @tcity
end
close CityCursor
deallocate CityCursor
select * from CityCursor
答案 0 :(得分:2)
首先,对于未来的读者:正如Sean Lange在评论中所写的那样,光标是这项工作的错误工具。正确的方法是使用带有for xml
的子查询。
但是,既然您想知道如何使用光标,那么您实际上非常接近。这是一个有效的例子:
set nocount on
declare @prevId int,
@tid int,
@tcity varchar(15)
declare @cursorResult table (id int, city varchar(32))
-- if you are expecting more than two cities for the same id,
-- the city column should be longer
declare CityCursor CURSOR FOR
select * from @t
order by id, city
open CityCursor
fetch next from CityCursor into @tid, @tcity
while ( @@FETCH_STATUS = 0)
begin
if @prevId is null or @prevId != @tid
insert into @cursorResult(id, city) values (@tid, @tcity)
else
update @cursorResult
set city = city +', '+ @tcity
where id = @tid
set @prevId = @tid
fetch next from CityCursor into @tid, @tcity
end
close CityCursor
deallocate CityCursor
select * from @cursorResult
结果:
id city
1 Dallas, Rome
2 Berlin, Rome, Tokyo
3 Bergen, Miami
我使用了另一个变量来保存以前的id值,并将游标的结果插入到表变量中。
答案 1 :(得分:1)
我已编写嵌套游标以与不同的城市ID同步。虽然它有性能问题,但您可以尝试以下过程
CREATE PROCEDURE USP_CITY
AS
BEGIN
set nocount on
declare @mastertid int
declare @detailstid int
declare @tcity varchar(MAX)
declare @finalCity varchar(MAX)
SET @finalCity = ''
declare @t table (id int, city varchar(max))
insert into @t values
(1, 'Rome')
,(1, 'Dallas')
,(2, 'Berlin')
,(2, 'Rome')
,(2, 'Tokyo')
,(3, 'Miami')
,(3, 'Bergen')
declare @finaltable table (id int, city varchar(max))
declare MasterCityCursor CURSOR FOR
select distinct id from @t
order by id
open MasterCityCursor
fetch next from MasterCityCursor into @mastertid
while ( @@FETCH_STATUS = 0)
begin
declare DetailsCityCursor CURSOR FOR
SELECT id,city from @t order by id
open DetailsCityCursor
fetch next from DetailsCityCursor into @detailstid,@tcity
while ( @@FETCH_STATUS = 0)
begin
if @mastertid = @detailstid
begin
SET @finalCity = @finalCity + CASE @finalCity WHEN '' THEN +'' ELSE ', ' END + @tcity
end
fetch next from DetailsCityCursor into @detailstid, @tcity
end
insert into @finaltable values(@mastertid,@finalCity)
SET @finalCity = ''
close DetailsCityCursor
deallocate DetailsCityCursor
fetch next from MasterCityCursor into @mastertid
end
close MasterCityCursor
deallocate MasterCityCursor
SELECT * FROM @finaltable
END
如果您遇到任何问题,请随时写评论部分。感谢
答案 2 :(得分:1)
使用游标可能是最慢的解决方案。如果表现很重要,那么有三种有效的方法。第一种方法是FOR XML,没有特殊的XML字符保护。
declare @t table (id int, city varchar(15))
insert into @t values (1, 'Rome'),(1, 'Dallas'),(2, 'Berlin'),(2, 'Rome'),(2, 'Tokyo'),
(3, 'Miami'),(3, 'Bergen');
SELECT
t.id,
city = STUFF((
SELECT ',' + t2.city
FROM @t t2
WHERE t.id = t2.id
FOR XML PATH('')),1,1,'')
FROM @t as t
GROUP BY t.id;
这种方法的缺点是当你添加一个保留的XML字符,例如&amp;,&lt;或&gt;时,你会得到一个XML实体(例如“&amp; amp”代表“&amp;”)。要处理这个问题,你必须修改你的查询,如下所示:
示例数据
IF OBJECT_ID('tempdb..#t') IS NOT NULL DROP TABLE #t;
CREATE TABLE #t (id int, words varchar(20))
INSERT #t VALUES (1, 'blah blah'),(1, 'yada yada'),(2, 'PB&J'),(2,' is good');
SELECT
t.id,
city = STUFF((
SELECT ',' + t2.words
FROM #t t2
WHERE t.id = t2.id
FOR XML PATH(''), TYPE).value('.','varchar(1000)'),1,1,'')
FROM #t as t
GROUP BY t.id;
这种方法的缺点是速度会慢一些。好消息(以及这种方法比游标好100倍的另一个原因)是当优化器选择并行执行计划时,这两个查询都会受益匪浅。
最好的方法是SQL Server 2017中提供的新功能STRING_AGG。 STRING_AGG没有特殊XML字符的问题,并且是迄今为止最干净的方法:
SELECT t.id, STRING_AGG(t.words,',') WITHIN GROUP (ORDER BY t.id)
FROM #t as t
GROUP BY t.id;