与光标连接

时间:2018-03-16 18:27:50

标签: sql-server tsql cursor string-concatenation

我真的想学习并理解如何使用游标方法连接字符串。

这是我的表:

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

3 个答案:

答案 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;