Comment diviser une chaîne pour pouvoir accéder à l'élément x?


493

À l'aide de SQL Server, comment fractionner une chaîne pour pouvoir accéder à l'élément x?

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



5
intégré à partir du serveur sql 2016 msdn.microsoft.com/en-us/library/mt684588.aspx
Tim Abell

4
Les réponses les plus élevées ici sont - du moins pour moi - assez démodées et plutôt dépassées. Localisation de procédure, boucles, récursions, CLR, fonctions, nombreuses lignes de code ... Il pourrait être intéressant de lire les réponses "actives" pour trouver des approches plus à jour .
Shnugo

J'ai ajouté une nouvelle réponse avec une approche plus à jour: stackoverflow.com/a/49669994/632604
Gorgi Rankovski

Essayez Obtenez le nième élément d'une liste -> portosql.wordpress.com/2019/05/27/enesimo-elemento-lista
José Diz

Réponses:


191

Vous pouvez trouver la solution dans la fonction définie par l'utilisateur SQL pour analyser une chaîne délimitée utile (à partir du 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

1
pourquoi SET @p_SourceText = RTRIM( LTRIM( @p_SourceText)) SET @w_Length = DATALENGTH( RTRIM( LTRIM( @p_SourceText)))et non SET @p_SourceText = RTRIM( LTRIM( @p_SourceText)) SET @w_Length = DATALENGTH( @p_SourceText)?
Beth

12
@GateKiller Cette solution ne prend pas en charge Unicode et utilise un code numérique codé en dur (18,3), ce qui n'en fait pas une fonction "réutilisable" viable.
Filip De Vos

4
Cela fonctionne mais alloue beaucoup de mémoire et gaspille le processeur.
jjxtra

2
Depuis SQL Server 2016, il existe désormais une fonction intégrée STRING_SPLITqui fractionnera une chaîne et renverra un résultat de table à une colonne que vous pouvez utiliser dans une SELECTinstruction ou ailleurs.
qJake

Dommage que les gars pour lesquels je travaille ne soient pas en 2016. Mais, je garderai cela à l'esprit au cas où ils retireraient la tête de leurs chaussures. Grande solution dans l'intervalle. Je l'ai implémenté en tant que fonction et j'ai ajouté un délimiteur comme argument.
Brandon Griffin

355

Je ne crois pas que SQL Server possède une fonction de division intégrée, donc à part un 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 point. Il prend un nombre comme deuxième argument, et ce nombre spécifie le segment de la chaîne à retourner (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 point. Je pense toujours que l'utilisation d'un UDF est la meilleure façon ... d'autres suggestions?


102
Merci Saul ... Je dois souligner que cette solution est vraiment une mauvaise solution pour un vrai développement. PARSENAME n'attend que quatre parties, donc l'utilisation d'une chaîne avec plus de quatre parties la renvoie NULL. Les solutions UDF sont évidemment meilleures.
Nathan Bedford

33
C'est un excellent hack, et cela me fait aussi pleurer que quelque chose comme ça est nécessaire pour quelque chose de si simple dans des langues réelles.
Factor Mystic

36
Pour que les index fonctionnent correctement, c'est-à-dire qu'à partir de 1, j'ai détourné votre détournement avec REVERSE: REVERSE (PARSENAME (REPLACE (REVERSE ('Hello John Smith'), '', '.')) , 1)) - Retourne Hello
NothingsImpossible

3
@FactorMystic First Normal Form requiert que vous ne mettiez pas plusieurs valeurs dans un seul champ. C'est littéralement la première règle d'un SGBDR. Une SPLIT()fonction n'est pas fournie car elle encourage une mauvaise conception de la base de données et la base de données ne sera jamais optimisée pour utiliser les données stockées dans ce format. Le SGBDR n'est pas obligé d'aider les développeurs faire des choses stupides qu'il a été conçu non à manipuler. La bonne réponse sera toujours "Normalisez votre base de données comme nous vous l'avions dit il y a 40 ans". Ni SQL ni le SGBDR ne sont à blâmer pour une mauvaise conception.
Bacon Bits

8
@BaconBits, bien que je sois d'accord en théorie, dans la pratique, des outils comme celui-ci sont utiles pour normaliser un mauvais design produit par quelqu'un qui vous a précédé.
Tim Abell

110

Tout d'abord, créez une fonction (en utilisant CTE, l'expression de table commune élimine le besoin d'une table temporaire)

 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

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

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

Mise à jour

La version précédente échouait pour une chaîne d'entrée supérieure à 4 000 caractères. Cette version prend en charge 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'utilisation reste la même.


14
Il est élégant mais ne fonctionne que pour 100 éléments en raison de la limite de profondeur de récursivité.
Pking

4
@Pking, non, la valeur par défaut est 100(pour éviter une boucle infinie). Utilisez l' indication MAXRECURSION pour définir le nombre de niveaux de récursivité ( 0à 32767, 0est "pas de limite" - peut écraser le serveur). BTW, bien meilleure réponse que PARSENAMEparce que c'est universel :-). +1
Michał Powaga

