À 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"?
À 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"?
Réponses:
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
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)
?
STRING_SPLIT
qui fractionnera une chaîne et renverra un résultat de table à une colonne que vous pouvez utiliser dans une SELECT
instruction ou ailleurs.
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?
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.
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.
100
(pour éviter une boucle infinie). Utilisez l' indication MAXRECURSION pour définir le nombre de niveaux de récursivité ( 0
à 32767
, 0
est "pas de limite" - peut écraser le serveur). BTW, bien meilleure réponse que PARSENAME
parce que c'est universel :-). +1
maxrecursion
à cette solution, gardez à l'esprit cette question et ses réponses Comment configurer l' maxrecursion
option pour un CTE à l'intérieur d'une fonction table .
s
n'est donc plus définie
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' idx
argument 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,1
le 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()
:
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
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.
desc
étaient supprimés?
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?
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, CTE
s, multiple CHARINDEX
, REVERSE
et PATINDEX
, inventant des fonctions, appel à des méthodes CLR, tableaux de nombres, CROSS APPLY
s ... 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: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 peut inclure des caractères interdits (en particulier un parmi &><
), vous pouvez toujours le faire de cette façon. Utilisez FOR XML PATH
d'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)');
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_VALUE
et OPENJSON
.
Avec JSON_VALUE
nous pouvons passer en position de tableau d'index.
Car OPENJSON
la 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,3
rien de plus que les besoins entre parenthèses: [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. 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 ()" ...
Nous pouvons utiliser un tableau dans un tableau simplement en utilisant doublé [[]]
. Cela permet une WITH
clause 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
<x><![CDATA[x<&>x]]></x>
.
CDATA
sections 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 ).
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','**')
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',' ')
À 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()));
}
};
Qu'en est-il de l'utilisation string
et 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
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 l'ensemble de résultats est:
id item
1 Hello
2 John
3 Smith
4 how
5 are
6 you
EXEC
. EXEC
appelle implicitement une procédure stockée et vous ne pouvez pas utiliser de procédures stockées dans les FDU.
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.
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';
xml
approche 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é
STRING_SPLIT
demandes pour v2016 +. Dans ce cas, il est préférable d'utiliser OPENJSON
ou JSON_VALUE
. Vous voudrez peut-être vérifier ma réponse
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
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
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')
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
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
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);
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
Avantages:
Limites:
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 LOOPS
car cela dégraderait les performances
Solution basée sur un ensemble pur utilisant TVF
avec récursif 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
Usage:
select *
from [dbo].[SplitStringToResultSet]('Hello John Smith', ' ')
where [index] = 1;
Résultat:
value index
-------------
John 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.
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
| A_L | N |
|--------|---|
| Hello | 0 |
| John | 1 |
| Smith | 2 |
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
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','')
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
)
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à.
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
À partir de SQL Server 2016, nous string_split
DECLARE @string varchar(100) = 'Richard, Mike, Mark'
SELECT value FROM string_split(@string, ',')
STRING_SPLIT
ne garantit pas de retourner la même commande. Mais le OPENJSON
fait (voir ma réponse (section mise à jour) )