如何将逗号分隔的值拆分为列

我有一张这样的桌子

Value   String
-------------------
1       Cleo, Smith

我想把逗号分隔的字符串分成两列

Value  Name Surname
-------------------
1      Cleo   Smith

我只需要两根固定的额外柱子

1217198 次浏览

您的目的可以使用以下查询解决-

Select Value  , Substring(FullName, 1,Charindex(',', FullName)-1) as Name,
Substring(FullName, Charindex(',', FullName)+1, LEN(FullName)) as  Surname
from Table1

在 sql 服务器中没有现成的 Split 函数,因此我们需要创建用户定义的函数。

CREATE FUNCTION Split (
@InputString                  VARCHAR(8000),
@Delimiter                    VARCHAR(50)
)


RETURNS @Items TABLE (
Item                          VARCHAR(8000)
)


AS
BEGIN
IF @Delimiter = ' '
BEGIN
SET @Delimiter = ','
SET @InputString = REPLACE(@InputString, ' ', @Delimiter)
END


IF (@Delimiter IS NULL OR @Delimiter = '')
SET @Delimiter = ','


--INSERT INTO @Items VALUES (@Delimiter) -- Diagnostic
--INSERT INTO @Items VALUES (@InputString) -- Diagnostic


DECLARE @Item           VARCHAR(8000)
DECLARE @ItemList       VARCHAR(8000)
DECLARE @DelimIndex     INT


SET @ItemList = @InputString
SET @DelimIndex = CHARINDEX(@Delimiter, @ItemList, 0)
WHILE (@DelimIndex != 0)
BEGIN
SET @Item = SUBSTRING(@ItemList, 0, @DelimIndex)
INSERT INTO @Items VALUES (@Item)


-- Set @ItemList = @ItemList minus one less item
SET @ItemList = SUBSTRING(@ItemList, @DelimIndex+1, LEN(@ItemList)-@DelimIndex)
SET @DelimIndex = CHARINDEX(@Delimiter, @ItemList, 0)
END -- End WHILE


IF @Item IS NOT NULL -- At least one delimiter was encountered in @InputString
BEGIN
SET @Item = @ItemList
INSERT INTO @Items VALUES (@Item)
END


-- No delimiters were encountered in @InputString, so just return @InputString
ELSE INSERT INTO @Items VALUES (@InputString)


RETURN


END -- End Function
GO


---- Set Permissions
--GRANT SELECT ON Split TO UserRole1
--GRANT SELECT ON Split TO UserRole2
--GO

您可能会发现 SQL 用户定义函数解析带分隔符的字符串中的解决方案很有帮助(来自 代码项目)。

这是本页的代码部分:

CREATE FUNCTION [fn_ParseText2Table]
(@p_SourceText VARCHAR(MAX)
,@p_Delimeter VARCHAR(100)=',' --default to comma delimited.
)
RETURNS @retTable
TABLE([Position] INT IDENTITY(1,1)
,[Int_Value] INT
,[Num_Value] NUMERIC(18,3)
,[Txt_Value] VARCHAR(MAX)
,[Date_value] DATETIME
)
AS
/*
********************************************************************************
Purpose: Parse values from a delimited string
& return the result as an indexed table
Copyright 1996, 1997, 2000, 2003 Clayton Groom (<A href="mailto:Clayton_Groom@hotmail.com">Clayton_Groom@hotmail.com</A>)
Posted to the public domain Aug, 2004
2003-06-17 Rewritten as SQL 2000 function.
Reworked to allow for delimiters > 1 character in length
and to convert Text values to numbers
2016-04-05 Added logic for date values based on "new" ISDATE() function, Updated to use XML approach, which is more efficient.
********************************************************************************
*/




BEGIN
DECLARE @w_xml xml;
SET @w_xml = N'<root><i>' + replace(@p_SourceText, @p_Delimeter,'</i><i>') + '</i></root>';




INSERT INTO @retTable
([Int_Value]
, [Num_Value]
, [Txt_Value]
, [Date_value]
)
SELECT CASE
WHEN ISNUMERIC([i].value('.', 'VARCHAR(MAX)')) = 1
THEN CAST(CAST([i].value('.', 'VARCHAR(MAX)') AS NUMERIC) AS INT)
END AS [Int_Value]
, CASE
WHEN ISNUMERIC([i].value('.', 'VARCHAR(MAX)')) = 1
THEN CAST([i].value('.', 'VARCHAR(MAX)') AS NUMERIC(18, 3))
END AS [Num_Value]
, [i].value('.', 'VARCHAR(MAX)') AS [txt_Value]
, CASE
WHEN ISDATE([i].value('.', 'VARCHAR(MAX)')) = 1
THEN CAST([i].value('.', 'VARCHAR(MAX)') AS DATETIME)
END AS [Num_Value]
FROM @w_xml.nodes('//root/i') AS [Items]([i]);
RETURN;
END;
GO
;WITH Split_Names (Value,Name, xmlname)
AS
(
SELECT Value,
Name,
CONVERT(XML,'<Names><name>'
+ REPLACE(Name,',', '</name><name>') + '</name></Names>') AS xmlname
FROM tblnames
)


SELECT Value,
xmlname.value('/Names[1]/name[1]','varchar(100)') AS Name,
xmlname.value('/Names[1]/name[2]','varchar(100)') AS Surname
FROM Split_Names

此外,你亦可参考以下连结

Http://jahaines.blogspot.in/2009/06/converting-delimited-string-of-values.html

基于 xml 的答案简单明了

请参阅 这个

DECLARE @S varchar(max),
@Split char(1),
@X xml


SELECT @S = 'ab,cd,ef,gh,ij',
@Split = ','


SELECT @X = CONVERT(xml,' <root> <myvalue>' +
REPLACE(@S,@Split,'</myvalue> <myvalue>') + '</myvalue>   </root> ')


