2010-09-07 13 views
9

Dada una tabla:Crear una tabla de columnas CSV en SQL Server sin necesidad de utilizar un cursor

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

me gustaría dividir estas filas a cabo para obtener:

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

He completado esta por debajo (El ejemplo está listo para ejecutarse en SSMS), compre mi solución usa un cursor que creo que es feo. ¿Hay una mejor manera de hacer esto? Estoy en SQL Server 2008 R2 si hay algo nuevo que me ayude.

Gracias

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 
+0

Aunque en su ejemplo cada persona tiene exactamente tres aficiones, supongo que cada persona puede tener realmente 1 o más pasatiempos. – LittleBobbyTables

+0

@LittleBobbyTables sí, debería haber mencionado la lista de pasatiempos puede ser de longitud arbitraria – amarsuperstar

Respuesta

9

utilizar una función de análisis de secuencia como la que se encontraron here. La clave es usar CROSS APPLY para ejecutar la función para cada fila en su tabla base.

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] 
+0

editar: le di los puntos, no puedo leer veces al parecer! – amarsuperstar

2

Crear esta función en su base de datos:

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 

y luego simplemente lo haga en su instrucción Select y utilizar cruz solicitar su adhesión a la función

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

Nopers. Tienes que aplicar de forma cruzada o aplicar externamente un TVF. –

+0

@Denis - gracias Denis. olvidé que estaba lidiando con la función de mesa allí. Actualizado mi respuesta ahora. – codingbadger

+0

¡Gracias, 'cross apply' es lo que estaba buscando! Voy a leer un poco más sobre eso ahora – amarsuperstar

4

Eso sí, el siguiente :

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

No hay una función de "división" nativa en SQL Server (que apesta). –

+2

@Philip La respuesta se da en el contexto de la pregunta. ¿Viste que ya hay una función de división implementada al comienzo de la pregunta? Entonces, está bien. –

+0

es correcto, la aplicación externa es la parte que necesitaba saber, por lo que esta respuesta es perfectamente válida – amarsuperstar

0

I generalmente prefieren usar XML para dividir la lista CSV en formato de tabla de valores. Puede comprobar esta función:

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 

y la siguiente article para más técnicas que muestran cómo hacer esto. Luego, solo necesita usar la cláusula CROSS APPLY para aplicar la función.

Cuestiones relacionadas