En ajoutant maxrecursionà cette solution, gardez à l'esprit cette question et ses réponses Comment configurer l' maxrecursionoption pour un CTE à l'intérieur d'une fonction table .
Michał Powaga

Plus précisément, référencez la réponse de Crisfole - sa méthode la ralentit quelque peu, mais est plus simple que la plupart des autres options.
AHiggins

point mineur mais l'utilisation ne reste pas la même parce que vous avez changé le nom de la colonne, elle sn'est donc plus définie
Tim Abell

62

La plupart des solutions utilisées ici utilisent des boucles while ou des CTE récursifs. Une approche basée sur un ensemble sera supérieure, je le promets, si vous pouvez utiliser un délimiteur autre qu'un espace:

CREATE FUNCTION [dbo].[SplitString]
    (
        @List NVARCHAR(MAX),
        @Delim VARCHAR(255)
    )
    RETURNS TABLE
    AS
        RETURN ( SELECT [Value], idx = RANK() OVER (ORDER BY n) FROM 
          ( 
            SELECT n = Number, 
              [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
        );

Exemple d'utilisation:

SELECT Value FROM dbo.SplitString('foo,bar,blat,foo,splunge',',')
  WHERE idx = 3;

Résultats:

----
blat

Vous pouvez également ajouter l' idxargument souhaité comme argument à la fonction, mais je laisse cela comme exercice au lecteur.

Vous ne pouvez pas le faire avec uniquement la fonction nativeSTRING_SPLIT ajoutée dans SQL Server 2016, car il n'y a aucune garantie que le résultat sera rendu dans l'ordre de la liste d'origine. En d'autres termes, si vous réussissez, 3,6,1le résultat sera probablement dans cet ordre, mais il pourrait l' être 1,3,6. J'ai demandé l'aide de la communauté pour améliorer la fonction intégrée ici:

Avec suffisamment de commentaires qualitatifs , ils peuvent en fait envisager d'apporter certaines de ces améliorations:

Plus d'informations sur les fonctions de fractionnement, pourquoi (et preuve que) alors que les boucles et les CTE récursifs ne sont pas mis à l'échelle, et de meilleures alternatives, si le fractionnement des chaînes provenant de la couche d'application:

Sur SQL Server 2016 ou supérieur, cependant, vous devriez regarder STRING_SPLIT()et STRING_AGG():


1
Meilleure réponse, à mon humble avis. Dans certaines autres réponses, il y a le problème de la limite de récursivité SQL de 100, mais pas dans ce cas. Implémentation très rapide et très simple. Où est le bouton +2?
T-moty

5
J'ai essayé cette fonction mot pour mot avec l'utilisation: select * from DBO.SplitString('Hello John smith', ' ');et la sortie produite était: Valeur Bonjour ello llo lo o John ohn hn n smith mith ith th h
wwmbes

2
@AaronBertrand Le problème d'origine signalé par GateKiller implique un délimiteur d'espace.
wwmbes du

1
@ user1255933 Adresse.
Aaron Bertrand

1
@Michael Oui, c'est vrai. Vous n'auriez pas non plus de table à sélectionner si vous n'aviez pas l'autorisation ALTER SCHEMA, et vous ne seriez pas en mesure de la sélectionner si vous n'avez pas l'autorisation SELECT Vous pouvez toujours demander à quelqu'un de créer la fonction pour vous . Ou créez-le quelque part où vous pouvez le créer (même temporairement, par exemple dans tempdb). Et sur 2016+, vous devriez utiliser STRING_SPLIT () et non une fonction que vous devez créer vous-même de toute façon.
Aaron Bertrand

38

Vous pouvez tirer parti d'une table numérique pour effectuer l'analyse syntaxique des chaînes.

Créez 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

Utilisation (produit 3mil de lignes en 40s sur mon ordinateur portable)

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

nettoyer

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

Les performances ici ne sont pas étonnantes, mais appeler une fonction sur un million de lignes n'est pas la meilleure idée. Si vous effectuez une chaîne divisée sur plusieurs lignes, j'éviterais la fonction.


2
La meilleure solution IMO, les autres ont une sorte de limitation .. c'est rapide et peut analyser de longues chaînes avec de nombreux éléments.
Pking

Pourquoi commandez-vous n décroissant? S'il y avait trois éléments, et que nous avons commencé à numéroter à 1, alors le premier élément sera le numéro 3, et le dernier sera le numéro 1. Ne donnerait-il pas des résultats plus intuitifs si les descétaient supprimés?
hache - fait avec SOverflow

1
D'accord, serait plus intuitif dans le sens asc. Je suivais la convention parsename () qui utilise desc
Nathan Skerl

3
une explication sur la façon dont cela fonctionne serait formidable
Tim Abell

Dans un test sur 100 millions de lignes contenant jusqu'à 3 champs à analyser, ufn_ParseArray n'a pas terminé après 25 minutes, tandis que REVERSE(PARSENAME(REPLACE(REVERSE('Hello John Smith'), ' ', '.'), 1)) @NothingsImpossible s'est terminé en 1,5 minute. @hello_earth Comment comparerait votre solution sur des chaînes plus longues avec plus de 4 champs?
wwmbes

32

Cette question ne concerne pas une approche de partage de chaînes , mais la façon d'obtenir le nième élément .

Toutes les réponses ici font une sorte de division de chaîne en utilisant récursion, CTEs, multiple CHARINDEX, REVERSEet PATINDEX, inventant des fonctions, appel à des méthodes CLR, tableaux de nombres, CROSS APPLYs ... La plupart des réponses couvrent de nombreuses lignes de code.

Mais - si vous ne voulez vraiment rien de plus qu'une approche pour obtenir le nième élément - cela peut être fait comme une véritable doublure , pas d'UDF, pas même une sous-sélection ... Et comme avantage supplémentaire: tapez safe

Obtenez 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 pour le délimiteur et la position (utilisez sql:columnpour 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 peut inclure des caractères interdits (en particulier un parmi &><), vous pouvez toujours le faire de cette façon. Utilisez FOR XML PATHd'abord votre chaîne pour remplacer implicitement tous les caractères interdits par la séquence d'échappement appropriée.

C'est un cas très spécial si - en plus - votre délimiteur est le point-virgule . Dans ce cas, je remplace d'abord le délimiteur par '# DLMT #', puis je le remplace par les balises XML:

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 JSON_VALUEet OPENJSON.

Avec JSON_VALUEnous pouvons passer en position de tableau d'index.

Car OPENJSONla documentation indique clairement:

Lorsque OPENJSON analyse un tableau JSON, la fonction renvoie les index des éléments du texte JSON sous forme de clés.

Une chaîne comme 1,2,3rien de plus que les besoins entre parenthèses: [1,2,3].
Une chaîne de mots comme this is an exampledoit être ["this","is","an","example"].
Ce sont des opérations de chaîne très faciles. Essayez-le:

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

--We can build the json-path '$[1]' using CONCAT
SELECT JSON_VALUE('["' + REPLACE(@str,' ','","') + '"]',CONCAT('$[',@position-1,']'));

- Voir ceci pour un séparateur de chaîne sûr de position ( basé sur zéro ):

SELECT  JsonArray.[key] AS [Position]
       ,JsonArray.[value] AS [Part]
FROM OPENJSON('["' + REPLACE(@str,' ','","') + '"]') JsonArray

Dans cet article, j'ai testé différentes approches et j'ai trouvé que c'étaitOPENJSON vraiment rapide. Encore plus rapide que la fameuse méthode "delimitedSplit8k ()" ...

UPDATE 2 - Obtenez les valeurs de type sécurisé

Nous pouvons utiliser un tableau dans un tableau simplement en utilisant doublé [[]]. Cela permet une WITHclause typée :

DECLARE  @SomeDelimitedString VARCHAR(100)='part1|1|20190920';

DECLARE @JsonArray NVARCHAR(MAX)=CONCAT('[["',REPLACE(@SomeDelimitedString,'|','","'),'"]]');

SELECT @SomeDelimitedString          AS TheOriginal
      ,@JsonArray                    AS TransformedToJSON
      ,ValuesFromTheArray.*
FROM OPENJSON(@JsonArray)
WITH(TheFirstFragment  VARCHAR(100) '$[0]'
    ,TheSecondFragment INT          '$[1]'
    ,TheThirdFragment  DATE         '$[2]') ValuesFromTheArray

Re: si votre chaîne peut inclure des caractères interdits ... vous pouvez simplement envelopper les sous-chaînes comme ça <x><![CDATA[x<&>x]]></x>.
Salman A

@SalmanA, oui, les CDATAsections peuvent gérer cela aussi ... Mais après le casting, elles ont disparu (changé en échappé text()implicitement). Je n'aime pas la magie sous le capot , donc je préfère l' (SELECT 'Text with <&>' AS [*] FOR XML PATH(''))approche -. Cela me semble plus propre et se produit de toute façon ... (Un peu plus sur CDATA et XML ).
Shnugo

22

Voici un UDF qui le fera. Il renverra un tableau des valeurs délimitées, je n'ai pas essayé tous les scénarios mais votre exemple fonctionne 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 l'appeleriez ainsi:


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

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


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

N'a pas fonctionné pour select * from dbo.ethos_SplitString_fn ('guy, wicks, was here', ',') id part ----------- ------------ -------------------------------------- 1 mec 2 mèche
Guy

2
attention avec len () car il ne retournera pas le nombre correct si son argument a des espaces de fin., par exemple len ('-') = 2.
Rory

Ne fonctionne pas: sélectionnez * dans dbo.SplitString ('foo, foo test ,,,, foo', ',')
cbp

1
Correction pour cbp .. Sélectionnez @myString = substring (@ mystring, @ iSpaces + len (@diminator), len (@myString) - charindex (@ deliminator, @ myString, 0))
Alxwest

16

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',' ')

J'ai aimé cette solution. Développé pour renvoyer une valeur scalaire basée sur la colonne spécifiée dans les résultats.
Alan

Je me suis brûlé avec un '&' dans la chaîne à séparer en utilisant ceci
KeithL

10

À mon avis, vous rendez les choses beaucoup trop compliquées. Il suffit de créer un UDF CLR et d'en finir.

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()));
  }
};

