使用 string_split 函数查询更新字符串
Posted
技术标签:
【中文标题】使用 string_split 函数查询更新字符串【英文标题】:Query to update strings using string_split function 【发布时间】:2021-09-24 20:13:49 【问题描述】:我正在尝试更新表格中数据格式如下的列:
Id | ColA
----------
1 Peter,John:Ryan,Jack:Evans,Chris
2 Peter,John:Ryan,Jack
3 Hank,Tom
4
5 Cruise,Tom
我需要将字符串按':'
拆分并删除','
并需要反转名称并再次附加以:
分隔的相同数据,最后数据应如图所示
Id | ColA
----------
1 John Peter:Jack Ryan:Chris Evans
2 John Peter:Jack Ryan
3 Tom Hank
4
5 Tom Cruise
请告诉我如何实现这一目标
我尝试使用Replace
和Substring
,但是如果我们的数据有些用两个冒号分隔,有些用单冒号分隔,我们该怎么做。
有什么方法可以识别和实现上述格式的数据。
【问题讨论】:
根据问题指南,请展示您的尝试并告诉我们您发现了什么(在本网站或其他地方)以及为什么它不能满足您的需求。 提问时,您需要提供minimal reproducible example: (1) DDL 和样本数据填充,即 CREATE 表和 INSERT T-SQL 语句。 (2) 你需要做什么,即逻辑和你的代码尝试在 T-SQL 中实现它。 (3) 期望的输出,基于上面#1 中的样本数据。 (4) 您的 SQL Server 版本 (SELECT @@version;)。 您有机会尝试提出的解决方案吗? 规范化你的架构。见"Is storing a delimited list in a database column really that bad?"(剧透:是的。)。 【参考方案1】:这是适用于 SQL Server 2008 及更高版本的解决方案。
它基于 XML 和 XQuery。
使用 XQuery 的 FLWOR 表达式可以标记奇数和偶数 XML 元素。剩下的只是几个 REPLACE()
函数调用来组成所需的输出。
SQL
-- DDL and sample data population, start
DECLARE @tbl TABLE (ID INT IDENTITY PRIMARY KEY, tokens VARCHAR(1024));
INSERT INTO @tbl (tokens) VALUES
('Peter,John:Ryan,Jack:Evans,Chris'),
('Peter,John:Ryan,Jack'),
('Hank,Tom'),
(''),
('Cruise,Tom');
-- DDL and sample data population, end
DECLARE @separator CHAR(1) = ':'
, @comma CHAR(1) = ',';
SELECT ID, tokens
, REPLACE(REPLACE(c.query('
for $x in /root/r[position() mod 2 eq 0]
let $pos := count(root/r[. << $x])
return concat($x, sql:variable("@comma"), (/root/r[$pos])[1])
').value('text()[1]', 'VARCHAR(8000)')
, SPACE(1), @separator), @comma, SPACE(1)) AS result
FROM @tbl
CROSS APPLY (SELECT CAST('<root><r><![CDATA[' +
REPLACE(REPLACE(tokens,@comma,@separator), @separator, ']]></r><r><![CDATA[') +
']]></r></root>' AS XML)) AS t1(c)
ORDER BY ID;
输出
+----+----------------------------------+----------------------------------+
| ID | tokens | result |
+----+----------------------------------+----------------------------------+
| 1 | Peter,John:Ryan,Jack:Evans,Chris | John Peter:Jack Ryan:Chris Evans |
| 2 | Peter,John:Ryan,Jack | John Peter:Jack Ryan |
| 3 | Hank,Tom | Tom Hank |
| 4 | | NULL |
| 5 | Cruise,Tom | Tom Cruise |
+----+----------------------------------+----------------------------------+
SQL #2(不要尝试,它不会起作用)
不幸的是,SQL Server 甚至不完全支持 XQuery 1.0 标准。 XQuery 3.1 是最新的标准。 XQuery 1.0 函数 fn:substring-after()
和 fn:substring-before()
严重缺失。
在梦境中,解决方案会简单得多,如下所示:
SELECT *
, c.query('
for $x in /root/r
return concat(fn:substring-after($x, ","), ",", fn:substring-before($x, ","))
')
FROM @tbl
CROSS APPLY (SELECT TRY_CAST('<root><r><![CDATA[' +
REPLACE(tokens, @separator, ']]></r><r><![CDATA[') +
']]></r></root>' AS XML)) AS t1(c);
请投票支持以下改进 SQL Server 的建议: SQL Server vNext (post 2019) and NoSQL functionality
它成为 SQL Server 最流行的请求之一。 目前的投票总数为 590,而且还在增加。
【讨论】:
感谢您的建议。我遇到了一些错误,例如 XML Parsing:line 1, 'Incorrect document syntax' @AMDI,请编辑您的原始问题并添加“DDL 和样本数据总体”部分,就像我的回答一样。 我能够从此答案运行提供的脚本(直接复制和粘贴),并且此答案中提供的输出与提供的脚本的输出相匹配。 @trenton-ftw,看来 OP 有不同的数据集......不是问题中提供的。 @YitzhakKhabinsky 我认为你是对的。在您的解决方案中,您可以将CAST
替换为TRY_CAST
。 OP 应该能够使用WHERE tokens IS NOT NULL AND tokens <> '' AND c IS NULL
之类的过滤器找出哪些行有违规数据。我相信您已经意识到这一点,但可能对 OP 进行故障排除或至少找到此解决方案有问题的令牌有用。【参考方案2】:
这样的事情应该可以工作:
CREATE TABLE YourTableNameHere (
Id int NULL
,ColA varchar(1000) NULL
);
INSERT INTO YourTableNameHere (Id,ColA) VALUES
(1, 'Peter,John:Ryan,Jack:Evans,Chris')
,(2, 'Peter,John:Ryan,Jack')
,(3, 'Hank,Tom')
,(4, '')
,(5, 'Cruise,Tom');
SELECT
tbl.Id
,STUFF((SELECT
CONCAT(':'
,RIGHT(REPLACE(ss.value, ',', ' '), LEN(REPLACE(ss.value, ',', ' ')) - CHARINDEX(' ', REPLACE(ss.value, ',', ' '), 1)) /*first name*/
,' '
,CASE WHEN CHARINDEX(',', ss.value, 1) > 1 THEN LEFT(REPLACE(ss.value, ',', ' '), CHARINDEX(' ', REPLACE(ss.value, ',', ' '), 1) - 1) /*last name*/ ELSE '' END)
FROM
YourTableNameHere AS tbl_inner
CROSS APPLY string_split(tbl_inner.ColA, ':') AS ss
WHERE
tbl_inner.Id = tbl.Id
FOR XML PATH('')), 1, 1, '') AS ColA
FROM
YourTableNameHere AS tbl;
这使用FOR XML
子句中的string_split
函数将ColA
中的值拆分为:
字符,然后将,
替换为空格,解析到空格的左侧和右侧,然后重新组合由:
字符分隔的解析值。
这里要注意一点,per Microsoftstring_split
的输出不保证与输入的顺序相同:
注意 输出的顺序可能会有所不同,因为不能保证顺序与输入字符串中子字符串的顺序匹配。
因此,为了保证此函数的输出将按照输入列中存在的相同顺序连接名称,您需要实现自己的函数来拆分字符串或提出一些标准按一定顺序组合它们。例如,您可以通过将ORDER BY ss.value
添加到最终结果集中ColA
的内部查询,按字母顺序重新组合它们。在我使用您的输入进行的测试中,最终值的排序与输入列的顺序相同,但值得注意的是,这种行为不是保证,为了保证它,您需要做更多的工作。
【讨论】:
您是否正在使用 SQL Server 2016?这是唯一一个有string_split
但不是string_agg
@Charlieface 我在很多不同的实例上工作 :) 但 OP 标记为 sql-server-2008-r2
。假设他实际上不在那个版本上,因为那个版本无论如何都不会有string_split
(尽管他在这里特别要求使用它)。但至少其余部分是兼容的。由于我提到的string_split
的订购问题,OP 可能无论如何都需要实现自定义功能。这将使整个 2008 R2 兼容。 string_agg
是个好电话。如果需要,我可以使用string_agg
进行编辑。
另外,最正确的 FOR XML
咒语是这样的 FOR XML PATH(''), TYPE).value('text()[1]','nvarchar(max)'), 1, LEN(yourseparator), '')
这可以防止 XML 转义并确保正确的长度被截断
@Charlieface 你能指出这个断言的来源吗?我很少看到人们使用这种咒语,除非由于输入数据而需要它。以这种方式使用TYPE
指令会产生额外的成本,并且使用该咒语依赖于隐式转换。我永远不会推荐的东西。
dba.stackexchange.com/questions/63445/… 或这里 red-gate.com/simple-talk/sql/t-sql-programming/… 如果您更喜欢官方文章,还有许多其他示例。在 XML 中,您需要转义常见的字符,例如 '
"
&
,最好确保它始终正确完成。我不相信涉及任何隐式转换,TYPE
意味着它直接进入xml
数据类型以上是关于使用 string_split 函数查询更新字符串的主要内容,如果未能解决你的问题,请参考以下文章