将逗号分隔的字符串转换为单独的行
将逗号分隔的字符串转换为单独的行
我有一个像这样的SQL表:\n
SomeID | OtherID | Data |
---|---|---|
abcdef-..... | cdef123-... | 18,20,22 |
abcdef-..... | 4554a24-... | 17,19 |
987654-..... | 12324a2-... | 13,19,20 |
\n是否有一种查询方式可以执行类似于SELECT OtherID, SplitData WHERE SomeID = \'abcdef-.......\'
的查询,返回单独的行,像这样:\n
OtherID | SplitData |
---|---|
cdef123-... | 18 |
cdef123-... | 20 |
cdef123-... | 22 |
4554a24-... | 17 |
4554a24-... | 19 |
\n基本上将我的数据按逗号分割成单独的行?\n我知道将一个逗号分隔的
字符串存储到关系数据库中听起来很蠢,但在消费者应用程序的正常使用情况下,这真的很有帮助。\n我不想在应用程序中进行拆分,因为我需要分页,所以我想在重构整个应用程序之前探索一些选项。\n这是一个SQL Server 2008
(非R2)的问题。
在这段代码中,作者使用了XML分割方法来将逗号分隔的字符串转换为单独的行。然而,使用这种方法需要确保值中不包含非法的XML字符。
如果想要新列只显示拆分字符串的第一个字符,可以对代码进行改写。
作者表示,XML分割方法几乎和While循环或递归CTE一样慢,强烈建议避免使用该方法。相比之下,作者推荐使用DelimitedSplit8K方法,除了2016年的Split_String()函数或者一个良好编写的CLR函数之外,它能更好地处理这个问题。
然而,XML分割方法有一个优点,即它可以在不允许/不可能创建函数(即使是临时函数)的环境中运行。
总之,虽然这种方法在任何环境下都可能相对较慢,但对于不允许创建函数的环境来说,XML分割方法是一个可行的选择。
问题出现的原因是在较旧版本的SQL Server中无法将逗号分隔的字符串转换为单独的行。在这种情况下,需要使用其他方法(如XML、Tally表、while循环等)来拆分字符串。
解决方法是在SQL Server 2016中引入了一个名为STRING_SPLIT的函数,该函数可以将逗号分隔的字符串拆分为单独的行。使用该函数的示例代码如下:
select OtherID, cs.Value --SplitData from yourtable cross apply STRING_SPLIT (Data, ',') cs
该函数已经取代了其他拆分字符串的方法,如XML、Tally表、while循环等。对于较旧的版本,可以使用Tally表的方法来拆分字符串。以下是一个使用Tally表的拆分字符串函数的示例代码:
CREATE FUNCTION [dbo].[DelimitedSplit8K] ( VARCHAR(8000), CHAR(1)) RETURNS TABLE WITH SCHEMABINDING AS RETURN --代码省略--
在使用STRING_SPLIT函数时,如果原始数据中包含要拆分的列(本例中为'Data'列)中的NULL值,则在使用CROSS APPLY时,结果中将省略这些行。为了保留这些行,可以使用OUTER APPLY。
需要注意的是,使用STRING_SPLIT函数需要兼容性级别130以及SQL Server 2016。如果服务器的版本较旧,将无法使用该函数。
总之,通过引入STRING_SPLIT函数,SQL Server 2016解决了在较旧版本中无法将逗号分隔的字符串转换为单独的行的问题。使用该函数可以简化拆分字符串的操作,提高性能和效率。对于较旧版本,可以使用Tally表的方法来实现类似的功能。
将逗号分隔的字符串转换为单独的行
在SQL Server中,可以使用递归函数来实现将逗号分隔的字符串转换为单独的行。下面是一个示例表和查询的代码:
示例表:
CREATE TABLE Testdata
(
SomeID INT,
OtherID INT,
String VARCHAR(MAX)
);
INSERT Testdata SELECT 1, 9, '18,20,22';
INSERT Testdata SELECT 2, 8, '17,19';
INSERT Testdata SELECT 3, 7, '13,19,20';
INSERT Testdata SELECT 4, 6, '';
INSERT Testdata SELECT 9, 11, '1,2,3,4';
查询代码:
WITH tmp(SomeID, OtherID, DataItem, String) AS
(
SELECT
SomeID,
OtherID,
LEFT(String, CHARINDEX(',', String + ',') - 1),
STUFF(String, 1, CHARINDEX(',', String + ','), '')
FROM Testdata
UNION all
SELECT
SomeID,
OtherID,
LEFT(String, CHARINDEX(',', String + ',') - 1),
STUFF(String, 1, CHARINDEX(',', String + ','), '')
FROM tmp
WHERE
String > ''
)
SELECT
SomeID,
OtherID,
DataItem
FROM tmp
ORDER BY SomeID;
上述查询的输出结果如下:
SomeID | OtherID | DataItem
--------+---------+----------
1 | 9 | 18
1 | 9 | 20
1 | 9 | 22
2 | 8 | 17
2 | 8 | 19
3 | 7 | 13
3 | 7 | 19
3 | 7 | 20
4 | 6 |
9 | 11 | 1
9 | 11 | 2
9 | 11 | 3
9 | 11 | 4
该查询代码在将Data列的数据类型从varchar(max)更改为varchar(4000)时会出现问题,例如create table Testdata(SomeID int, OtherID int, Data varchar(4000))。可能是因为UNION ALL之前和之后的部分返回的类型与LEFT函数不同。如果是我个人的话,一旦达到4000,我不明白为什么不直接使用MAX类型...
对于大量的值,这可能会超出CTE递归限制。这时可以使用OPTION (maxrecursion 0)来解决。
LEFT函数可能需要进行CAST才能正常工作...例如LEFT(CAST(Data AS VARCHAR(MAX))....
如果需要在PostgreSQL中使用这个查询,需要进行一些修改:
WITH -> WITH RECURSIVE
+ -> ||
CHARINDEX -> POSITION
STUFF($a, $b, $c, $d) -> OVERLAY($a PLACING $d FROM $b FOR $c)
感谢您的提问!第二个SELECT语句中WHERE String > ''的作用是排除空字符串的行。