Comment partager une chaîne de caractères pour accéder à l'élément x?

avec SQL Server, comment partager une chaîne de caractères pour accéder à l'élément x?

prenez une chaîne "Hello John Smith". Comment puis-je diviser la chaîne par espace et accéder à l'élément index 1 qui doit retourner "John"?

456
demandé sur Shnugo 2008-08-05 22:15:47

30 réponses

vous pouvez trouver la solution dans fonction définie par L'utilisateur SQL pour analyser une chaîne délimitée utile (de le projet de Code ).

vous pouvez utiliser cette logique simple:

Declare @products varchar(200) = '1|20|3|343|44|6|8765'
Declare @individual varchar(20) = null

WHILE LEN(@products) > 0
BEGIN
    IF PATINDEX('%|%', @products) > 0
    BEGIN
        SET @individual = SUBSTRING(@products,
                                    0,
                                    PATINDEX('%|%', @products))
        SELECT @individual

        SET @products = SUBSTRING(@products,
                                  LEN(@individual + '|') + 1,
                                  LEN(@products))
    END
    ELSE
    BEGIN
        SET @individual = @products
        SET @products = NULL
        SELECT @individual
    END
END
180
répondu Jonesinator 2016-12-02 02:42:41

Je ne crois pas que SQL Server ait une fonction split intégrée, donc à part UDF, la seule autre réponse que je connaisse est de détourner la fonction PARSENAME:

SELECT PARSENAME(REPLACE('Hello John Smith', ' ', '.'), 2) 

PARSENAME prend une chaîne et la divise sur le caractère de période. Il prend un nombre comme second argument, et ce nombre spécifie quel segment de la chaîne de caractères retourner (travaillant de l'arrière vers l'avant).

SELECT PARSENAME(REPLACE('Hello John Smith', ' ', '.'), 3)  --return Hello

le problème évident est lorsque la chaîne contient déjà un période. Je pense toujours qu'utiliser un UDF est le meilleur moyen...toutes les autres suggestions?

343
répondu Nathan Bedford 2015-11-02 10:05:50

tout d'abord, créer une fonction (en utilisant CTE, l'expression common table supprime la nécessité d'une table temp)

 create function dbo.SplitString 
    (
        @str nvarchar(4000), 
        @separator char(1)
    )
    returns table
    AS
    return (
        with tokens(p, a, b) AS (
            select 
                1, 
                1, 
                charindex(@separator, @str)
            union all
            select
                p + 1, 
                b + 1, 
                charindex(@separator, @str, b + 1)
            from tokens
            where b > 0
        )
        select
            p-1 zeroBasedOccurance,
            substring(
                @str, 
                a, 
                case when b > 0 then b-a ELSE 4000 end) 
            AS s
        from tokens
      )
    GO

alors, utilisez-le comme n'importe quelle table (ou modifiez-le pour s'adapter à votre proc stocké existant) comme ceci.

select s 
from dbo.SplitString('Hello John Smith', ' ')
where zeroBasedOccurance=1

mise à Jour

version précédente échouerait pour la chaîne de saisie plus de 4000 caractères. Cette version s'occupe de la limitation:

create function dbo.SplitString 
(
    @str nvarchar(max), 
    @separator char(1)
)
returns table
AS
return (
with tokens(p, a, b) AS (
    select 
        cast(1 as bigint), 
        cast(1 as bigint), 
        charindex(@separator, @str)
    union all
    select
        p + 1, 
        b + 1, 
        charindex(@separator, @str, b + 1)
    from tokens
    where b > 0
)
select
    p-1 ItemIndex,
    substring(
        @str, 
        a, 
        case when b > 0 then b-a ELSE LEN(@str) end) 
    AS s
from tokens
);

GO
L'Usage de

reste le même.

107
répondu vzczc 2016-07-07 11:07:35

la plupart des solutions utilisées ici sont des boucles ou des CTEs récursifs. Une approche basée sur les ensembles sera supérieure, je vous le promets:

CREATE FUNCTION [dbo].[SplitString]
    (
        @List NVARCHAR(MAX),
        @Delim VARCHAR(255)
    )
    RETURNS TABLE
    AS
        RETURN ( SELECT [Value] FROM 
          ( 
            SELECT 
              [Value] = LTRIM(RTRIM(SUBSTRING(@List, [Number],
              CHARINDEX(@Delim, @List + @Delim, [Number]) - [Number])))
            FROM (SELECT Number = ROW_NUMBER() OVER (ORDER BY name)
              FROM sys.all_objects) AS x
              WHERE Number <= LEN(@List)
              AND SUBSTRING(@Delim + @List, [Number], LEN(@Delim)) = @Delim
          ) AS y
        );