SELECT  T.c.value('.','varchar(20)'),              --retrieve ALL values at once
T.c.value('(/root/myvalue)[1]','VARCHAR(20)')  , --retrieve index 1 only, which is the 'ab'
T.c.value('(/root/myvalue)[2]','VARCHAR(20)')
FROM @X.nodes('/root/myvalue') T(c)

我们可以创建这样的函数

CREATE Function [dbo].[fn_CSVToTable]
(
@CSVList Varchar(max)
)
RETURNS @Table TABLE (ColumnData VARCHAR(100))
AS
BEGIN
IF RIGHT(@CSVList, 1) <> ','
SELECT @CSVList = @CSVList + ','


DECLARE @Pos    BIGINT,
@OldPos BIGINT
SELECT  @Pos    = 1,
@OldPos = 1


WHILE   @Pos < LEN(@CSVList)
BEGIN
SELECT  @Pos = CHARINDEX(',', @CSVList, @OldPos)
INSERT INTO @Table
SELECT  LTRIM(RTRIM(SUBSTRING(@CSVList, @OldPos, @Pos - @OldPos))) Col001


SELECT  @OldPos = @Pos + 1
END


RETURN
END

然后,我们可以使用 SELECT 语句将 CSV 值分隔到各自的列中

我觉得这很酷

SELECT value,
PARSENAME(REPLACE(String,',','.'),2) 'Name' ,
PARSENAME(REPLACE(String,',','.'),1) 'Surname'
FROM table WITH (NOLOCK)

Mytable:

Value  ColOne
--------------------
1      Cleo, Smith

如果没有太多的列,下面的方法应该可行

ALTER TABLE mytable ADD ColTwo nvarchar(256);
UPDATE mytable SET ColTwo = LEFT(ColOne, Charindex(',', ColOne) - 1);
--'Cleo' = LEFT('Cleo, Smith', Charindex(',', 'Cleo, Smith') - 1)
UPDATE mytable SET ColTwo = REPLACE(ColOne, ColTwo + ',', '');
--' Smith' = REPLACE('Cleo, Smith', 'Cleo' + ',')
UPDATE mytable SET ColOne = REPLACE(ColOne, ',' + ColTwo, ''), ColTwo = LTRIM(ColTwo);
--'Cleo' = REPLACE('Cleo, Smith', ',' + ' Smith', '')

结果:

Value  ColOne ColTwo
--------------------
1      Cleo   Smith

使用 Parsename ()函数

with cte as(
select 'Aria,Karimi' as FullName
Union
select 'Joe,Karimi' as FullName
Union
select 'Bab,Karimi' as FullName
)


SELECT PARSENAME(REPLACE(FullName,',','.'),2) as Name,
PARSENAME(REPLACE(FullName,',','.'),1) as Family
FROM cte

结果

Name    Family
-----   ------
Aria    Karimi
Bab     Karimi
Joe     Karimi
SELECT id,
Substring(NAME, 0, Charindex(',', NAME))             AS firstname,
Substring(NAME, Charindex(',', NAME), Len(NAME) + 1) AS lastname
FROM   spilt

我发现如上所述使用 PARSENAME 会导致任何带句点的名称为空。

因此,如果名称中有一个首字母或者一个标题后面跟着一个点,它们返回 NULL。

我发现这对我很管用:

SELECT
REPLACE(SUBSTRING(FullName, 1,CHARINDEX(',', FullName)), ',','') as Name,
REPLACE(SUBSTRING(FullName, CHARINDEX(',', FullName), LEN(FullName)), ',', '') as Surname
FROM Table1

交叉适用

select ParsedData.*
from MyTable mt
cross apply ( select str = mt.String + ',,' ) f1
cross apply ( select p1 = charindex( ',', str ) ) ap1
cross apply ( select p2 = charindex( ',', str, p1 + 1 ) ) ap2
cross apply ( select Nmame = substring( str, 1, p1-1 )
, Surname = substring( str, p1+1, p2-p1-1 )
) ParsedData

解决这个问题的方法有很多,而且已经提出了很多不同的方法。最简单的方法是使用 LEFT/SUBSTRING和其他字符串函数来实现所需的结果。

样本数据

DECLARE @tbl1 TABLE (Value INT,String VARCHAR(MAX))


INSERT INTO @tbl1 VALUES(1,'Cleo, Smith');
INSERT INTO @tbl1 VALUES(2,'John, Mathew');

使用类似 LEFT的字符串函数

SELECT
Value,
LEFT(String,CHARINDEX(',',String)-1) as Fname,
LTRIM(RIGHT(String,LEN(String) - CHARINDEX(',',String) )) AS Lname
FROM @tbl1

如果 String 中有2个以上的项,则此方法将失败。 在这样的场景中,我们可以使用拆分器,然后使用 PIVOT或将字符串转换为 XML,并使用 .nodes获取字符串项。基于 XML的解决方案已经被 aads 和 bvr 在他们的解决方案中详细阐述。

这个问题的答案使用分解器,都使用 WHILE,这是效率低下的分解。看看这个 性能比较。其中一个最好的分离器是 DelimitedSplit8K,由杰夫摩登创建。你可以阅读更多关于它的 给你

PIVOT的分流器

DECLARE @tbl1 TABLE (Value INT,String VARCHAR(MAX))


INSERT INTO @tbl1 VALUES(1,'Cleo, Smith');
INSERT INTO @tbl1 VALUES(2,'John, Mathew');




SELECT t3.Value,[1] as Fname,[2] as Lname
FROM @tbl1 as t1
CROSS APPLY [dbo].[DelimitedSplit8K](String,',') as t2
PIVOT(MAX(Item) FOR ItemNumber IN ([1],[2])) as t3

输出

Value   Fname   Lname
1   Cleo    Smith
2   John    Mathew

作者: Jeff Moden