20
Je suppose que c'est trop compliqué, car j'ai besoin d'avoir Visual Studio, puis d'activer CLR sur le serveur, puis de créer et de compiler le projet, et enfin d'ajouter les assemblys à la base de données, afin de l'utiliser. Mais c'est toujours une réponse intéressante.
Guillermo Gutiérrez

3
@ guillegr123, ça n'a pas besoin d'être compliqué. Vous pouvez simplement télécharger et installer (gratuitement!), SQL #, qui est une bibliothèque de fonctions et de processus SQLCLR. Vous pouvez l'obtenir sur SQLsharp.com . Oui, je suis l'auteur mais String_Split est inclus dans la version gratuite.
Solomon Rutzky

10

Qu'en est-il de l'utilisation stringet de la values()déclaration?

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 de résultats atteint.

id  item
1   Hello
2   John
3   Smith

1
j'ai utilisé votre réponse mais je n'ai pas fonctionné, mais j'ai modifié et cela a fonctionné avec union all, j'utilise sql 2005
angel

9

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

Je l'ai modifié et j'utilise selectavec union allet ç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 l'ensemble de résultats est:

id  item
1   Hello
2   John
3   Smith
4   how
5   are
6   you

C'est vraiment génial que j'aie jamais vu dans des choses sql, cela a fonctionné pour mon travail et j'apprécie cela, merci!
Abdurrahman I.