plus sur les fonctions split, pourquoi (et la preuve que) tandis que les boucles et les CTEs récursifs ne se dimensionnent pas, et de meilleures alternatives, si la division des chaînes provenant de la couche d'application:

http://www.sqlperformance.com/2012/07/t-sql-queries/split-strings

http://www.sqlperformance.com/2012/08/t-sql-queries/splitting-strings-now-with-less-t-sql

http://sqlblog.com/blogs/aaron_bertrand/archive/2010/07/07/splitting-a-list-of-integers-another-roundup.aspx

56
répondu Aaron Bertrand 2013-11-12 17:16:08

vous pouvez utiliser une table de nombres pour faire le parsing de chaîne.

créer une table de nombres physiques:

    create table dbo.Numbers (N int primary key);
    insert into dbo.Numbers
        select top 1000 row_number() over(order by number) from master..spt_values
    go

créer une table de test avec 1000000 lignes

    create table #yak (i int identity(1,1) primary key, array varchar(50))

    insert into #yak(array)
        select 'a,b,c' from dbo.Numbers n cross join dbo.Numbers nn
    go

créer la fonction

    create function [dbo].[ufn_ParseArray]
        (   @Input      nvarchar(4000), 
            @Delimiter  char(1) = ',',
            @BaseIdent  int
        )
    returns table as
    return  
        (   select  row_number() over (order by n asc) + (@BaseIdent - 1) [i],
                    substring(@Input, n, charindex(@Delimiter, @Input + @Delimiter, n) - n) s
            from    dbo.Numbers
            where   n <= convert(int, len(@Input)) and
                    substring(@Delimiter + @Input, n, 1) = @Delimiter
        )
    go

Usage (sorties 3mil rows en 40s sur mon ordinateur portable)

    select * 
    from #yak 
    cross apply dbo.ufn_ParseArray(array, ',', 1)

nettoyage

    drop table dbo.Numbers;
    drop function  [dbo].[ufn_ParseArray]

Performance ici n'est pas étonnant, mais appel une fonction au-dessus d'une table de million de lignes n'est pas la meilleure idée. Si j'exécutais une chaîne de caractères divisée en plusieurs lignes, j'éviterais la fonction.

37
répondu Nathan Skerl 2016-10-28 18:06:12

voici un UDF qui le fera. Il retournera une table des valeurs délimitées, n'ont pas essayé tous les scénarios sur elle, mais votre exemple fonctionne très bien.


CREATE FUNCTION SplitString 
(
    -- Add the parameters for the function here
    @myString varchar(500),
    @deliminator varchar(10)
)
RETURNS 
@ReturnTable TABLE 
(
    -- Add the column definitions for the TABLE variable here
    [id] [int] IDENTITY(1,1) NOT NULL,
    [part] [varchar](50) NULL
)
AS
BEGIN
        Declare @iSpaces int
        Declare @part varchar(50)

        --initialize spaces
        Select @iSpaces = charindex(@deliminator,@myString,0)
        While @iSpaces > 0

        Begin
            Select @part = substring(@myString,0,charindex(@deliminator,@myString,0))

            Insert Into @ReturnTable(part)
            Select @part

    Select @myString = substring(@mystring,charindex(@deliminator,@myString,0)+ len(@deliminator),len(@myString) - charindex(' ',@myString,0))


            Select @iSpaces = charindex(@deliminator,@myString,0)
        end

        If len(@myString) > 0
            Insert Into @ReturnTable
            Select @myString

    RETURN 
END
GO

vous appelleriez ça comme ça:


Select * From SplitString('Hello John Smith',' ')

modifier: solution mise à jour pour gérer les délimiteurs avec un len>1 comme dans:


select * From SplitString('Hello**John**Smith','**')
20
répondu brendan 2008-08-05 18:51:07

ici je poste un moyen simple de solution

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écutez la fonction comme ceci

  select * from dbo.split('Hello John Smith',' ')
15
répondu Sivaganesh Tamilvendhan 2013-04-03 10:08:49

Cette question est pas sur une chaîne de split approche , mais de comment obtenir le n-ième élément .

Toutes les réponses sont ici de faire une sorte de chaîne de fractionnement à l'aide de la récursivité, CTE s, de multiples CHARINDEX , REVERSE et PATINDEX , en inventant des fonctions, à l'appel de méthodes CLR, nombre de tables, de CROSS APPLY ... La plupart des réponses couvrent plusieurs lignes de code.

mais-si vous vraiment ne veulent rien de plus qu'une approche pour obtenir le nth élément - cela peut être fait en tant que véritable un-liner , aucun UDF, même pas un sous-select... Et comme un avantage supplémentaire: type sûr