CREATE FUNCTION [dbo].[DelimitedSplit8K]
/**********************************************************************************************************************
Purpose:
Split a given string at a given delimiter and return a list of the split elements (items).


Notes:
1.  Leading a trailing delimiters are treated as if an empty string element were present.
2.  Consecutive delimiters are treated as if an empty string element were present between them.
3.  Except when spaces are used as a delimiter, all spaces present in each element are preserved.


Returns:
iTVF containing the following:
ItemNumber = Element position of Item as a BIGINT (not converted to INT to eliminate a CAST)
Item       = Element value as a VARCHAR(8000)


Statistics on this function may be found at the following URL:
http://www.sqlservercentral.com/Forums/Topic1101315-203-4.aspx


CROSS APPLY Usage Examples and Tests:
--=====================================================================================================================
-- TEST 1:
-- This tests for various possible conditions in a string using a comma as the delimiter.  The expected results are
-- laid out in the comments
--=====================================================================================================================
--===== Conditionally drop the test tables to make reruns easier for testing.
-- (this is NOT a part of the solution)
IF OBJECT_ID('tempdb..#JBMTest') IS NOT NULL DROP TABLE #JBMTest
;
--===== Create and populate a test table on the fly (this is NOT a part of the solution).
-- In the following comments, "b" is a blank and "E" is an element in the left to right order.
-- Double Quotes are used to encapsulate the output of "Item" so that you can see that all blanks
-- are preserved no matter where they may appear.
SELECT *
INTO #JBMTest
FROM (                                               --# & type of Return Row(s)
SELECT  0, NULL                      UNION ALL --1 NULL
SELECT  1, SPACE(0)                  UNION ALL --1 b (Empty String)
SELECT  2, SPACE(1)                  UNION ALL --1 b (1 space)
SELECT  3, SPACE(5)                  UNION ALL --1 b (5 spaces)
SELECT  4, ','                       UNION ALL --2 b b (both are empty strings)
SELECT  5, '55555'                   UNION ALL --1 E
SELECT  6, ',55555'                  UNION ALL --2 b E
SELECT  7, ',55555,'                 UNION ALL --3 b E b
SELECT  8, '55555,'                  UNION ALL --2 b B
SELECT  9, '55555,1'                 UNION ALL --2 E E
SELECT 10, '1,55555'                 UNION ALL --2 E E
SELECT 11, '55555,4444,333,22,1'     UNION ALL --5 E E E E E
SELECT 12, '55555,4444,,333,22,1'    UNION ALL --6 E E b E E E
SELECT 13, ',55555,4444,,333,22,1,'  UNION ALL --8 b E E b E E E b
SELECT 14, ',55555,4444,,,333,22,1,' UNION ALL --9 b E E b b E E E b
SELECT 15, ' 4444,55555 '            UNION ALL --2 E (w/Leading Space) E (w/Trailing Space)
SELECT 16, 'This,is,a,test.'                   --E E E E
) d (SomeID, SomeValue)
;
--===== Split the CSV column for the whole table using CROSS APPLY (this is the solution)
SELECT test.SomeID, test.SomeValue, split.ItemNumber, Item = QUOTENAME(split.Item,'"')
FROM #JBMTest test
CROSS APPLY dbo.DelimitedSplit8K(test.SomeValue,',') split
;
--=====================================================================================================================
-- TEST 2:
-- This tests for various "alpha" splits and COLLATION using all ASCII characters from 0 to 255 as a delimiter against
-- a given string.  Note that not all of the delimiters will be visible and some will show up as tiny squares because
-- they are "control" characters.  More specifically, this test will show you what happens to various non-accented
-- letters for your given collation depending on the delimiter you chose.
--=====================================================================================================================
WITH
cteBuildAllCharacters (String,Delimiter) AS
(
SELECT TOP 256
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789',
CHAR(ROW_NUMBER() OVER (ORDER BY (SELECT NULL))-1)
FROM master.sys.all_columns
)
SELECT ASCII_Value = ASCII(c.Delimiter), c.Delimiter, split.ItemNumber, Item = QUOTENAME(split.Item,'"')
FROM cteBuildAllCharacters c
CROSS APPLY dbo.DelimitedSplit8K(c.String,c.Delimiter) split
ORDER BY ASCII_Value, split.ItemNumber
;
-----------------------------------------------------------------------------------------------------------------------
Other Notes:
1. Optimized for VARCHAR(8000) or less.  No testing or error reporting for truncation at 8000 characters is done.
2. Optimized for single character delimiter.  Multi-character delimiters should be resolvedexternally from this
function.
3. Optimized for use with CROSS APPLY.
4. Does not "trim" elements just in case leading or trailing blanks are intended.
5. If you don't know how a Tally table can be used to replace loops, please see the following...
http://www.sqlservercentral.com/articles/T-SQL/62867/
6. Changing this function to use NVARCHAR(MAX) will cause it to run twice as slow.  It's just the nature of
VARCHAR(MAX) whether it fits in-row or not.
7. Multi-machine testing for the method of using UNPIVOT instead of 10 SELECT/UNION ALLs shows that the UNPIVOT method
is quite machine dependent and can slow things down quite a bit.
-----------------------------------------------------------------------------------------------------------------------
Credits:
This code is the product of many people's efforts including but not limited to the following:
cteTally concept originally by Iztek Ben Gan and "decimalized" by Lynn Pettis (and others) for a bit of extra speed
and finally redacted by Jeff Moden for a different slant on readability and compactness. Hat's off to Paul White for
his simple explanations of CROSS APPLY and for his detailed testing efforts. Last but not least, thanks to
Ron "BitBucket" McCullough and Wayne Sheffield for their extreme performance testing across multiple machines and
versions of SQL Server.  The latest improvement brought an additional 15-20% improvement over Rev 05.  Special thanks
to "Nadrek" and "peter-757102" (aka Peter de Heer) for bringing such improvements to light.  Nadrek's original
improvement brought about a 10% performance gain and Peter followed that up with the content of Rev 07.


I also thank whoever wrote the first article I ever saw on "numbers tables" which is located at the following URL
and to Adam Machanic for leading me to it many years ago.
http://sqlserver2000.databases.aspfaq.com/why-should-i-consider-using-an-auxiliary-numbers-table.html
-----------------------------------------------------------------------------------------------------------------------
Revision History:
Rev 00 - 20 Jan 2010 - Concept for inline cteTally: Lynn Pettis and others.
Redaction/Implementation: Jeff Moden
- Base 10 redaction and reduction for CTE.  (Total rewrite)


Rev 01 - 13 Mar 2010 - Jeff Moden
- Removed one additional concatenation and one subtraction from the SUBSTRING in the SELECT List for that tiny
bit of extra speed.


Rev 02 - 14 Apr 2010 - Jeff Moden
- No code changes.  Added CROSS APPLY usage example to the header, some additional credits, and extra
documentation.


Rev 03 - 18 Apr 2010 - Jeff Moden
- No code changes.  Added notes 7, 8, and 9 about certain "optimizations" that don't actually work for this
type of function.


Rev 04 - 29 Jun 2010 - Jeff Moden
- Added WITH SCHEMABINDING thanks to a note by Paul White.  This prevents an unnecessary "Table Spool" when the
function is used in an UPDATE statement even though the function makes no external references.


Rev 05 - 02 Apr 2011 - Jeff Moden
- Rewritten for extreme performance improvement especially for larger strings approaching the 8K boundary and
for strings that have wider elements.  The redaction of this code involved removing ALL concatenation of
delimiters, optimization of the maximum "N" value by using TOP instead of including it in the WHERE clause,
and the reduction of all previous calculations (thanks to the switch to a "zero based" cteTally) to just one
instance of one add and one instance of a subtract. The length calculation for the final element (not
followed by a delimiter) in the string to be split has been greatly simplified by using the ISNULL/NULLIF
combination to determine when the CHARINDEX returned a 0 which indicates there are no more delimiters to be
had or to start with. Depending on the width of the elements, this code is between 4 and 8 times faster on a
single CPU box than the original code especially near the 8K boundary.
- Modified comments to include more sanity checks on the usage example, etc.
- Removed "other" notes 8 and 9 as they were no longer applicable.


Rev 06 - 12 Apr 2011 - Jeff Moden
- Based on a suggestion by Ron "Bitbucket" McCullough, additional test rows were added to the sample code and
the code was changed to encapsulate the output in pipes so that spaces and empty strings could be perceived
in the output.  The first "Notes" section was added.  Finally, an extra test was added to the comments above.


Rev 07 - 06 May 2011 - Peter de Heer, a further 15-20% performance enhancement has been discovered and incorporated
into this code which also eliminated the need for a "zero" position in the cteTally table.
**********************************************************************************************************************/
--===== Define I/O parameters
(@pString VARCHAR(8000), @pDelimiter CHAR(1))
RETURNS TABLE WITH SCHEMABINDING AS
RETURN
--===== "Inline" CTE Driven "Tally Table" produces values from 0 up to 10,000...
-- enough to cover NVARCHAR(4000)
WITH E1(N) AS (
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
),                          --10E+1 or 10 rows
E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
cteTally(N) AS (--==== This provides the "base" CTE and limits the number of rows right up front
-- for both a performance gain and prevention of accidental "overruns"
SELECT TOP (ISNULL(DATALENGTH(@pString),0)) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
),
cteStart(N1) AS (--==== This returns N+1 (starting position of each "element" just once for each delimiter)
SELECT 1 UNION ALL
SELECT t.N+1 FROM cteTally t WHERE SUBSTRING(@pString,t.N,1) = @pDelimiter
),
cteLen(N1,L1) AS(--==== Return start and length (for use in substring)
SELECT s.N1,
ISNULL(NULLIF(CHARINDEX(@pDelimiter,@pString,s.N1),0)-s.N1,8000)
FROM cteStart s
)
--===== Do the actual split. The ISNULL/NULLIF combo handles the length for the final element when no delimiter is found.
SELECT ItemNumber = ROW_NUMBER() OVER(ORDER BY l.N1),
Item       = SUBSTRING(@pString, l.N1, l.L1)
FROM cteLen l
;