Je suis vraiment excité quand j'ai vu ça parce que ça avait l'air si propre et facile à comprendre, mais malheureusement vous ne pouvez pas mettre ça dans un UDF à cause de EXEC. EXECappelle implicitement une procédure stockée et vous ne pouvez pas utiliser de procédures stockées dans les FDU.
Kristen Hammack

Cela fonctionne parfaitement !! je cherchais à utiliser une fonction (SplitStrings_Moden) à partir d'ici: sqlperformance.com/2012/07/07/t-sql-queries/split-strings#comments qui le fait et cela prenait une minute et demie pour diviser les données et retourner les lignes lorsque vous utilisez uniquement 4 numéros de compte. J'ai testé votre version avec une jointure gauche sur la table avec les données sur les numéros de compte et cela a pris environ 2 ou 3 secondes! Énorme différence et fonctionne parfaitement! Je donnerais ces 20 votes si possible!
MattE

8

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

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

notez FIELD , INDEX et TYPE .

Laissez une table avec des identifiants comme

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

Ensuite, 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

fendre et couler toutes les pièces.


C'est la seule solution ici qui vous permet de caster des types spécifiques et est moyennement efficace (CLR est toujours le plus efficace, mais cette approche gère une table de lignes de 8 Go, 10 jetons et 10 Mo en 9 minutes environ (serveur m3 aws, iops 4k) lecteur provisionné)
Andrew Hill