obtenir la partie 2 délimitée par un espace:

DECLARE @input NVARCHAR(100)=N'part1 part2 part3';
SELECT CAST(N'<x>' + REPLACE(@input,N' ',N'</x><x>') + N'</x>' AS XML).value('/x[2]','nvarchar(max)')

bien sûr vous pouvez utiliser des variables de délimiteur, et la position (utiliser sql:column pour récupérer la position directement à partir de la valeur d'une requête):

DECLARE @dlmt NVARCHAR(10)=N' ';
DECLARE @pos INT = 2;
SELECT CAST(N'<x>' + REPLACE(@input,@dlmt,N'</x><x>') + N'</x>' AS XML).value('/x[sql:variable("@pos")][1]','nvarchar(max)')

si votre chaîne de caractères peut inclure les caractères interdits (en particulier un parmi &>< ), vous pouvez toujours le faire de cette façon. Il suffit d'utiliser FOR XML PATH sur votre chaîne de caractères pour remplacer tous les caractères interdits par la séquence d'échappement appropriée implicitement.

c'est un cas très particulier si - en plus - votre délimiteur est le point-virgule . Dans ce cas, je remplacer le délimiteur d'abord par ' # DLMT#', et le remplacer par les balises XML finalement:

SET @input=N'Some <, > and &;Other äöü@€;One more';
SET @dlmt=N';';
SELECT CAST(N'<x>' + REPLACE((SELECT REPLACE(@input,@dlmt,'#DLMT#') AS [*] FOR XML PATH('')),N'#DLMT#',N'</x><x>') + N'</x>' AS XML).value('/x[sql:variable("@pos")][1]','nvarchar(max)');

mise à jour pour SQL-Server 2016+

malheureusement, les développeurs ont oublié de retourner l'index de la pièce avec STRING_SPLIT . Mais, en utilisant SQL-Server 2016+, il y a OPENJSON .

le documentation indique clairement:

quand OPENJSON parse un JSON array, le la fonction renvoie les index des éléments du texte JSON sous forme de touches.

une chaîne comme 1,2,3 n'a besoin que de crochets: [1,2,3] .

Une chaîne de mots comme this is an example doit être ["this","is","an"," example"] .

Ce sont des opérations de chaîne très faciles. Il suffit de l'essayer:

DECLARE @str VARCHAR(100)='Hello John Smith';

SELECT [value]
FROM OPENJSON('["' + REPLACE(@str,' ','","') + '"]')
WHERE [key]=1 --zero-based!
14
répondu Shnugo 2018-09-14 07:03:22

à mon avis, vous compliquez les choses. Il suffit de créer un CLR UDF et être fait avec elle.

using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
using System.Collections.Generic;

public partial class UserDefinedFunctions {
  [SqlFunction]
  public static SqlString SearchString(string Search) {
    List<string> SearchWords = new List<string>();
    foreach (string s in Search.Split(new char[] { ' ' })) {
      if (!s.ToLower().Equals("or") && !s.ToLower().Equals("and")) {
        SearchWords.Add(s);
      }
    }

    return new SqlString(string.Join(" OR ", SearchWords.ToArray()));
  }
};
10
répondu Damon Drake 2012-07-19 21:46:38

et si on utilisait string et values() ?

DECLARE @str varchar(max)
SET @str = 'Hello John Smith'

DECLARE @separator varchar(max)
SET @separator = ' '

DECLARE @Splited TABLE(id int IDENTITY(1,1), item varchar(max))

SET @str = REPLACE(@str, @separator, '''),(''')
SET @str = 'SELECT * FROM (VALUES(''' + @str + ''')) AS V(A)' 

INSERT INTO @Splited
EXEC(@str)

SELECT * FROM @Splited

ensemble des résultats obtenus.

id  item
1   Hello
2   John
3   Smith
10
répondu Frederic 2015-05-31 10:20:43

Ce modèle fonctionne très bien et vous pouvez généraliser

Convert(xml,'<n>'+Replace(FIELD,'.','</n><n>')+'</n>').value('(/n[INDEX])','TYPE')
                          ^^^^^                                   ^^^^^     ^^^^

note zone , INDEX et TYPE .

laissez une table avec des identificateurs comme

sys.message.1234.warning.A45
sys.message.1235.error.O98
....

alors, vous pouvez écrire

SELECT Source         = q.value('(/n[1])', 'varchar(10)'),
       RecordType     = q.value('(/n[2])', 'varchar(20)'),
       RecordNumber   = q.value('(/n[3])', 'int'),
       Status         = q.value('(/n[4])', 'varchar(5)')
FROM   (
         SELECT   q = Convert(xml,'<n>'+Replace(fieldName,'.','</n><n>')+'</n>')
         FROM     some_TABLE
       ) Q

du fractionnement et de la coulée de toutes les parties.

8
répondu josejuan 2014-11-11 14:31:37

j'utilise la réponse de frederic mais cela n'a pas fonctionné dans SQL Server 2005

Je l'ai modifié et j'utilise select avec union all et ça marche

DECLARE @str varchar(max)
SET @str = 'Hello John Smith how are you'

DECLARE @separator varchar(max)
SET @separator = ' '

DECLARE @Splited table(id int IDENTITY(1,1), item varchar(max))

SET @str = REPLACE(@str, @separator, ''' UNION ALL SELECT ''')
SET @str = ' SELECT  ''' + @str + '''  ' 

INSERT INTO @Splited
EXEC(@str)

SELECT * FROM @Splited

et le résultat-ensemble est:

id  item
1   Hello
2   John
3   Smith
4   how
5   are
6   you
8
répondu angel 2015-05-31 10:26:25

je cherchais la solution sur le net et le dessous fonctionne pour moi. Ref .

et vous appelez la fonction comme ceci :

SELECT * FROM dbo.split('ram shyam hari gopal',' ')

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO

CREATE FUNCTION [dbo].[Split](@String VARCHAR(8000), @Delimiter CHAR(1))       
RETURNS @temptable TABLE (items VARCHAR(8000))       
AS       
BEGIN       
    DECLARE @idx INT       
    DECLARE @slice VARCHAR(8000)        
    SELECT @idx = 1       
    IF len(@String)<1 OR @String IS NULL  RETURN       
    WHILE @idx!= 0       
    BEGIN       
        SET @idx = charindex(@Delimiter,@String)       
        IF @idx!=0       
            SET @slice = LEFT(@String,@idx - 1)       
        ELSE       
            SET @slice = @String       
        IF(len(@slice)>0)  
            INSERT INTO @temptable(Items) VALUES(@slice)       
        SET @String = RIGHT(@String,len(@String) - @idx)       
        IF len(@String) = 0 break       
    END   
    RETURN       
END
6
répondu kta 2015-05-31 10:23:11

encore une autre partie de la chaîne par la fonction delimeter:

create function GetStringPartByDelimeter (
    @value as nvarchar(max),
    @delimeter as nvarchar(max),
    @position as int
) returns NVARCHAR(MAX) 
AS BEGIN
    declare @startPos as int
    declare @endPos as int
    set @endPos = -1
    while (@position > 0 and @endPos != 0) begin
        set @startPos = @endPos + 1
        set @endPos = charindex(@delimeter, @value, @startPos)

        if(@position = 1) begin
            if(@endPos = 0)
                set @endPos = len(@value) + 1

            return substring(@value, @startPos, @endPos - @startPos)
        end

        set @position = @position - 1
    end

    return null
end

et l'usage:

select dbo.GetStringPartByDelimeter ('a;b;c;d;e', ';', 3)

qui renvoie:

c
6
répondu Ramazan Binarbasi 2016-01-10 06:26:41

essayez ceci:

CREATE function [SplitWordList]
(
 @list varchar(8000)
)
returns @t table 
(
 Word varchar(50) not null,
 Position int identity(1,1) not null
)
as begin
  declare 
    @pos int,
    @lpos int,
    @item varchar(100),
    @ignore varchar(100),
    @dl int,
    @a1 int,
    @a2 int,
    @z1 int,
    @z2 int,
    @n1 int,
    @n2 int,
    @c varchar(1),
    @a smallint
  select 
    @a1 = ascii('a'),
    @a2 = ascii('A'),
    @z1 = ascii('z'),
    @z2 = ascii('Z'),
    @n1 = ascii('0'),
    @n2 = ascii('9')
  set @ignore = '''"'
  set @pos = 1
  set @dl = datalength(@list)
  set @lpos = 1
  set @item = ''
  while (@pos <= @dl) begin
    set @c = substring(@list, @pos, 1)
    if (@ignore not like '%' + @c + '%') begin
      set @a = ascii(@c)
      if ((@a >= @a1) and (@a <= @z1))  
        or ((@a >= @a2) and (@a <= @z2))
        or ((@a >= @n1) and (@a <= @n2))
      begin
        set @item = @item + @c
      end else if (@item > '') begin
        insert into @t values (@item)
        set @item = ''
      end
    end 
    set @pos = @pos + 1
  end
  if (@item > '') begin
    insert into @t values (@item)
  end
  return
end

Tester comme ceci:

select * from SplitWordList('Hello John Smith')
5
répondu Seibar 2008-08-05 18:41:34

l'exemple suivant utilise un CTE récursif

mise à Jour 18.09.2013

CREATE FUNCTION dbo.SplitStrings_CTE(@List nvarchar(max), @Delimiter nvarchar(1))
RETURNS @returns TABLE (val nvarchar(max), [level] int, PRIMARY KEY CLUSTERED([level]))
AS
BEGIN
;WITH cte AS
 (
  SELECT SUBSTRING(@List, 0, CHARINDEX(@Delimiter,  @List + @Delimiter)) AS val,
         CAST(STUFF(@List + @Delimiter, 1, CHARINDEX(@Delimiter, @List + @Delimiter), '') AS nvarchar(max)) AS stval, 
         1 AS [level]
  UNION ALL
  SELECT SUBSTRING(stval, 0, CHARINDEX(@Delimiter, stval)),
         CAST(STUFF(stval, 1, CHARINDEX(@Delimiter, stval), '') AS nvarchar(max)),
         [level] + 1
  FROM cte
  WHERE stval != ''
  )
  INSERT @returns
  SELECT REPLACE(val, ' ','' ) AS val, [level]
  FROM cte
  WHERE val > ''
  RETURN
END

Demo on SQLFiddle

5
répondu Aleksandr Fedorenko 2013-09-25 06:55:47

si votre base de données a un niveau de compatibilité de 130 ou plus, vous pouvez utiliser la fonction STRING_SPLIT ainsi que les clauses OFFSET FETCH pour obtenir l'élément spécifique par index.

pour obtenir l'article à index n (base zéro), vous pouvez utiliser le code suivant

SELECT value
FROM STRING_SPLIT('Hello John Smith',' ')
ORDER BY (SELECT NULL)
OFFSET N ROWS
FETCH NEXT 1 ROWS ONLY

pour vérifier le niveau de compatibilité de votre base de données , exécutez ce code:

SELECT compatibility_level  
FROM sys.databases WHERE name = 'YourDBName';
5
répondu Gorgi Rankovski 2018-08-01 15:05:13


    Alter Function dbo.fn_Split
    (
    @Expression nvarchar(max),
    @Delimiter  nvarchar(20) = ',',
    @Qualifier  char(1) = Null
    )
    RETURNS @Results TABLE (id int IDENTITY(1,1), value nvarchar(max))
    AS
    BEGIN
       /* USAGE
            Select * From dbo.fn_Split('apple pear grape banana orange honeydew cantalope 3 2 1 4', ' ', Null)
            Select * From dbo.fn_Split('1,abc,"Doe, John",4', ',', '"')
            Select * From dbo.fn_Split('Hello 0,"&""&&&&', ',', '"')
       */

       -- Declare Variables
       DECLARE
          @X     xml,
          @Temp  nvarchar(max),
          @Temp2 nvarchar(max),
          @Start int,
          @End   int

       -- HTML Encode @Expression
       Select @Expression = (Select @Expression For XML Path(''))

       -- Find all occurences of @Delimiter within @Qualifier and replace with |||***|||
       While PATINDEX('%' + @Qualifier + '%', @Expression) > 0 AND Len(IsNull(@Qualifier, '')) > 0
       BEGIN
          Select
             -- Starting character position of @Qualifier
             @Start = PATINDEX('%' + @Qualifier + '%', @Expression),
             -- @Expression starting at the @Start position
             @Temp = SubString(@Expression, @Start + 1, LEN(@Expression)-@Start+1),
             -- Next position of @Qualifier within @Expression
             @End = PATINDEX('%' + @Qualifier + '%', @Temp) - 1,
             -- The part of Expression found between the @Qualifiers
             @Temp2 = Case When @End &LT 0 Then @Temp Else Left(@Temp, @End) End,
             -- New @Expression
             @Expression = REPLACE(@Expression,
                                   @Qualifier + @Temp2 + Case When @End &LT 0 Then '' Else @Qualifier End,
                                   Replace(@Temp2, @Delimiter, '|||***|||')
                           )
       END

       -- Replace all occurences of @Delimiter within @Expression with '&lt/fn_Split&gt&ltfn_Split&gt'
       -- And convert it to XML so we can select from it
       SET
          @X = Cast('&ltfn_Split&gt' +
                    Replace(@Expression, @Delimiter, '&lt/fn_Split&gt&ltfn_Split&gt') +
                    '&lt/fn_Split&gt' as xml)

       -- Insert into our returnable table replacing '|||***|||' back to @Delimiter
       INSERT @Results
       SELECT
          "Value" = LTRIM(RTrim(Replace(C.value('.', 'nvarchar(max)'), '|||***|||', @Delimiter)))
       FROM
          @X.nodes('fn_Split') as X(C)

       -- Return our temp table
       RETURN
    END

3
répondu T-Rex 2013-11-05 00:12:17

je sais que C'est une vieille Question, mais je pense que quelqu'un peut bénéficier de ma solution.

select 
SUBSTRING(column_name,1,CHARINDEX(' ',column_name,1)-1)
,SUBSTRING(SUBSTRING(column_name,CHARINDEX(' ',column_name,1)+1,LEN(column_name))
    ,1
    ,CHARINDEX(' ',SUBSTRING(column_name,CHARINDEX(' ',column_name,1)+1,LEN(column_name)),1)-1)
,SUBSTRING(SUBSTRING(column_name,CHARINDEX(' ',column_name,1)+1,LEN(column_name))
    ,CHARINDEX(' ',SUBSTRING(column_name,CHARINDEX(' ',column_name,1)+1,LEN(column_name)),1)+1
    ,LEN(column_name))
from table_name

SQL FIDDLE

avantages:

  • il sépare tous les 3 délimiteurs de sous-chaînes par ' '.
  • on ne doit pas utiliser en boucle, car il diminue les performances.
  • pas besoin de pivoter comme toutes les sous-chaînes résultantes seront affichées en une rangée

Limitations:

  • il faut connaître le total no. des espaces (sous-chaîne).

Note : la solution peut donner une sous-chaîne allant Jusqu'à N.

pour surmonter la limitation, nous pouvons utiliser le suivant ref .

mais encore une fois le ci-dessus solution ne peut pas être utilisé dans un tableau (Actaully Je n'ai pas été en mesure de l'utiliser).

encore une fois j'espère que cette solution peut aider certains-one.

mise à jour: dans le cas d'enregistrements > 50000, il n'est pas conseillé d'utiliser LOOPS car il dégradera la Performance

2
répondu Luv 2017-05-23 12:02:53

presque toutes les autres réponses split code remplacent la chaîne en cours de split qui gaspille les cycles CPU et effectue des attributions de mémoire inutiles.

je couvre une bien meilleure façon de faire une fente de chaîne ici: http://www.digitalruby.com/split-string-sql-server /

voici le code:

SET NOCOUNT ON

-- You will want to change nvarchar(MAX) to nvarchar(50), varchar(50) or whatever matches exactly with the string column you will be searching against
DECLARE @SplitStringTable TABLE (Value nvarchar(MAX) NOT NULL)
DECLARE @StringToSplit nvarchar(MAX) = 'your|string|to|split|here'
DECLARE @SplitEndPos int
DECLARE @SplitValue nvarchar(MAX)
DECLARE @SplitDelim nvarchar(1) = '|'
DECLARE @SplitStartPos int = 1

SET @SplitEndPos = CHARINDEX(@SplitDelim, @StringToSplit, @SplitStartPos)

WHILE @SplitEndPos > 0
BEGIN
    SET @SplitValue = SUBSTRING(@StringToSplit, @SplitStartPos, (@SplitEndPos - @SplitStartPos))
    INSERT @SplitStringTable (Value) VALUES (@SplitValue)
    SET @SplitStartPos = @SplitEndPos + 1
    SET @SplitEndPos = CHARINDEX(@SplitDelim, @StringToSplit, @SplitStartPos)
END

SET @SplitValue = SUBSTRING(@StringToSplit, @SplitStartPos, 2147483647)
INSERT @SplitStringTable (Value) VALUES(@SplitValue)

SET NOCOUNT OFF

-- You can select or join with the values in @SplitStringTable at this point.
2
répondu jjxtra 2015-05-26 16:57:57

vous pouvez fendre une chaîne en SQL sans avoir besoin d'une fonction:

DECLARE @bla varchar(MAX)
SET @bla = 'BED40DFC-F468-46DD-8017-00EF2FA3E4A4,64B59FC5-3F4D-4B0E-9A48-01F3D4F220B0,A611A108-97CA-42F3-A2E1-057165339719,E72D95EA-578F-45FC-88E5-075F66FD726C'

-- /q/how-to-query-values-from-xml-nodes-36980/"Conex"<html>,Barnes & Noble,abc,def,ghi'

-- http://stackoverflow.com/questions/14712864/how-to-query-values-from-xml-nodes
SELECT 
    x.XmlCol.value('.', 'nvarchar(MAX)') AS val 
FROM 
(
    SELECT 
    CAST('<e>' + REPLACE((SELECT @bla FOR XML PATH('')), ',', '</e><e>') + '</e>' AS xml) AS RawXml
) AS b 
CROSS APPLY b.RawXml.nodes('e') x(XmlCol); 
2
répondu Stefan Steiger 2015-10-23 10:14:38

Pur à base de jeu de la solution à l'aide de TVF avec récursive CTE . Vous pouvez JOIN et APPLY cette fonction à n'importe quel ensemble de données.

create function [dbo].[SplitStringToResultSet] (@value varchar(max), @separator char(1))
returns table
as return
with r as (
    select value, cast(null as varchar(max)) [x], -1 [no] from (select rtrim(cast(@value as varchar(max))) [value]) as j
    union all
    select right(value, len(value)-case charindex(@separator, value) when 0 then len(value) else charindex(@separator, value) end) [value]
    , left(r.[value], case charindex(@separator, r.value) when 0 then len(r.value) else abs(charindex(@separator, r.[value])-1) end ) [x]
    , [no] + 1 [no]
    from r where value > '')

select ltrim(x) [value], [no] [index] from r where x is not null;
go

Utilisation:

select *
from [dbo].[SplitStringToResultSet]('Hello John Smith', ' ')
where [index] = 1;

résultat:

value   index
-------------
John    1
1
répondu Andrey Morozov 2015-01-13 06:37:07

commençant par SQL Server 2016 we string_split

DECLARE @string varchar(100) = 'Richard, Mike, Mark'

SELECT value FROM string_split(@string, ',')
1
répondu Victor Hugo Terceros 2017-09-04 21:52:57

une approche moderne utilisant STRING_SPLIT , nécessite SQL Server 2016 et au-dessus.

DECLARE @string varchar(100) = 'Hello John Smith'

SELECT
    ROW_NUMBER() OVER (ORDER BY value) AS RowNr,
    value
FROM string_split(@string, ' ')

résultat:

RowNr   value
1       Hello
2       John
3       Smith

il est maintenant possible d'obtenir le neuvième élément à partir du numéro de ligne.

1
répondu uzr 2018-01-02 15:02:56

la réponse D'Aaron Bertrand est grande, mais imparfaite. Il ne gère pas correctement un espace comme un délimiteur (comme c'était le cas dans la question originale) puisque la fonction length trace les espaces.