GO

我遇到了一个类似的问题,但一个复杂的,因为这是第一个线程,我发现有关的问题,我决定张贴我的发现。我知道这是一个简单问题的复杂解决方案,但我希望我可以帮助其他人谁去这个线程寻找一个更复杂的解决方案。我必须分割一个包含5个数字的字符串(列名: levelsFeed) ,并在一个单独的列中显示每个数字。 例如: 8,1,2,2,2 应显示如下:

1  2  3  4  5
-------------
8  1  2  2  2

解决方案1: 使用 XML 函数: 这个解决方案是迄今为止最慢的解决方案

SELECT Distinct FeedbackID,
, S.a.value('(/H/r)[1]', 'INT') AS level1
, S.a.value('(/H/r)[2]', 'INT') AS level2
, S.a.value('(/H/r)[3]', 'INT') AS level3
, S.a.value('(/H/r)[4]', 'INT') AS level4
, S.a.value('(/H/r)[5]', 'INT') AS level5
FROM (
SELECT *,CAST (N'<H><r>' + REPLACE(levelsFeed, ',', '</r><r>')  + '</r> </H>' AS XML) AS [vals]
FROM Feedbacks
)  as d
CROSS APPLY d.[vals].nodes('/H/r') S(a)

解决方案2: 使用拆分函数和 pivot (拆分函数将字符串拆分为列名为 Data 的行)

SELECT FeedbackID, [1],[2],[3],[4],[5]
FROM (
SELECT *, ROW_NUMBER() OVER (PARTITION BY feedbackID ORDER BY (SELECT  null)) as rn
FROM (
SELECT FeedbackID, levelsFeed
FROM Feedbacks
) as a
CROSS APPLY dbo.Split(levelsFeed, ',')
) as SourceTable
PIVOT
(
MAX(data)
FOR rn IN ([1],[2],[3],[4],[5])
)as pivotTable

解决方案3: 使用字符串操作函数——比解决方案2快一点

