我在Table1中有一个列,其中的字符串由commma分隔:
Id Val
1 ,4
2 ,3,1,0
3 NULL
4 ,5,2
是否有一种简单的方法可以从该列中拆分并获取任何值, 例如
SELECT值(1)FROM Table1应该得到
Id Val
1 4
2 3
3 NULL
4 5
SELECT值(2)FROM Table1应该得到
Id Val
1 NULL
2 1
3 NULL
4 2
谢谢!
答案 0 :(得分:2)
在列中存储逗号分隔值总是很麻烦,请考虑更改表结构
要完成此操作,请创建一个拆分字符串函数。这是将字符串拆分为单个行的最佳方法之一。参考http://www.sqlservercentral.com/articles/Tally+Table/72993/
CREATE FUNCTION [dbo].[DelimitedSplit8K]
(@pString VARCHAR(8000), @pDelimiter CHAR(1))
RETURNS TABLE WITH SCHEMABINDING AS
RETURN
--===== "Inline" CTE Driven "Tally Table" produces values from 0 up to 10,000...
-- enough to cover NVARCHAR(4000)
WITH E1(N) AS (
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
), --10E+1 or 10 rows
E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
cteTally(N) AS (--==== This provides the "base" CTE and limits the number of rows right up front
-- for both a performance gain and prevention of accidental "overruns"
SELECT TOP (ISNULL(DATALENGTH(@pString),0)) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
),
cteStart(N1) AS (--==== This returns N+1 (starting position of each "element" just once for each delimiter)
SELECT 1 UNION ALL
SELECT t.N+1 FROM cteTally t WHERE SUBSTRING(@pString,t.N,1) = @pDelimiter
),
cteLen(N1,L1) AS(--==== Return start and length (for use in substring)
SELECT s.N1,
ISNULL(NULLIF(CHARINDEX(@pDelimiter,@pString,s.N1),0)-s.N1,8000)
FROM cteStart s
)
--===== Do the actual split. The ISNULL/NULLIF combo handles the length for the final element when no delimiter is found.
SELECT ItemNumber = ROW_NUMBER() OVER(ORDER BY l.N1),
Item = SUBSTRING(@pString, l.N1, l.L1)
FROM cteLen l
调用函数
SELECT *
FROM yourtable
CROSS apply (SELECT CASE WHEN LEFT(val, 1) = ',' THEN Stuff(val, 1, 1, '') ELSE val END) cs (cleanedval)
CROSS apply [dbo].[Delimitedsplit8k](cs.cleanedval, ',')
WHERE ItemNumber = 1
SELECT *
FROM yourtable
CROSS apply (SELECT CASE WHEN LEFT(val, 1) = ',' THEN Stuff(val, 1, 1, '') ELSE val END) cs (cleanedval)
CROSS apply [dbo].[Delimitedsplit8k](cs.cleanedval, ',')
WHERE ItemNumber = 2
答案 1 :(得分:1)
使用递归CTE尝试此逻辑
DECLARE @Pos INT = 2
DECLARE @T TABLE
(
Id INT,
Val VARCHAR(50)
)
INSERT INTO @T
VALUES(1,',4'),(2,',3,1,0'),(3,NULL),(4,',5,2')
;WITH CTE
AS
(
SELECT
Id,
SeqNo = 0,
MyStr = SUBSTRING(Val,CHARINDEX(',',Val)+1,LEN(Val)),
Num = REPLACE(SUBSTRING(Val,1,CHARINDEX(',',Val)),',','')
FROM @T
UNION ALL
SELECT
Id,
SeqNo = SeqNo+1,
MyStr = CASE WHEN CHARINDEX(',',MyStr)>0
THEN SUBSTRING(MyStr,CHARINDEX(',',MyStr)+1,LEN(MyStr))
ELSE NULL END,
Num = CASE WHEN CHARINDEX(',',MyStr)>0
THEN REPLACE(SUBSTRING(MyStr,1,CHARINDEX(',',MyStr)),',','')
ELSE MyStr END
FROM CTE
WHERE ISNULL(REPLACE(MyStr,',',''),'')<>''
)
SELECT
T.Id,
CTE.Num
FROM @T t
LEFT JOIN CTE
ON T.Id = cte.Id
AND SeqNo = @Pos
以上
的输出答案 2 :(得分:1)
使用Parse / Split函数和OUTER APPLY的另一个选项
示例强>
Declare @YourTable Table ([Id] int,[Val] varchar(50))
Insert Into @YourTable Values
(1,',4')
,(2,',3,1,0')
,(3,NULL)
,(4,',5,2')
Select A.ID
,Val = B.RetVal
From @YourTable A
Outer Apply (
Select * From [dbo].[tvf-Str-Parse](A.Val,',')
Where RetSeq = 2
) B
<强>返回强>
ID Val
1 4
2 3
3 NULL
4 5
感兴趣的UDF
CREATE FUNCTION [dbo].[tvf-Str-Parse] (@String varchar(max),@Delimiter varchar(10))
Returns Table
As
Return (
Select RetSeq = Row_Number() over (Order By (Select null))
,RetVal = LTrim(RTrim(B.i.value('(./text())[1]', 'varchar(max)')))
From (Select x = Cast('<x>' + replace((Select replace(@String,@Delimiter,'§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
);
答案 3 :(得分:1)
Declare @t TABLE (Id INT , Val VARCHAR(100))
INSERT INTO @t VALUES
(1 , '4'),
(2 , '3,1,0'),
(3 , NULL),
(4 , '5,2')
CREATE FUNCTION [dbo].[fn_xml_Splitter]
(
@delimited nvarchar(max)
, @delimiter nvarchar(1)
, @Position INT = NULL
)
RETURNS TABLE
AS
RETURN
(
SELECT Item
FROM (
SELECT Split.a.value('.', 'VARCHAR(100)') Item
, ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) ItemNumber
FROM
(SELECT Cast ('<X>' + Replace(@delimited, @delimiter, '</X><X>')
+ '</X>' AS XML) AS Data
) AS t CROSS APPLY Data.nodes ('/X') AS Split(a)
)x
WHERE x.ItemNumber = @Position OR @Position IS NULL
);
GO
现在您可以通过两种不同的方式调用此函数。
1。要在特定位置返回Item,请在函数的第3个参数中指定位置:
SELECT *
FROM @t t
CROSS APPLY [dbo].[fn_xml_Splitter](t.Val , ',', 1)
2。要返回所有项目,请在函数的第3个参数中指定关键字DEFUALT
:
SELECT *
FROM @t t
CROSS APPLY [dbo].[fn_xml_Splitter](t.Val , ',', DEFAULT)
答案 4 :(得分:1)
以下是使用CTE并将CSV转换为XML的示例:
DECLARE @Test TABLE (
CsvData VARCHAR(10)
);
INSERT INTO @Test (CsvData)
VALUES
('1,2,3'),
(',4,5,7'),
(NULL),
(',3,');
WITH XmlData AS (
SELECT CONVERT(XML, '<val>' + REPLACE(CsvData, ',', '</val><val>') + '</val>') [CsvXml]
FROM @Test
)
SELECT xd.CsvXml.value('val[2]', 'VARCHAR(10)')
FROM XmlData xd;
这将输出:
2
4
NULL
3
要显示的列由XPath查询控制。在这种情况下,val[2]
。
这里的主要优点是不需要用户定义的功能。