voici son code, avec un petit ajustement pour permettre un espace séparateur:

CREATE FUNCTION [dbo].[SplitString]
(
    @List NVARCHAR(MAX),
    @Delim VARCHAR(255)
)
RETURNS TABLE
AS
    RETURN ( SELECT [Value] FROM 
      ( 
        SELECT 
          [Value] = LTRIM(RTRIM(SUBSTRING(@List, [Number],
          CHARINDEX(@Delim, @List + @Delim, [Number]) - [Number])))
        FROM (SELECT Number = ROW_NUMBER() OVER (ORDER BY name)
          FROM sys.all_objects) AS x
          WHERE Number <= LEN(@List)
          AND SUBSTRING(@Delim + @List, [Number], LEN(@Delim+'x')-1) = @Delim
      ) AS y
    );
1
répondu zipppy 2018-03-22 14:56:57

Voici une fonction qui accomplira l'objectif de la question de diviser une chaîne et d'accéder à l'élément X:

CREATE FUNCTION [dbo].[SplitString]
(
   @List       VARCHAR(MAX),
   @Delimiter  VARCHAR(255),
   @ElementNumber INT
)
RETURNS VARCHAR(MAX)
AS
BEGIN

       DECLARE @inp VARCHAR(MAX)
       SET @inp = (SELECT REPLACE(@List,@Delimiter,'_DELMTR_') FOR XML PATH(''))

       DECLARE @xml XML
       SET @xml = '<split><el>' + REPLACE(@inp,'_DELMTR_','</el><el>') + '</el></split>'

       DECLARE @ret VARCHAR(MAX)
       SET @ret = (SELECT
              el = split.el.value('.','varchar(max)')
       FROM  @xml.nodes('/split/el[string-length(.)>0][position() = sql:variable("@elementnumber")]') split(el))

       RETURN @ret