SELECT FeedbackID,
SUBSTRING(levelsFeed,0,CHARINDEX(',',levelsFeed)) AS level1,
PARSENAME(REPLACE(SUBSTRING(levelsFeed,CHARINDEX(',',levelsFeed)+1,LEN(levelsFeed)),',','.'),4) AS level2,
PARSENAME(REPLACE(SUBSTRING(levelsFeed,CHARINDEX(',',levelsFeed)+1,LEN(levelsFeed)),',','.'),3) AS level3,
PARSENAME(REPLACE(SUBSTRING(levelsFeed,CHARINDEX(',',levelsFeed)+1,LEN(levelsFeed)),',','.'),2) AS level4,
PARSENAME(REPLACE(SUBSTRING(levelsFeed,CHARINDEX(',',levelsFeed)+1,LEN(levelsFeed)),',','.'),1) AS level5
FROM Feedbacks

因为 levelsFeed 包含5个字符串值,我需要为第一个字符串使用子字符串函数。

我希望我的解决方案能够帮助其他进入这个线程的人寻找更复杂的分列方法

使用字符串函数:)

select Value,
substring(String,1,instr(String," ") -1) Fname,
substring(String,instr(String,",") +1) Sname
from tablename;

用了两个函数,
1. substring(string, position, length) = = > 将字符串从一个位置返回到另一个长度
2. instr(string,pattern) = = > 返回图案的位置。

如果我们不在子字符串中提供长度参数,它将返回到字符串的结尾

CREATE FUNCTION [dbo].[fnSplit](@sInputList VARCHAR(8000), @sDelimiter VARCHAR(8000) = ',')
RETURNS @List TABLE (item VARCHAR(8000))
BEGIN


DECLARE @sItem VARCHAR(8000)
WHILE CHARINDEX(@sDelimiter, @sInputList, 0) <> 0
BEGIN


SELECT @sItem = RTRIM(LTRIM(SUBSTRING(@sInputList, 1, CHARINDEX(@sDelimiter, @sInputList,0) - 1))),
@sInputList = RTRIM(LTRIM(SUBSTRING(@sInputList, CHARINDEX(@sDelimiter, @sInputList, 0) + LEN(@sDelimiter),LEN(@sInputList))))


-- Indexes to keep the position of searching
IF LEN(@sItem) > 0


INSERT INTO @List SELECT @sItem


END


IF LEN(@sInputList) > 0
BEGIN


INSERT INTO @List SELECT @sInputList -- Put the last item in


END


RETURN


END

我认为 PARSENAME 是这个示例中使用的简洁的函数,正如本文中所描述的: http://www.sqlshack.com/parsing-and-rotating-delimited-data-in-sql-server-2012/

PARSENAME 函数的逻辑设计是解析由四部分组成的对象名称。PARSENAME 的优点在于它不仅仅局限于解析由四部分组成的 SQL Server 对象名称——它还可以解析任何由点分隔的函数或字符串数据。

第一个参数是要解析的对象,第二个参数是要返回的对象片段的整数值。本文讨论了解析和旋转分隔数据公司的电话号码,但也可用于解析姓名数据。

例如:

USE COMPANY;
SELECT PARSENAME('Whatever.you.want.parsed',3) AS 'ReturnValue';

本文还描述了如何使用名为“ replaceChars”的公共表表达式(Common Table Expression,CTE)对分隔符替换的值运行 PARSENAME。CTE 对于返回临时视图或结果集非常有用。

之后,UNPIVOT 函数被用来将一些列转换成行; SUBSTRING 和 CHARINDEX 函数被用来清除数据中的不一致性,LAG 函数(SQL Server 2012新增)最终被使用,因为它允许引用以前的记录。

试试这个:

declare @csv varchar(100) ='aaa,bb,csda,daass';
set @csv = @csv+',';


with cte as
(
select SUBSTRING(@csv,1,charindex(',',@csv,1)-1) as val, SUBSTRING(@csv,charindex(',',@csv,1)+1,len(@csv)) as rem
UNION ALL
select SUBSTRING(a.rem,1,charindex(',',a.rem,1)-1)as val, SUBSTRING(a.rem,charindex(',',a.rem,1)+1,len(A.rem))
from cte a where LEN(a.rem)>=1
) select val from cte

它是如此简单,你可以通过以下查询:

DECLARE @str NVARCHAR(MAX)='ControlID_05436b78-04ba-9667-fa01-9ff8c1b7c235,3'
SELECT LEFT(@str, CHARINDEX(',',@str)-1),RIGHT(@str,LEN(@str)-(CHARINDEX(',',@str)))
ALTER FUNCTION [dbo].[StringListTo] (@StringList Nvarchar(max),@Separators char(1),@start int, @index int )
RETURNS nvarchar(max)
AS
BEGIN
declare @out Nvarchar(max)
declare @i int
declare @start_old int
set @start=@start+1
set @i=1
while(@i<=@index)
begin
set @start_old=@start
set @start=CHARINDEX('.',@StringList,@start+1)
if (@start>0)
begin
set @out=Substring(@StringList,@start_old+1,@start-@start_old-1)
end
else
begin
set @out=Substring(@StringList,@start_old+1,len(@StringList)-1)
end
set @i=@i+1
end
RETURN @out
END;
DECLARE @INPUT VARCHAR (MAX)='N,A,R,E,N,D,R,A'
DECLARE @ELIMINATE_CHAR CHAR (1)=','
DECLARE @L_START INT=1
DECLARE @L_END INT=(SELECT LEN (@INPUT))
DECLARE @OUTPUT CHAR (1)


WHILE @L_START <=@L_END
BEGIN
SET @OUTPUT=(SUBSTRING (@INPUT,@L_START,1))
IF @OUTPUT!=@ELIMINATE_CHAR
BEGIN
PRINT @OUTPUT
END
SET @L_START=@L_START+1
END

使用 SQLServer2016,我们可以使用 string _ split 来完成以下任务:

create table commasep (
id int identity(1,1)
,string nvarchar(100) )