7

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

Pour obtenir l'élément à l' index N (basé sur 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';

L'astuce se trouve dans les RANGS OFFSET 1, qui ignoreront le premier élément et retourneront le deuxième élément. Si vos index sont basés sur 0 et @X est la variable contenant l'index de l'article que vous souhaitez récupérer, vous pouvez certainement faire OFFSET @X ROWS
Gorgi Rankovski

D'accord, je n'ai pas utilisé cela avant ... Bon à savoir ... Je préférerais toujours l' xmlapproche basée sur -split, car elle permet de récupérer la valeur de type sécurisé et n'a pas besoin d'une sous-requête, mais c'est un bon. +1 de mon côté
Shnugo

3
le problème ici est que STRING_SPLIT ne garantit pas l'ordre des résultats renvoyés. Donc, votre article 1 peut ou non être mon article 1.
user1443098

@GorgiRankovski, Utilisation des STRING_SPLITdemandes pour v2016 +. Dans ce cas, il est préférable d'utiliser OPENJSONou JSON_VALUE. Vous voudrez peut-être vérifier ma réponse
Shnugo

6

Je cherchais la solution sur le net et ce qui suit fonctionne pour moi. Réf. .

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

Vous ne pouvez pas accéder facilement au Nième élément à l'aide de cette fonction.
Björn Lindqvist

6

Encore une autre partie de la chaîne par fonction de délimitation:

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'utilisation:

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

qui renvoie:

c

J'aime cette solution comme option pour retourner une seule sous-chaîne au lieu d'obtenir une table analysée que vous devez ensuite sélectionner. L'utilisation d'un résultat de table a ses utilisations, mais pour ce dont j'avais besoin, cela fonctionnait parfaitement.
James H

5

Essaye ça:

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

Testez-le comme ceci:

select * from SplitWordList('Hello John Smith')

Je l'ai traversé et c'est parfaitement comme ce que je veux! même moi, je peux aussi le personnaliser pour ignorer les caractères spéciaux que je choisis!
Vikas

5

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

Démo sur SQLFiddle


2


    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 < 0 Then @Temp Else Left(@Temp, @End) End,
             -- New @Expression
             @Expression = REPLACE(@Expression,
                                   @Qualifier + @Temp2 + Case When @End < 0 Then '' Else @Qualifier End,
                                   Replace(@Temp2, @Delimiter, '|||***|||')
                           )
       END

       -- Replace all occurences of @Delimiter within @Expression with '</fn_Split><fn_Split>'
       -- And convert it to XML so we can select from it
       SET
          @X = Cast('<fn_Split>' +
                    Replace(@Expression, @Delimiter, '</fn_Split><fn_Split>') +
                    '</fn_Split>' 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

2

Vous pouvez fractionner 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'

-- http://stackoverflow.com/questions/14712864/how-to-query-values-from-xml-nodes
SELECT 
    x.XmlCol.value('.', 'varchar(36)') AS val 
FROM 
(
    SELECT 
    CAST('<e>' + REPLACE(@bla, ',', '</e><e>') + '</e>' AS xml) AS RawXml
) AS b 
CROSS APPLY b.RawXml.nodes('e') x(XmlCol);

Si vous devez prendre en charge des chaînes arbitraires (avec des caractères spéciaux xml)

DECLARE @bla NVARCHAR(MAX)
SET @bla = '<html>unsafe & safe Utf8CharsDon''tGetEncoded ÄöÜ - "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); 

1

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 les 3 sous-chaînes du suppresseur par ''.
  • Il ne faut pas utiliser la boucle while, car cela diminue les performances.
  • Pas besoin de pivoter car toute la sous-chaîne résultante sera affichée sur une seule ligne

Limites:

  • Il faut connaître le total non. d'espaces (sous-chaîne).

Remarque : la solution peut donner une sous-chaîne jusqu'à N.

Pour surmonter la limitation, nous pouvons utiliser la référence suivante .

Mais encore une fois la solution ci-dessus ne peut pas être utilisée dans une table (en fait, je n'ai pas pu l'utiliser).

Encore une fois, j'espère que cette solution pourra aider quelqu'un.

Mise à jour: En cas d'enregistrements> 50000, il n'est pas conseillé de l'utiliser LOOPScar cela dégraderait les performances


1

Solution basée sur un ensemble pur utilisant TVFavec récursif CTE. Vous pouvez JOINet APPLYcette 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

Usage:

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

Résultat:

value   index
-------------
John    1

1

Presque toutes les autres réponses remplacent la chaîne en cours de fractionnement qui gaspille les cycles CPU et effectue des allocations de mémoire inutiles.

Je couvre une bien meilleure façon de faire un split 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.

0

Solution CTE récursive avec des problèmes de serveur, testez-la

Configuration du schéma MS SQL Server 2008 :

create table Course( Courses varchar(100) );
insert into Course values ('Hello John Smith');

Requête 1 :

with cte as
   ( select 
        left( Courses, charindex( ' ' , Courses) ) as a_l,
        cast( substring( Courses, 
                         charindex( ' ' , Courses) + 1 , 
                         len(Courses ) ) + ' ' 
              as varchar(100) )  as a_r,
        Courses as a,
        0 as n
     from Course t
    union all
      select 
        left(a_r, charindex( ' ' , a_r) ) as a_l,
        substring( a_r, charindex( ' ' , a_r) + 1 , len(a_R ) ) as a_r,
        cte.a,
        cte.n + 1 as n
    from Course t inner join cte 
         on t.Courses = cte.a and len( a_r ) > 0

   )
select a_l, n from cte
--where N = 1

Résultats :

|    A_L | N |
|--------|---|
| Hello  | 0 |
|  John  | 1 |
| Smith  | 2 |

0

bien que similaire à la réponse basée sur xml de josejuan, j'ai trouvé que le traitement du chemin xml une seule fois, puis le pivotement était modérément plus efficace:

select ID,
    [3] as PathProvidingID,
    [4] as PathProvider,
    [5] as ComponentProvidingID,
    [6] as ComponentProviding,
    [7] as InputRecievingID,
    [8] as InputRecieving,
    [9] as RowsPassed,
    [10] as InputRecieving2
    from
    (
    select id,message,d.* from sysssislog cross apply       ( 
          SELECT Item = y.i.value('(./text())[1]', 'varchar(200)'),
              row_number() over(order by y.i) as rn
          FROM 
          ( 
             SELECT x = CONVERT(XML, '<i>' + REPLACE(Message, ':', '</i><i>') + '</i>').query('.')
          ) AS a CROSS APPLY x.nodes('i') AS y(i)
       ) d
       WHERE event
       = 
       'OnPipelineRowsSent'
    ) as tokens 
    pivot 
    ( max(item) for [rn] in ([3],[4],[5],[6],[7],[8],[9],[10]) 
    ) as data

couru en 8:30

select id,
tokens.value('(/n[3])', 'varchar(100)')as PathProvidingID,
tokens.value('(/n[4])', 'varchar(100)') as PathProvider,
tokens.value('(/n[5])', 'varchar(100)') as ComponentProvidingID,
tokens.value('(/n[6])', 'varchar(100)') as ComponentProviding,
tokens.value('(/n[7])', 'varchar(100)') as InputRecievingID,
tokens.value('(/n[8])', 'varchar(100)') as InputRecieving,
tokens.value('(/n[9])', 'varchar(100)') as RowsPassed
 from
(
    select id, Convert(xml,'<n>'+Replace(message,'.','</n><n>')+'</n>') tokens
         from sysssislog 
       WHERE event
       = 
       'OnPipelineRowsSent'
    ) as data

couru en 9:20


0
CREATE FUNCTION [dbo].[fnSplitString] 
( 
    @string NVARCHAR(MAX), 
    @delimiter CHAR(1) 
) 
RETURNS @output TABLE(splitdata NVARCHAR(MAX) 
) 
BEGIN 
    DECLARE @start INT, @end INT 
    SELECT @start = 1, @end = CHARINDEX(@delimiter, @string) 
    WHILE @start < LEN(@string) + 1 BEGIN 
        IF @end = 0  
            SET @end = LEN(@string) + 1

        INSERT INTO @output (splitdata)  
        VALUES(SUBSTRING(@string, @start, @end - @start)) 
        SET @start = @end + 1 
        SET @end = CHARINDEX(@delimiter, @string, @start)

    END 
    RETURN 
END

ET L'UTILISER

select *from dbo.fnSplitString('Querying SQL Server','')

0

si quelqu'un veut obtenir une seule partie du texte séparé, vous pouvez l'utiliser

sélectionnez * parmi fromSplitStringSep ('Word1 wordr2 word3', '')

CREATE function [dbo].[SplitStringSep] 
(
    @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
  )

0

J'ai développé ça,

declare @x nvarchar(Max) = 'ali.veli.deli.';
declare @item nvarchar(Max);
declare @splitter char='.';

while CHARINDEX(@splitter,@x) != 0
begin
    set @item = LEFT(@x,CHARINDEX(@splitter,@x))
    set @x    = RIGHT(@x,len(@x)-len(@item) )
     select @item as item, @x as x;
end

la seule attention que vous devriez faire est le point '.' cette extrémité du @x doit toujours être là.


0

en s'appuyant sur la solution @NothingsImpossible ou, plutôt, en commentant la réponse la plus votée (juste en dessous de la solution acceptée), j'ai trouvé que la solution rapide et sale suivante répond à mes propres besoins - elle a l'avantage d'être uniquement dans le domaine SQL.

étant donné une chaîne "premier; deuxième; troisième; quatrième; cinquième", disons, je veux obtenir le troisième jeton. cela ne fonctionne que si nous savons combien de jetons la chaîne va avoir - dans ce cas, c'est 5. donc ma façon d'agir est de couper les deux derniers jetons (requête interne), puis de couper les deux premiers jetons ( requête externe)

je sais que c'est moche et couvre les conditions spécifiques dans lesquelles j'étais, mais je le poste juste au cas où quelqu'un le trouverait utile. à votre santé

select 
    REVERSE(
        SUBSTRING(
            reverse_substring, 
            0, 
            CHARINDEX(';', reverse_substring)
        )
    ) 
from 
(
    select 
        msg,
        SUBSTRING(
            REVERSE(msg), 
            CHARINDEX(
                ';', 
                REVERSE(msg), 
                CHARINDEX(
                    ';',
                    REVERSE(msg)
                )+1
            )+1,
            1000
        ) reverse_substring
    from 
    (
        select 'first;second;third;fourth;fifth' msg
    ) a
) b

cela ne fonctionne que si nous savons combien de jetons la chaîne va avoir - une limitation de rupture ...
Shnugo

0
declare @strng varchar(max)='hello john smith'
select (
    substring(
        @strng,
        charindex(' ', @strng) + 1,
        (
          (charindex(' ', @strng, charindex(' ', @strng) + 1))
          - charindex(' ',@strng)
        )
    ))

0

À partir de SQL Server 2016, nous string_split

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

SELECT value FROM string_split(@string, ',')

C'est bien beau, mais cela ne règle pas la question de l'obtention du nième résultat.
Johnie Karr

STRING_SPLITne garantit pas de retourner la même commande. Mais le OPENJSONfait (voir ma réponse (section mise à jour) )
Shnugo
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.