END

Utilisation:

SELECT dbo.SplitString('Hello John Smith', ' ', 2)

résultat:

John
1
répondu VinceL 2018-04-27 20:41:00

SIMPLE SOLUTION FOR PARSING FIRST AND LAST NAME

DECLARE @Name varchar(10) = 'John Smith'

-- Get First Name
SELECT SUBSTRING(@Name, 0, (SELECT CHARINDEX(' ', @Name)))

-- Get Last Name
SELECT SUBSTRING(@Name, (SELECT CHARINDEX(' ', @Name)) + 1, LEN(@Name))

Dans mon cas (et dans beaucoup d'autres, il me semble...), J'ai une liste de noms et de prénoms séparés par un seul espace. Ceci peut être utilisé directement à l'intérieur d'une instruction select pour analyser les prénom et nom de famille.

-- i.e. Get First and Last Name from a table of Full Names
SELECT SUBSTRING(FullName, 0, (SELECT CHARINDEX(' ', FullName))) as FirstName,
SUBSTRING(FullName, (SELECT CHARINDEX(' ', FullName)) + 1, LEN(FullName)) as LastName,
From FullNameTable
1
répondu Sam K 2018-08-20 19:08:13

je sais qu'il est tard, mais j'ai récemment eu cette exigence et est venu avec le code ci-dessous. Je n'ai pas de choix d'utiliser la fonction définie par l'Utilisateur. Espérons que cette aide.

sélectionner SUBSTRING( Substrat ('Hello John Smith', 0, CHARINDEX ('', 'Hello John Smith', CHARINDEX (' ' , 'Hello John Smith')+1) ),CHARINDEX(' ','Bonjour John Smith"), LEN('Bonjour John Smith") )

1
répondu GGadde 2018-09-18 18:54:23

Eh bien, le mien n'est pas si simple, mais voici le code que j'utilise pour diviser une variable d'entrée délimitée par une virgule en valeurs individuelles, et le mettre dans une variable de table. Je suis sûr que vous pouvez modifier légèrement cela pour diviser basé sur un espace et puis de faire une requête de sélection de base contre cette variable de table pour obtenir vos résultats.

-- Create temporary table to parse the list of accounting cycles.
DECLARE @tblAccountingCycles table
(
    AccountingCycle varchar(10)
)

DECLARE @vchAccountingCycle varchar(10)
DECLARE @intPosition int

SET @vchAccountingCycleIDs = LTRIM(RTRIM(@vchAccountingCycleIDs)) + ','
SET @intPosition = CHARINDEX(',', @vchAccountingCycleIDs, 1)

IF REPLACE(@vchAccountingCycleIDs, ',', '') <> ''
BEGIN
    WHILE @intPosition > 0
    BEGIN
        SET @vchAccountingCycle = LTRIM(RTRIM(LEFT(@vchAccountingCycleIDs, @intPosition - 1)))
        IF @vchAccountingCycle <> ''
        BEGIN
            INSERT INTO @tblAccountingCycles (AccountingCycle) VALUES (@vchAccountingCycle)
        END
        SET @vchAccountingCycleIDs = RIGHT(@vchAccountingCycleIDs, LEN(@vchAccountingCycleIDs) - @intPosition)
        SET @intPosition = CHARINDEX(',', @vchAccountingCycleIDs, 1)
    END
END

le concept est à peu près le même. Une autre solution consiste à exploiter la compatibilité .NET au sein de SQL Server 2005. Vous pouvez essentiellement écrire vous-même une méthode simple dans .NET qui diviserait la chaîne et exposerait ensuite cela comme une procédure/fonction stockée.

0
répondu Dillie-O 2011-10-22 16:07:04

C'est quelque chose que j'ai fait pour obtenir un token spécifique dans une chaîne. (Testé en MSSQL 2008)

D'abord, créant les fonctions suivantes: (trouvé dans: ici

CREATE FUNCTION dbo.SplitStrings_Moden
(
   @List NVARCHAR(MAX),
   @Delimiter NVARCHAR(255)
)
RETURNS TABLE
WITH SCHEMABINDING AS
RETURN
  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),
       E2(N)        AS (SELECT 1 FROM E1 a, E1 b),
       E4(N)        AS (SELECT 1 FROM E2 a, E2 b),
       E42(N)       AS (SELECT 1 FROM E4 a, E2 b),
       cteTally(N)  AS (SELECT 0 UNION ALL SELECT TOP (DATALENGTH(ISNULL(@List,1))) 
                         ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E42),
       cteStart(N1) AS (SELECT t.N+1 FROM cteTally t
                         WHERE (SUBSTRING(@List,t.N,1) = @Delimiter OR t.N = 0))
  SELECT Item = SUBSTRING(@List, s.N1, ISNULL(NULLIF(CHARINDEX(@Delimiter,@List,s.N1),0)-s.N1,8000))
    FROM cteStart s;

et

create FUNCTION dbo.getToken
(
@List NVARCHAR(MAX),
@Delimiter NVARCHAR(255),
@Pos int
)
RETURNS varchar(max)
as 
begin
declare @returnValue varchar(max);
select @returnValue = tbl.Item from (
select ROW_NUMBER() over (order by (select null)) as id, * from dbo.SplitStrings_Moden(@List, @Delimiter)
) as tbl
where tbl.id = @Pos
return @returnValue
end

, alors vous pouvez l'utiliser comme ça:

select dbo.getToken('1111_2222_3333_', '_', 1)

qui renvoient 1111

0
répondu Rotem 2013-07-25 11:07:57