用游标连接
Concatenating with Cursor
我真的很想学习和理解如何使用游标方法连接字符串。
这是我的 table:
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')
我正在尝试创建一个 table,其中每个 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
首先,对于未来的读者:正如 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值,并将游标的结果也插入到一个table变量中。
我已经编写了嵌套游标来与不同的城市 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
如果您遇到任何问题,请随时在评论部分留言。谢谢
为此使用游标可能是最慢的解决方案。如果性能很重要,那么可以采用三种有效的方法。第一种方法是 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 字符(如 &、< 或 >)时,您将返回一个 XML 实体(例如,“&”代表“& ”)。要处理这个问题,您必须将查询修改为如下所示:
示例数据
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;
我真的很想学习和理解如何使用游标方法连接字符串。
这是我的 table:
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')
我正在尝试创建一个 table,其中每个 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
首先,对于未来的读者:正如 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值,并将游标的结果也插入到一个table变量中。
我已经编写了嵌套游标来与不同的城市 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
如果您遇到任何问题,请随时在评论部分留言。谢谢
为此使用游标可能是最慢的解决方案。如果性能很重要,那么可以采用三种有效的方法。第一种方法是 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 字符(如 &、< 或 >)时,您将返回一个 XML 实体(例如,“&”代表“& ”)。要处理这个问题,您必须将查询修改为如下所示:
示例数据
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;