2010-09-07 38 views
9

dato un tavolo:Creare una tabella da colonne CSV in SQL Server senza l'utilizzo di un cursore

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

Vorrei dividere queste righe fuori per ottenere:

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

Ho completato questo qui sotto (esempio è pronto per l'esecuzione in SSMS), acquistare la mia soluzione utilizza un cursore che penso sia brutto. C'è un modo migliore per farlo? Sono su SQL Server 2008 R2 se c'è qualcosa di nuovo che mi aiuterà.

Grazie

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

Anche se nel tuo esempio ogni persona ha esattamente tre hobby, presumo che ogni persona possa effettivamente avere 1 o più hobby? – LittleBobbyTables

+0

@LittleBobbyTables sì, avrei dovuto menzionare la lista degli hobby può essere di lunghezza arbitraria – amarsuperstar

risposta

9

Utilizzare una funzione di stringa di analisi come quello trovato here. La chiave è usare CROSS APPLY per eseguire la funzione per ogni riga nella tabella di 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

modifica: ti ha dato i punti, non riesco a leggere i tempi apparentemente! – amarsuperstar

2

Creare questa funzione nel vostro DB:

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 

e poi semplicemente chiamare nel vostro Select e utilizzare croce chiedere di aderire alla funzione

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

Nopers. Devi attraversare applicare o esterno applicare un TVF. –

+0

@Denis - grazie Denis. Dimenticavo che avevo a che fare con la funzione Tabella. Aggiornato la mia risposta ora. – codingbadger

+0

Grazie, "cross apply" è quello che cercavo! Ora leggerò ancora qualcosa su questo ora – amarsuperstar

4

Basta procedere come segue :

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

Non c'è alcuna funzione "split" nativa in SQL Server (che fa schifo). –

+2

@Philip La risposta è data nel contesto della domanda. Hai visto che c'è già una funzione di splitting implementata all'inizio della domanda? Quindi, va bene. –

+0

è corretto, l'esterno applica è la parte che dovevo sapere quindi questa risposta è perfettamente valida – amarsuperstar

0

I generalmente preferisco usare XML per dividere l'elenco CSV nel formato di tabella. È possibile controllare questa funzione:

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 

e la seguente article per ulteriori tecniche che mostrano come fare questo. Quindi, è sufficiente utilizzare la clausola CROSS APPLY per applicare la funzione.

Problemi correlati