insert into commasep (string) values ('John, Adam'), ('test1,test2,test3')


select id, [value] as String from commasep
cross apply string_split(string,',')
select distinct modelFileId,F4.*
from contract
cross apply (select XmlList=convert(xml, '<x>'+replace(modelFileId,';','</x><x>')+'</x>').query('.')) F2
cross apply (select mfid1=XmlNode.value('/x[1]','varchar(512)')
,mfid2=XmlNode.value('/x[2]','varchar(512)')
,mfid3=XmlNode.value('/x[3]','varchar(512)')
,mfid4=XmlNode.value('/x[4]','varchar(512)') from XmlList.nodes('x') F3(XmlNode)) F4
where modelFileId like '%;%'
order by modelFileId

尝试这样做(更改“ to”的实例或您想使用的任何分隔符)

CREATE FUNCTION dbo.Wordparser
(
@multiwordstring VARCHAR(255),
@wordnumber      NUMERIC
)
returns VARCHAR(255)
AS
BEGIN
DECLARE @remainingstring VARCHAR(255)
SET @remainingstring=@multiwordstring


DECLARE @numberofwords NUMERIC
SET @numberofwords=(LEN(@remainingstring) - LEN(REPLACE(@remainingstring, ' ', '')) + 1)


DECLARE @word VARCHAR(50)
DECLARE @parsedwords TABLE
(
line NUMERIC IDENTITY(1, 1),
word VARCHAR(255)
)


WHILE @numberofwords > 1
BEGIN
SET @word=LEFT(@remainingstring, CHARINDEX(' ', @remainingstring) - 1)


INSERT INTO @parsedwords(word)
SELECT @word


SET @remainingstring= REPLACE(@remainingstring, Concat(@word, ' '), '')
SET @numberofwords=(LEN(@remainingstring) - LEN(REPLACE(@remainingstring, ' ', '')) + 1)


IF @numberofwords = 1
BREAK


ELSE
CONTINUE
END


IF @numberofwords = 1
SELECT @word = @remainingstring
INSERT INTO @parsedwords(word)
SELECT @word


RETURN
(SELECT word
FROM   @parsedwords
WHERE  line = @wordnumber)


END

示例用法:

SELECT dbo.Wordparser(COLUMN, 1),
dbo.Wordparser(COLUMN, 2),
dbo.Wordparser(COLUMN, 3)
FROM   TABLE

这招对我很管用

