Sql server 从SQL Server中的CSV列创建表,而不使用光标

Sql server 从SQL Server中的CSV列创建表,而不使用光标,sql-server,tsql,cursor,Sql Server,Tsql,Cursor,给出一张表格: |Name | Hobbies | ----------------------------------- |Joe | Eating,Running,Golf | |Dafydd | Swimming,Coding,Gaming | 我想将这些行拆分为: |Name | Hobby | ---------------------- |Joe | Eating | |Joe | Runnin

给出一张表格:

|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

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

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]

在数据库中创建此函数:

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 *
from @inputtable
outer apply dbo.split(',', hobbies) splithobbies
我通常更喜欢使用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

下面我们将介绍更多的技术来演示如何做到这一点。然后,您只需要使用CROSS APPLY子句来应用函数。

即使在您的示例中,每个人都有三个兴趣爱好,但我假设每个人实际上可以有一个或多个兴趣爱好?@littlebbytables是的,我应该提到兴趣爱好列表可以是任意长度的。您必须交叉应用或外部应用TVF。@Denis-谢谢Denis。忘了我在处理表函数。现在更新我的答案。谢谢,交叉申请是我想要的!我将进一步阅读SQL Server中没有本机拆分函数,这很糟糕。@Philip答案在问题的上下文中给出。您是否看到在问题的开头已经实现了一个拆分函数?所以,没关系。是正确的,外部应用是我需要知道的部分,所以这个答案是完全有效的。我只是阅读了介绍,暗示这是一个典型的基于光标的循环解决方案,看到了其他人发布的拆分函数,并假设这与SO上发布的所有其他字符串解析问题一样。唉,除非修改原来的答复,否则投票被否决或以其他方式被否决的情况是无法逆转的;如果你加一个空格,改变一些标点,我会把它倒过来。
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