【问题标题】:Create a table from CSV columns in SQL Server without using a cursor在不使用游标的情况下从 SQL Server 中的 CSV 列创建表
【发布时间】:2011-04-09 07:29:11
【问题描述】:

给定一张桌子:

|Name    | Hobbies                |
-----------------------------------
|Joe     | Eating,Running,Golf    |
|Dafydd  | Swimming,Coding,Gaming |

我想把这些行分开得到:

|Name    | Hobby     |
----------------------
|Joe     | Eating    |
|Joe     | Running   |
|Joe     | Golf      |
|Dafydd  | Swimming  |
|Dafydd  | Coding    |
|Dafydd  | Gaming    |

我在下面完成了这个(示例已准备好在 SSMS 中运行),购买我的解决方案使用我认为丑陋的光标。有没有更好的方法来做到这一点?如果有任何对我有帮助的新内容,我正在使用 SQL Server 2008 R2。

谢谢

if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[Split]') and xtype in (N'FN', N'IF', N'TF')) drop function [dbo].Split
go
CREATE FUNCTION dbo.Split (@sep char(1), @s varchar(512))
RETURNS table
AS
RETURN (
    WITH Pieces(pn, start, stop) AS (
      SELECT 1, 1, CHARINDEX(@sep, @s)
      UNION ALL
      SELECT pn + 1, stop + 1, CHARINDEX(@sep, @s, stop + 1)
      FROM Pieces
      WHERE stop > 0
    )
    SELECT pn,
      SUBSTRING(@s, start, CASE WHEN stop > 0 THEN stop-start ELSE 512 END) AS s
    FROM Pieces
  )


go

declare @inputtable table (
    name varchar(200) not null,
    hobbies varchar(200) not null
)

declare @outputtable table (
    name varchar(200) not null,
    hobby varchar(200) not null
)

insert into @inputtable values('Joe', 'Eating,Running,Golf')
insert into @inputtable values('Dafydd', 'Swimming,Coding,Gaming')

select * from @inputtable

declare inputcursor cursor for
select name, hobbies
from @inputtable

open inputcursor

declare @name varchar(255), @hobbiescsv varchar(255)
fetch next from inputcursor into @name, @hobbiescsv
while(@@FETCH_STATUS <> -1) begin

    insert into @outputtable
    select @name, splithobbies.s
    from dbo.split(',', @hobbiescsv) splithobbies

    fetch next from inputcursor into @name, @hobbiescsv 
end
close inputcursor
deallocate inputcursor

select * from @outputtable

【问题讨论】:

  • 尽管在你的例子中每个人都有三个爱好,但我假设每个人实际上可以有一个或多个爱好?
  • @LittleBobbyTables 是的,我应该提到爱好列表可以是任意长度

标签: sql-server tsql database-cursor


【解决方案1】:

使用类似here 的字符串解析函数。关键是使用CROSS APPLY 为基表中的每一行执行函数。

CREATE FUNCTION [dbo].[fnParseStringTSQL] (@string NVARCHAR(MAX),@separator NCHAR(1))
RETURNS @parsedString TABLE (string NVARCHAR(MAX))
AS 
BEGIN
   DECLARE @position int
   SET @position = 1
   SET @string = @string + @separator
   WHILE charindex(@separator,@string,@position) <> 0
      BEGIN
         INSERT into @parsedString
         SELECT substring(@string, @position, charindex(@separator,@string,@position) - @position)
         SET @position = charindex(@separator,@string,@position) + 1
      END
     RETURN
END
go

declare @MyTable table (
    Name char(10),
    Hobbies varchar(100)
)

insert into @MyTable
    (Name, Hobbies)
    select 'Joe', 'Eating,Running,Golf'
    union all
    select 'Dafydd', 'Swimming,Coding,Gaming'

select t.Name, p.String
    from @mytable t
        cross apply dbo.fnParseStringTSQL(t.Hobbies, ',') p

DROP FUNCTION [dbo].[fnParseStringTSQL]

【讨论】:

    【解决方案2】:

    在你的数据库中创建这个函数:

    CREATE FUNCTION dbo.Split(@origString varchar(max), @Delimiter char(1))     
    returns @temptable TABLE (items varchar(max))     
    as     
    begin     
        declare @idx int     
        declare @split varchar(max)     
    
        select @idx = 1     
            if len(@origString )<1 or @origString is null  return     
    
        while @idx!= 0     
        begin     
            set @idx = charindex(@Delimiter,@origString)     
            if @idx!=0     
                set @split= left(@origString,@idx - 1)     
            else     
                set @split= @origString
    
            if(len(@split)>0)
                insert into @temptable(Items) values(@split)     
    
            set @origString= right(@origString,len(@origString) - @idx)     
            if len(@origString) = 0 break     
        end 
    return     
    end
    

    然后只需在 Select 语句中调用它并使用 cross apply 加入函数

    Select t.Name, 
           s.items as 'Hobby'
    from dbo.MyTable as t
    Cross Apply dbo.Split(t.Hobbies,',') as s 
    

    【讨论】:

    • Nopers。您必须交叉应用或外部应用 TVF。
    • @Denis - 谢谢丹尼斯。忘了我在那里处理表函数。现在更新我的答案。
    • 谢谢,cross apply 是我所追求的!现在将对此进行更多阅读
    【解决方案3】:

    只需执行以下操作:

    select *
    from @inputtable
    outer apply dbo.split(',', hobbies) splithobbies
    

    【讨论】:

    • SQL Server 中没有原生的“拆分”功能(很烂)。
    • @Philip 答案在问题的上下文中给出。您是否看到在问题的一开始就已经实现了拆分功能?所以,没关系。
    • 是正确的,外部应用是我需要知道的部分,所以这个答案是完全有效的
    • 很公平。我刚刚阅读了介绍,暗示它是一个典型的基于游标的循环解决方案,看到其他人发布的拆分功能,并假设这就像发布在 SO 上的所有其他字符串解析问题一样。唉,除非原始回复被修改,否则投票(否决或其他)不能被撤销;如果你(添加一个空格,更改一些标点符号),我会颠倒它。
    【解决方案4】:

    我通常更喜欢使用XML 将 CSV 列表拆分为表值格式。您可以检查此功能:

    CREATE FUNCTION dbo.SplitStrings_XML
    (
       @List       NVARCHAR(MAX),
       @Delimiter  NVARCHAR(255)
    )
    RETURNS TABLE
    WITH SCHEMABINDING
    AS
       RETURN 
       (  
          SELECT Item = y.i.value('(./text())[1]', 'nvarchar(4000)')
          FROM 
          ( 
            SELECT x = CONVERT(XML, '<i>' 
              + REPLACE(@List, @Delimiter, '</i><i>') 
              + '</i>').query('.')
          ) AS a CROSS APPLY x.nodes('i') AS y(i)
       );
    GO
    

    和下面的article 了解更多展示如何做到这一点的技术。然后,您只需要使用CROSS APPLY 子句来应用该功能。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-11-22
      • 1970-01-01
      • 2017-11-08
      • 2021-08-21
      • 2015-03-17
      • 1970-01-01
      • 2012-05-31
      相关资源
      最近更新 更多