CREATE FUNCTION [dbo].[SplitString](
@delimited NVARCHAR(MAX),
@delimiter NVARCHAR(100)
) RETURNS @t TABLE ( 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

可以使用拆分函数。

SELECT
(select top 1 item from dbo.Split(FullName,',') where id=1 ) as Name,
(select top 1 item from dbo.Split(FullName,',') where id=2 ) as Surname,
FROM MyTbl
Select distinct PROJ_UID,PROJ_NAME,RES_UID from E2E_ProjectWiseTimesheetActuals
where   CHARINDEX(','+cast(PROJ_UID as varchar(8000))+',', @params) > 0 and  CHARINDEX(','+cast(RES_UID as varchar(8000))+',', @res) > 0

您可以使用 桌面价值函数 STRING_SPLIT,该函数仅在兼容级别130下可用。如果数据库兼容级别低于130,SQLServer 将无法查找和执行 STRING_SPLIT函数。可以使用以下命令更改数据库的兼容级别:

ALTER DATABASE DatabaseName SET COMPATIBILITY_LEVEL = 130

语法

SELECT * FROM STRING_SPLIT ( string, separator )

请参阅这里的文档

我认为以下功能将为您工作:

您必须首先在 SQL 中创建一个函数

CREATE FUNCTION [dbo].[fn_split](
@str VARCHAR(MAX),
@delimiter CHAR(1)
)
RETURNS @returnTable TABLE (idx INT PRIMARY KEY IDENTITY, item VARCHAR(8000))
AS
BEGIN
DECLARE @pos INT
SELECT @str = @str + @delimiter
WHILE LEN(@str) > 0
BEGIN
SELECT @pos = CHARINDEX(@delimiter,@str)
IF @pos = 1
INSERT @returnTable (item)
VALUES (NULL)
ELSE
INSERT @returnTable (item)
VALUES (SUBSTRING(@str, 1, @pos-1))
SELECT @str = SUBSTRING(@str, @pos+1, LEN(@str)-@pos)
END
RETURN
END

你可以这样调用这个函数:

select * from fn_split('1,24,5',',')

实施方法:

Declare @test TABLE (
ID VARCHAR(200),
Data VARCHAR(200)
)


insert into @test
(ID, Data)
Values
('1','Cleo,Smith')




insert into @test
(ID, Data)
Values
('2','Paul,Grim')


select ID,
(select item from fn_split(Data,',') where idx in (1)) as Name ,
(select item from fn_split(Data,',') where idx in (2)) as Surname
from @test

结果会是这样的:

enter image description here

这个函数是最快的:

CREATE FUNCTION dbo.F_ExtractSubString
(
@String VARCHAR(MAX),
@NroSubString INT,
@Separator VARCHAR(5)
)
RETURNS VARCHAR(MAX) AS
BEGIN
DECLARE @St INT = 0, @End INT = 0, @Ret VARCHAR(MAX)
SET @String = @String + @Separator
WHILE CHARINDEX(@Separator, @String, @End + 1) > 0 AND @NroSubString > 0
BEGIN
SET @St = @End + 1
SET @End = CHARINDEX(@Separator, @String, @End + 1)
SET @NroSubString = @NroSubString - 1
END
IF @NroSubString > 0
SET @Ret = ''
ELSE
SET @Ret = SUBSTRING(@String, @St, @End - @St)
RETURN @Ret
END
GO

示例用法:

SELECT dbo.F_ExtractSubString(COLUMN, 1, ', '),
dbo.F_ExtractSubString(COLUMN, 2, ', '),
dbo.F_ExtractSubString(COLUMN, 3, ', ')
FROM   TABLE

试试以下方法:

USE TRIAL
GO
CREATE TABLE DETAILS
(
ID INT,
NAME VARCHAR(50),
ADDRESS VARCHAR(50)
)


INSERT INTO DETAILS
VALUES (100, 'POPE-JOHN-PAUL','VATICAN CIT|ROME|ITALY')
,(240, 'SIR-PAUL-McARTNEY','NEWYORK CITY|NEWYORK|USA')
,(460,'BARRACK-HUSSEIN-OBAMA','WHITE HOUSE|WASHINGTON|USA')
,(700, 'PRESIDENT-VLADAMIR-PUTIN','RED SQUARE|MOSCOW|RUSSIA')
,(950, 'NARENDRA-DAMODARDAS-MODI','10 JANPATH|NEW DELHI|INDIA')

质疑:

select [ID]
,[NAME]
,[ADDRESS]
,REPLACE(LEFT(NAME, CHARINDEX('-', NAME)),'-',' ') as First_Name
,CASE
WHEN CHARINDEX('-',REVERSE(NAME))+ CHARINDEX('-',NAME) < LEN(NAME)
THEN  SUBSTRING(NAME, CHARINDEX('-', (NAME)) + 1, LEN(NAME) - CHARINDEX('-', REVERSE(NAME)) - CHARINDEX('-', NAME))
ELSE 'NULL'
END AS Middle_Name
,REPLACE(REVERSE( SUBSTRING( REVERSE(NAME), 1, CHARINDEX('-',REVERSE(NAME)))), '-','') AS Last_Name
,REPLACE(LEFT(ADDRESS, CHARINDEX('|', ADDRESS)),'|',' ') AS Locality
,CASE
WHEN CHARINDEX('|',REVERSE(ADDRESS))+ CHARINDEX('|',ADDRESS) < LEN(ADDRESS)
THEN SUBSTRING(ADDRESS, CHARINDEX('|', (ADDRESS))+1, LEN(ADDRESS)-CHARINDEX('|', REVERSE(ADDRESS))-CHARINDEX('|',ADDRESS))
ELSE 'Null'
END AS STATE
,REPLACE(REVERSE(SUBSTRING(REVERSE(ADDRESS),1 ,CHARINDEX('|',REVERSE(ADDRESS)))),'|','') AS Country
FROM DETAILS


SELECT CHARINDEX('-', REVERSE(NAME)) AS LAST,CHARINDEX('-',NAME)AS FIRST, LEN(NAME) AS LENGTH
FROM DETAILS


SELECT SUBSTRING(NAME, CHARINDEX('-', (NAME))+1, LEN(NAME) -CHARINDEX('-', REVERSE(NAME)) - CHARINDEX('-', NAME))
FROM DETAILS

如果你对密码有任何疑问,请告诉我

ALTER function get_occurance_index(@delimiter varchar(1),@occurence int,@String varchar(100))
returns int
AS Begin
--Declare @delimiter varchar(1)=',',@occurence int=2,@String varchar(100)='a,b,c'
Declare @result int
;with T as (
select 1 Rno,0 as row, charindex(@delimiter, @String) pos,@String st
union all
select Rno+1,pos + 1, charindex(@delimiter, @String, pos + 1), @String
from T
where pos > 0
)
select  @result=pos
from T
where pos > 0   and rno = @occurence
return isnull(@result,0)
ENd




declare @data as table (data varchar(100))
insert into @data values('1,2,3')
insert into @data values('aaa,bbbbb,cccc')
select top  3 Substring (data,0,dbo.get_occurance_index( ',',1,data)) ,--First Record always starts with 0
Substring (data,dbo.get_occurance_index( ',',1,data)+1,dbo.get_occurance_index( ',',2,data)-dbo.get_occurance_index( ',',1,data)-1) ,
Substring (data,dbo.get_occurance_index( ',',2,data)+1,len(data)) , -- Last record cant be more than len of actual data
data
From @data
CREATE FUNCTION [dbo].[fn_split_string_to_column] (
@string NVARCHAR(MAX),
@delimiter CHAR(1)
)
RETURNS @out_put TABLE (
[column_id] INT IDENTITY(1, 1) NOT NULL,
[value] NVARCHAR(MAX)
)
AS
BEGIN
DECLARE @value NVARCHAR(MAX),
@pos INT = 0,
@len INT = 0


SET @string = CASE
WHEN RIGHT(@string, 1) != @delimiter
THEN @string + @delimiter
ELSE @string
END


WHILE CHARINDEX(@delimiter, @string, @pos + 1) > 0
BEGIN
SET @len = CHARINDEX(@delimiter, @string, @pos + 1) - @pos
SET @value = SUBSTRING(@string, @pos, @len)


INSERT INTO @out_put ([value])
SELECT LTRIM(RTRIM(@value)) AS [column]


SET @pos = CHARINDEX(@delimiter, @string, @pos + @len) + 1
END


RETURN
END

我重写了上面的一个答案,并做得更好:

CREATE FUNCTION [dbo].[CSVParser]
(
@s        VARCHAR(255),
@idx      NUMERIC
)
RETURNS VARCHAR(12)
BEGIN
DECLARE @comma int
SET @comma = CHARINDEX(',', @s)
WHILE 1=1
BEGIN
IF @comma=0
IF @idx=1
RETURN @s
ELSE
RETURN ''


IF @idx=1
BEGIN
DECLARE @word VARCHAR(12)
SET @word=LEFT(@s, @comma - 1)
RETURN @word
END


SET @s = RIGHT(@s,LEN(@s)-@comma)
SET @comma = CHARINDEX(',', @s)
SET @idx = @idx - 1
END
RETURN 'not used'
END

示例用法:

SELECT dbo.CSVParser(COLUMN, 1),
dbo.CSVParser(COLUMN, 2),
dbo.CSVParser(COLUMN, 3)
FROM   TABLE

问题很简单,但问题是热点:)

