用游标连接

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;