2009-03-30 5 views
112

Je cherche à diviser '1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 ...' (délimité par des virgules) dans une table ou variable de table.Fonction de partage équivalente en T-SQL?

Est-ce que quelqu'un a une fonction qui retourne chacun d'affilée?

+2

J'ai récemment effectué une étude mineure comparant les approches les plus communes à ce problème, qui peut être utile de lire: http://www.sqlperformance.com/2012/07/t-sql-queries/split-strings et http://www.sqlperformance.com/2012/08/t- sql-queries/splitting-strings-follow-up –

+1

duplication possible f [Séparer une chaîne dans SQL] (http://stackoverflow.com/questions/2647/split-string-in-sql) – Luv

+1

Erland Sommarskog a maintenu la réponse faisant autorité à cette question au cours des 12 dernières années: [http://www.sommarskog.se/arrays-in-sql.html](http://www.sommarskog.se/arrays-in-sql.html) Il ne vaut pas la peine de reproduire toutes les options ici sur StackOverflow, il suffit de visiter sa page et vous apprendrez tout ce que vous avez toujours voulu savoir. – Portman

Répondre

45

Voici la solution un peu à l'ancienne:

/* 
    Splits string into parts delimitered with specified character. 
*/ 
CREATE FUNCTION [dbo].[SDF_SplitString] 
(
    @sString nvarchar(2048), 
    @cDelimiter nchar(1) 
) 
RETURNS @tParts TABLE (part nvarchar(2048)) 
AS 
BEGIN 
    if @sString is null return 
    declare @iStart int, 
      @iPos int 
    if substring(@sString, 1, 1) = @cDelimiter 
    begin 
     set @iStart = 2 
     insert into @tParts 
     values(null) 
    end 
    else 
     set @iStart = 1 
    while 1=1 
    begin 
     set @iPos = charindex(@cDelimiter, @sString, @iStart) 
     if @iPos = 0 
      set @iPos = len(@sString)+1 
     if @iPos - @iStart > 0   
      insert into @tParts 
      values (substring(@sString, @iStart, @[email protected])) 
     else 
      insert into @tParts 
      values(null) 
     set @iStart = @iPos+1 
     if @iStart > len(@sString) 
      break 
    end 
    RETURN 

END 

Dans SQL Server 2008, vous pouvez obtenir le même avec le code .NET. Peut-être que cela fonctionnerait plus vite, mais cette approche est certainement plus facile à gérer.

+0

Merci, j'aimerais aussi savoir. Y a-t-il une erreur ici? J'ai écrit ce code il y a peut-être 6 ans et ça fonctionnait bien depuis quand. – XOR

+0

Je suis d'accord. C'est une très bonne solution lorsque vous ne voulez pas (ou simplement ne pouvez pas) vous impliquer dans la création de paramètres de type table, ce qui serait le cas dans mon cas. Les DBA ont verrouillé cette fonctionnalité et ne le permettent pas. Merci XOR! – dscarr

2

Je suis tenté de serrer dans ma solution préférée. La table résultante sera composée de 2 colonnes: PosIdx pour la position de l'entier trouvé; et Valeur en entier.


create function FnSplitToTableInt 
(
    @param nvarchar(4000) 
) 
returns table as 
return 
    with Numbers(Number) as 
    (
     select 1 
     union all 
     select Number + 1 from Numbers where Number < 4000 
    ), 
    Found as 
    (
     select 
      Number as PosIdx, 
      convert(int, ltrim(rtrim(convert(nvarchar(4000), 
       substring(@param, Number, 
       charindex(N',' collate Latin1_General_BIN, 
       @param + N',', Number) - Number))))) as Value 
     from 
      Numbers 
     where 
      Number <= len(@param) 
     and substring(N',' + @param, Number, 1) = N',' collate Latin1_General_BIN 
    ) 
    select 
     PosIdx, 
     case when isnumeric(Value) = 1 
      then convert(int, Value) 
      else convert(int, null) end as Value 
    from 
     Found 

Il fonctionne en utilisant le CTE récursif comme liste de positions, de 1 à 100 par défaut. Si vous avez besoin de travailler avec chaîne de plus de 100, il suffit d'appeler cette fonction en utilisant 'l'option (maxrecursion 4000)' comme ce qui suit:


select * from FnSplitToTableInt 
(
    '9, 8, 7, 6, 5, 4, 3, 2, 1, 0, ' + 
    '9, 8, 7, 6, 5, 4, 3, 2, 1, 0, ' + 
    '9, 8, 7, 6, 5, 4, 3, 2, 1, 0, ' + 
    '9, 8, 7, 6, 5, 4, 3, 2, 1, 0, ' + 
    '9, 8, 7, 6, 5, 4, 3, 2, 1, 0' 
) 
option (maxrecursion 4000) 
+2

+1 pour avoir mentionné l'option maxrecursion. De toute évidence, une récursion lourde doit être utilisée avec précaution dans un environnement de production, mais il est bon d'utiliser des CTE pour effectuer de lourdes tâches d'importation ou de conversion de données. –

43

Essayez cette

DECLARE @xml xml, @str varchar(100), @delimiter varchar(10) 
SET @str = '1,2,3,4,5,6,7,8,9,10,11,12,13,14,15' 
SET @delimiter = ',' 
SET @xml = cast(('<X>'+replace(@str, @delimiter, '</X><X>')+'</X>') as xml) 
SELECT C.value('.', 'varchar(10)') as value FROM @xml.nodes('X') as X(C) 

OU

DECLARE @str varchar(100), @delimiter varchar(10) 
SET @str = '1,2,3,4,5,6,7,8,9,10,11,12,13,14,15' 
SET @delimiter = ',' 
;WITH cte AS 
(
    SELECT 0 a, 1 b 
    UNION ALL 
    SELECT b, CHARINDEX(@delimiter, @str, b) + LEN(@delimiter) 
    FROM CTE 
    WHERE b > a 
) 
SELECT SUBSTRING(@str, a, 
CASE WHEN b > LEN(@delimiter) 
    THEN b - a - LEN(@delimiter) 
    ELSE LEN(@str) - a + 1 END) value  
FROM cte WHERE a > 0 

Beaucoup plus de façons de faire la même chose est ici How to split comma delimited string?

+8

Remarque pour tous ceux qui recherchent un séparateur de chaînes général: La première solution donnée ici n'est pas un séparateur de chaînes général - il est sûr seulement si vous êtes sûr que l'entrée ne contiendra jamais <', '> ou '&' (par exemple une entrée est une séquence d'entiers). L'un des trois caractères ci-dessus entraînera une erreur d'analyse au lieu du résultat attendu. – miroxlav

+1

Événement avec les problèmes mentionnés par miroxlav (Ce qui devrait être résolu avec un peu de réflexion), c'est certainement l'une des solutions les plus créatives que j'ai trouvées (la première)! Très agréable! –

+0

La ligne 'SELECT b, CHARINDEX (@delimiter, @str, b) + LEN (@delimiter)' devrait être 'SELECT b, CHARINDEX (@delimiter, @str, b + 1) + LEN (@delimiter)' . Le ** b + 1 ** fait une grande différence. Testé ici avec l'espace comme délimiteur, n'a pas fonctionné sans ce correctif. – JwJosefy

10

Cela ressemble beaucoup à .NET, pour ceux d'entre vous qui sont familiers avec cette fonction:

CREATE FUNCTION dbo.[String.Split] 
(
    @Text VARCHAR(MAX), 
    @Delimiter VARCHAR(100), 
    @Index INT 
) 
RETURNS VARCHAR(MAX) 
AS BEGIN 
    DECLARE @A TABLE (ID INT IDENTITY, V VARCHAR(MAX)); 
    DECLARE @R VARCHAR(MAX); 
    WITH CTE AS 
    (
    SELECT 0 A, 1 B 
    UNION ALL 
    SELECT B, CONVERT(INT,CHARINDEX(@Delimiter, @Text, B) + LEN(@Delimiter)) 
    FROM CTE 
    WHERE B > A 
    ) 
    INSERT @A(V) 
    SELECT SUBSTRING(@Text,A,CASE WHEN B > LEN(@Delimiter) THEN B-A-LEN(@Delimiter) ELSE LEN(@Text) - A + 1 END) VALUE  
    FROM CTE WHERE A >0 

    SELECT  @R 
    =   V 
    FROM  @A 
    WHERE  ID = @Index + 1 
    RETURN  @R 
END 

SELECT dbo.[String.Split]('121,2,3,0',',',1) -- gives '2' 
+0

Cela a fonctionné pour moi après avoir ajouté GO après la fin – clairestreb

0

This blog est venu avec une assez bonne solution en utilisant XML dans T-SQL.

Ceci est la fonction que je suis venu avec basé sur ce blog (type changement de dénomination sociale et le résultat fonction cast par nécessité):

SET ANSI_NULLS ON 
GO 
SET QUOTED_IDENTIFIER ON 
GO 
CREATE FUNCTION [dbo].[SplitIntoBigints] 
(@List varchar(MAX), @Splitter char) 
RETURNS TABLE 
AS 
RETURN 
(
    WITH SplittedXML AS(
     SELECT CAST('<v>' + REPLACE(@List, @Splitter, '</v><v>') + '</v>' AS XML) AS Splitted 
    ) 
    SELECT x.v.value('.', 'bigint') AS Value 
    FROM SplittedXML 
    CROSS APPLY Splitted.nodes('//v') x(v) 
) 
GO 
7

ici est la fonction split que u demandé

CREATE FUNCTION [dbo].[split](
      @delimited NVARCHAR(MAX), 
      @delimiter NVARCHAR(100) 
     ) RETURNS @t TABLE (id INT IDENTITY(1,1), val NVARCHAR(MAX)) 
     AS 
     BEGIN 
      DECLARE @xml XML 
      SET @xml = N'<t>' + REPLACE(@delimited,@delimiter,'</t><t>') + '</t>' 

      INSERT INTO @t(val) 
      SELECT r.value('.','varchar(MAX)') as item 
      FROM @xml.nodes('/t') as records(r) 
      RETURN 
     END 

exécuter la fonction comme celui-ci

select * from dbo.split('1,2,3,4,5,6,7,8,9,10,11,12,13,14,15',',') 
+1

Si simple, a travaillé comme un charme –

0
CREATE Function [dbo].[CsvToInt] (@Array varchar(4000)) 
returns @IntTable table 
(IntValue int) 
AS 
begin 
declare @separator char(1) 
set @separator = ',' 
declare @separator_position int 
declare @array_value varchar(4000) 

set @array = @array + ',' 

while patindex('%,%' , @array) <> 0 
begin 

select @separator_position = patindex('%,%' , @array) 
select @array_value = left(@array, @separator_position - 1) 

Insert @IntTable 
Values (Cast(@array_value as int)) 
select @array = stuff(@array, 1, @separator_position, '') 
end 
2

Ceci est une autre version qui n'a aucune restriction (ex: caractères spéciaux lors de l'utilisation de l'approche xml, nombre d'enregistrements dans l'approche CTE) et fonctionne beaucoup plus rapidement sur la base d'un test sur 10M + Cela pourrait aider.

Create function [dbo].[udf_split] (
    @ListString nvarchar(max), 
    @Delimiter nvarchar(1000), 
    @IncludeEmpty bit) 
Returns @ListTable TABLE (ID int, ListValue nvarchar(1000)) 
AS 
BEGIN 
    Declare @CurrentPosition int, @NextPosition int, @Item nvarchar(max), @ID int, @L int 
    Select @ID = 1, 
    @L = len(replace(@Delimiter,' ','^')), 
      @ListString = @ListString + @Delimiter, 
      @CurrentPosition = 1 
    Select @NextPosition = Charindex(@Delimiter, @ListString, @CurrentPosition) 
    While @NextPosition > 0 Begin 
    Set @Item = LTRIM(RTRIM(SUBSTRING(@ListString, @CurrentPosition, @[email protected]))) 
    If  @IncludeEmpty=1 or LEN(@Item)>0 Begin 
    Insert Into @ListTable (ID, ListValue) Values (@ID, @Item) 
    Set @ID = @ID+1 
    End 
    Set @CurrentPosition = @[email protected] 
    Set @NextPosition = Charindex(@Delimiter, @ListString, @CurrentPosition) 
    End 
    RETURN 
END 
1
/* *Object: UserDefinedFunction [dbo].[Split] Script Date: 10/04/2013 18:18:38* */ 
SET ANSI_NULLS ON 
GO 
SET QUOTED_IDENTIFIER ON 
GO 
ALTER FUNCTION [dbo].[Split] 
(@List varchar(8000),@SplitOn Nvarchar(5)) 
RETURNS @RtnValue table 
(Id int identity(1,1),Value nvarchar(100)) 
AS 
BEGIN 
    Set @List = Replace(@List,'''','') 
    While (Charindex(@SplitOn,@List)>0) 
    Begin 

    Insert Into @RtnValue (value) 
    Select 
    Value = ltrim(rtrim(Substring(@List,1,Charindex(@SplitOn,@List)-1))) 

    Set @List = Substring(@List,Charindex(@SplitOn,@List)+len(@SplitOn),len(@List)) 
    End 

    Insert Into @RtnValue (Value) 
    Select Value = ltrim(rtrim(@List)) 

    Return 
END 
go 

Select * 
From [Clv].[Split] ('1,2,3,3,3,3,',',') 
GO 
4
DECLARE 
    @InputString NVARCHAR(MAX) = 'token1,token2,token3,token4,token5' 
    , @delimiter varchar(10) = ',' 

DECLARE @xml AS XML = CAST(('<X>'+REPLACE(@InputString,@delimiter ,'</X><X>')+'</X>') AS XML) 
SELECT C.value('.', 'varchar(10)') AS value 
FROM @xml.nodes('X') as X(C) 

Source de cette réponse: http://sqlhint.com/sqlserver/how-to/best-split-function-tsql-delimited

+0

Alors que cela peut théoriquement répondre à la question, [il serait préférable] (http://meta.stackexchange.com/q/8259) pour inclure les parties essentielles de la réponse ici, et fournir le lien pour référence. –

+1

@Xavi: ok, j'ai inclus les parties essentielles de la réponse. Merci pour votre indice. –

2
CREATE FUNCTION Split 
(
    @delimited nvarchar(max), 
    @delimiter nvarchar(100) 
) RETURNS @t TABLE 
(
-- Id column can be commented out, not required for sql splitting string 
    id int identity(1,1), -- I use this column for numbering splitted parts 
    val nvarchar(max) 
) 
AS 
BEGIN 
    declare @xml xml 
    set @xml = N'<root><r>' + replace(@delimited,@delimiter,'</r><r>') + '</r></root>' 

    insert into @t(val) 
    select 
    r.value('.','varchar(max)') as item 
    from @xml.nodes('//root/r') as records(r) 

    RETURN 
END 
GO 

utilisation

Select * from dbo.Split(N'1,2,3,4,6',',') 
17

Vous avez balisé ce SQL Server 2008, mais les futurs visiteurs à cette question (en utilisant SQL Server 2016+) voudront probablement savoir sur STRING_SPLIT.

Avec cette nouvelle fonction intégrée, vous pouvez maintenant utiliser simplement

SELECT TRY_CAST(value AS INT) 
FROM STRING_SPLIT ('1,2,3,4,5,6,7,8,9,10,11,12,13,14,15', ',') 

Certaines restrictions de cette fonction et des résultats prometteurs des tests de performance sont en this blog post by Aaron Bertrand.

0

en utilisant le tableau de pointage ici est une fonction de chaîne de split (meilleure approche possible) par Jeff Moden

CREATE FUNCTION [dbo].[DelimitedSplit8K] 
     (@pString VARCHAR(8000), @pDelimiter CHAR(1)) 
RETURNS TABLE WITH SCHEMABINDING AS 
RETURN 
--===== "Inline" CTE Driven "Tally Table" produces values from 0 up to 10,000... 
    -- enough to cover NVARCHAR(4000) 
    WITH E1(N) AS (
       SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL 
       SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL 
       SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 
       ),       --10E+1 or 10 rows 
     E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows 
     E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max 
cteTally(N) AS (--==== This provides the "base" CTE and limits the number of rows right up front 
        -- for both a performance gain and prevention of accidental "overruns" 
       SELECT TOP (ISNULL(DATALENGTH(@pString),0)) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4 
       ), 
cteStart(N1) AS (--==== This returns N+1 (starting position of each "element" just once for each delimiter) 
       SELECT 1 UNION ALL 
       SELECT t.N+1 FROM cteTally t WHERE SUBSTRING(@pString,t.N,1) = @pDelimiter 
       ), 
cteLen(N1,L1) AS(--==== Return start and length (for use in substring) 
       SELECT s.N1, 
         ISNULL(NULLIF(CHARINDEX(@pDelimiter,@pString,s.N1),0)-s.N1,8000) 
        FROM cteStart s 
       ) 
--===== Do the actual split. The ISNULL/NULLIF combo handles the length for the final element when no delimiter is found. 
SELECT ItemNumber = ROW_NUMBER() OVER(ORDER BY l.N1), 
     Item  = SUBSTRING(@pString, l.N1, l.L1) 
    FROM cteLen l 
; 

Référé de Tally OH! An Improved SQL 8K “CSV Splitter” Function

Questions connexes