因此,我为 String _ split ()创建了一些包装器,支点以更通用的方式产生了这些包装器。它是返回值(nn,value1,value2,... ,value50)的 table 函数——对于大多数 CSV 行来说足够了。如果有更多的值,它们将换行到下一行-你好表示行号。将第三个参数 @ columnCnt = [ yourNumber ]设置为在特定位置换行:

alter FUNCTION fn_Split50
(
@str varchar(max),
@delim char(1),
@columnCnt int = 50
)
RETURNS TABLE
AS
RETURN
(
SELECT *
FROM (SELECT
nn = (nn - 1) / @columnCnt + 1,
nnn = 'value' + cast(((nn - 1) % @columnCnt) + 1 as varchar(10)),
value
FROM (SELECT
nn = ROW_NUMBER() over (order by (select null)),
value
FROM string_split(@str, @delim) aa
) aa
where nn > 0
) bb
PIVOT
(
max(value)
FOR nnn IN (
value1, value2, value3, value4, value5, value6, value7, value8, value9, value10,
value11, value12, value13, value14, value15, value16, value17, value18, value19, value20,
value21, value22, value23, value24, value25, value26, value27, value28, value29, value30,
value31, value32, value33, value34, value35, value36, value37, value38, value39, value40,
value41, value42, value43, value44, value45, value46, value47, value48, value49, value50
)
) AS PivotTable
)

使用示例:

select * from dbo.fn_split50('zz1,aa2,ss3,dd4,ff5', ',', DEFAULT)

result 1

select * from dbo.fn_split50('zz1,aa2,ss3,dd4,ff5,gg6,hh7,jj8,ww9,qq10', ',', 3)

enter image description here

select * from dbo.fn_split50('zz1,11,aa2,22,ss3,33,dd4,44,ff5,55,gg6,66,hh7,77,jj8,88,ww9,99,qq10,1010', ',',2)

enter image description here

希望,这会有所帮助:)

可以使用 SQLServerSTRING_SPLIT函数:

STRING_SPLIT ( string , separator )

试试这个

CREATE FUNCTION [dbo].[Split]
(
@ListOfValues varchar(max),
@ValueSeparator varchar(10)
)
RETURNS @ListOfValuesInRows TABLE
(
Value varchar(max)
)
AS
BEGIN


IF Len(@ListOfValues) = 0
RETURN


if @ValueSeparator <> ' '
Begin
WHILE CHARINDEX(@ValueSeparator, @ListOfValues) > 0
BEGIN


INSERT INTO @ListOfValuesInRows
SELECT LTRIM(RTRIM(SUBSTRING(@ListOfValues, 1, CHARINDEX(@ValueSeparator, @ListOfValues)-1)))


SET @ListOfValues = SubString(@ListOfValues, CharIndex(@ValueSeparator, @ListOfValues)+Len(@ValueSeparator), Len(@ListOfValues))


END


INSERT INTO @ListOfValuesInRows
SELECT LTRIM(RTRIM(@ListOfValues))
End
Else
BEGIN
DECLARE @xml XML;
SET @xml = N'<t>' + REPLACE(@ListOfValues, @ValueSeparator, '</t><t>') + '</t>';
INSERT INTO @ListOfValuesInRows (Value)
SELECT LTRIM(RTRIM(r.value( '.', 'varchar(MAX)' ))) AS item
FROM @xml.nodes( '/t' ) AS records( r )


END


RETURN


END

这是一个老问题,但是如果有可能升级到 SQLServer2017 + ,基于 JSON 的方法也是一种选择。我们的想法是进行适当的转变:

  • 将存储在 String列中的文本转换为有效的 JSON 数组(Cleo, Smith转换为 ["Cleo"," Smith"]) ,并使用 JSON_VALUE()解析该数组。

  • 将存储在 String列中的文本转换为有效的嵌套 JSON 数组(Cleo, Smith转换为 [["Cleo"," Smith"]]) ,并使用 OPENJSON()和显式模式(列定义)解析该数组。

表:

SELECT [Value], [String]
INTO Data
FROM (VALUES
(1, 'Cleo, Smith'),
(2, 'John, Smith'),
(3, 'Marian')
) v ([Value], [String])

使用 JSON_VALUE()的语句:

SELECT
[Value],
TRIM(JSON_VALUE(CONCAT('["', REPLACE(STRING_ESCAPE([String], 'json'), ',', '","'), '"]'), 'lax $[0]')) AS Name,
TRIM(JSON_VALUE(CONCAT('["', REPLACE(STRING_ESCAPE([String], 'json'), ',', '","'), '"]'), 'lax $[1]')) AS Surname
FROM Data

使用 OPENJSON()的语句:

SELECT d.[Value], TRIM(j.[Name]) AS [Name], TRIM(j.[Surname]) AS [Surname]
FROM Data d
OUTER APPLY OPENJSON(CONCAT('[["', REPLACE(STRING_ESCAPE(d.[String], 'json'), ',', '","'), '"]]')) WITH (
Name varchar(100) 'lax $[0]',
Surname varchar(100) 'lax $[1]'
) j

结果:

Value  Name   Surname
---------------------
1      Cleo   Smith
2      John   Smith
3      Marian

另外,使用这种技术,您可以通过添加适当的 JSON path轻松地解析包含两列以上的文本。

我说了:

drop table if exists #test;
create table #test(valor varchar(200));
insert into #test values ('Cleo, Smith'), ('Jhon');
select
*
,REVERSE(PARSENAME(REPLACE(REVERSE(valor), ',', '.'), 1)) as name
,REVERSE(PARSENAME(REPLACE(REVERSE(valor), ',', '.'), 2)) as Surname
,REVERSE(PARSENAME(REPLACE(REVERSE(valor), ',', '.'), 3)) as other
from #test;


/*
+-----------+----+-------+-----+
|valor      |name|Surname|other|
+-----------+----+-------+-----+
|Cleo, Smith|Cleo| Smith |NULL |
|Jhon       |Jhon|NULL   |NULL |
+-----------+----+-------+-----+
*/