将 SQL Server 中的字符串拆分为最大长度,并将每个字符串作为一行返回
Posted
技术标签:
【中文标题】将 SQL Server 中的字符串拆分为最大长度,并将每个字符串作为一行返回【英文标题】:Split string in SQL Server to a maximum length, returning each as a row 【发布时间】:2012-06-01 14:55:14 【问题描述】:有没有办法将字符串(从特定列)拆分为 n 个字符而不会破坏单词,并且每个结果都在自己的行中?
例子:
2012-04-24 Change request #3 for the contract per terms and conditions and per John Smith in the PSO department Customer states terms should be Net 60 not Net 30. Please review signed contract for this information.
结果:
2012-04-24 Change request #3 for the contract per terms and conditions and per John Smith in the
PSO department Customer states terms should be Net 60 not Net 30.
Please review signed contract for this information.
我知道我可以使用charindex
找到最后一个空格,但我不确定如何获取剩余的空格并将它们作为行返回。
【问题讨论】:
查看我在这里找到的 PrintMax 的代码。 . . weblogs.asp.net/bdill/archive/2007/09/29/…。它会查找换行符,但您也可以轻松查找空格。 看看 Jeff Moden 的 Split8k 函数 here。这可能会被修改为你想要的。 您始终可以通过 SQL CLR UDF 使用 .NET Regex。 【参考方案1】:试试这样的。可能是您可以创建以下实现的 SQL 函数。
DECLARE @Str VARCHAR(1000)
SET @Str = '2012-04-24 Change request #3 for the contract per terms and conditions and per John Smith in the PSO department Customer states terms should be Net 60 not Net 30. Please review signed contract for this information.'
DECLARE @End INT
DECLARE @Split INT
SET @Split = 100
declare @SomeTable table
(
Content varchar(3000)
)
WHILE (LEN(@Str) > 0)
BEGIN
IF (LEN(@Str) > @Split)
BEGIN
SET @End = LEN(LEFT(@Str, @Split)) - CHARINDEX(' ', REVERSE(LEFT(@Str, @Split)))
INSERT INTO @SomeTable VALUES (RTRIM(LTRIM(LEFT(LEFT(@Str, @Split), @End))))
SET @Str = SUBSTRING(@Str, @End + 1, LEN(@Str))
END
ELSE
BEGIN
INSERT INTO @SomeTable VALUES (RTRIM(LTRIM(@Str)))
SET @Str = ''
END
END
SELECT *
FROM @SomeTable
输出将是这样的:
2012-04-24 Change request #3 for the contract per terms and conditions and per John Smith in the
PSO department Customer states terms should be Net 60 not Net 30. Please review signed contract
for this information.
【讨论】:
我不想打印它,我希望它在记录集中作为单独的行返回。 这在小尺寸下是如此缓慢且无用。例如,当我想分割每 3 个字符时。 谢谢您...这是您后兜里需要的食谱之一。我用它作为我需要的东西的基础,但在搞砸这个的同时也学到了很多东西。【参考方案2】:我阅读了一些文章,每篇文章都有错误或性能不佳,或者无法在我们想要的小块或大块中工作。您甚至可以在下面的这篇文章中阅读我的 cmets 任何答案。最后我找到了一个很好的答案,并决定在这个问题中分享它。我没有检查各种情况下的性能,但我认为对于大小块长度来说是可以接受的并且工作正常。 这是代码:
CREATE function SplitString
(
@str varchar(max),
@length int
)
RETURNS @Results TABLE( Result varchar(50),Sequence INT )
AS
BEGIN
DECLARE @Sequence INT
SET @Sequence = 1
DECLARE @s varchar(50)
WHILE len(@str) > 0
BEGIN
SET @s = left(@str, @length)
INSERT @Results VALUES (@s,@Sequence)
IF(len(@str)<@length)
BREAK
SET @str = right(@str, len(@str) - @length)
SET @Sequence = @Sequence + 1
END
RETURN
END
来源是@Rhyno 对这个问题的回答:TSQL UDF To Split String Every 8 Characters
希望对您有所帮助。
【讨论】:
这不回答 OP 的问题,因为它不保留单词(即如果在 50 个字符标记处有一个单词,它将把它切成两半而不是将整个单词移动到下一行)。但是……它确实正是我所需要的。 :) @Keiki 感谢您的关注,我会尽快检查。【参考方案3】:只是为了看看是否可以完成,我想出了一个不循环的解决方案。它基于somebody else's function根据分隔符拆分字符串。
注意: 这要求您提前知道最大令牌长度。该函数将在遇到比指定行长更长的标记时停止返回行。可能还潜伏着其他错误,因此请谨慎使用此代码。
CREATE FUNCTION SplitLines
(
@pString VARCHAR(7999),
@pLineLen INT,
@pDelim CHAR(1)
)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN
WITH
E1(N) AS ( --=== Create Ten 1's
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 --10
),
E2(N) AS (SELECT 1 FROM E1 a, E1 b), --100
E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10,000
cteTally(N) AS (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT N)) FROM E4),
lines AS (
SELECT TOP 1
1 as LineNumber,
ltrim(rtrim(SUBSTRING(@pString, 1, N))) as Line,
N + 1 as start
FROM cteTally
WHERE N <= DATALENGTH(@pString) + 1
AND N <= @pLineLen + 1
AND SUBSTRING(@pString + @pDelim, N, 1) = @pDelim
ORDER BY N DESC
UNION ALL
SELECT LineNumber, Line, start
FROM (
SELECT LineNumber + 1 as LineNumber,
ltrim(rtrim(SUBSTRING(@pString, start, N))) as Line,
start + N + 1 as start,
ROW_NUMBER() OVER (ORDER BY N DESC) as r
FROM cteTally, lines
WHERE N <= DATALENGTH(@pString) + 1 - start
AND N <= @pLineLen
AND SUBSTRING(@pString + @pDelim, start + N, 1) = @pDelim
) A
WHERE r = 1
)
SELECT LineNumber, Line
FROM lines
它实际上非常快,你可以做一些很酷的事情,比如加入它。下面是一个简单的例子,它从表的每一行中获取第一行:
declare @table table (
id int,
paragraph varchar(7999)
)
insert into @table values (1, '2012-04-24 Change request #3 for the contract per terms and conditions and per John Smith in the PSO department Customer states terms should be Net 60 not Net 30. Please review signed contract for this information.')
insert into @table values (2, 'Is there a way to split a string (from a specific column) to n-number chars without breaking words, with each result in its own row?')
select t.id, l.LineNumber, l.Line, len(Line)
from @table t
cross apply SplitLines(t.paragraph, 42, ' ') l
where l.LineNumber = 1
【讨论】:
一个大错误。尝试将 pLineLen 设置为较小的值,例如 5,您会看到它无法获得结果。我将它与可变长度作为 pString 进行了检查,发现当 pString 足够长时,这适用于 pLineLen => 11,并且可以通过 pString 长度进行更改,但是在任何长度下,您都不能将 pLineLen 设置为小于 11。这太麻烦了而且我认为在大多数情况下都没用。 经过一番实验,我认为@pLineLen
至少需要设置为最大令牌大小加2。这是有道理的,因为否则你会把一个词分成两半。在上面的例子中,“信息”。长度为 12,因此 @pLineLen
需要设置为至少 14 才能获取所有内容。
@Bret,感谢您指出这一点。我修复了代码中的一些错误,所以现在只要@pLineLen
至少与最大令牌长度一样大,它的行为(大部分)应该是正确的。【参考方案4】:
我知道这有点晚了,但递归 cte 可以实现这一点。
您还可以使用包含数字序列的种子表作为起始索引的乘数输入子字符串。
【讨论】:
@Chris 我在信息安全网站上回答了一个问题并得到了 5 分,只是因为没有详细解释答案,即使我还写了一些有用的链接。如果没有解释、代码、示例和链接,您的答案将毫无用处。我不会减分,但请小心并在回答问题时做到最好。 @QMaster 非常感谢您的评论,真的希望我有时间对我的答案给出完整的解释 - 不幸的是,发布答案主要是是一种不用感谢的爱好。在这种情况下,我懒得回答,因为我虽然提出不同的观点会很有用,而且我认为 Chad Henderson 提供了一个答案,之后显示了 CTE 示例。此外,我个人认为应该保留负面投票以用于误导或不正确的答案。根据我的回答获得的票数,它既无用也不不正确。 @ChrisMoutray 我看到了你的好主意并尝试提供帮助。由于我的经验,我只想提示您。你能想象-5分吗? :) 最后一位专家用户决定删除答案,甚至我写了新的答案并给出了完整的解释,没有一点+1 :) 你可以看看这个来了解我的诚实:security.stackexchange.com/questions/49315/… 无论如何,我很感谢你的尝试和来到这里。跨度>以上是关于将 SQL Server 中的字符串拆分为最大长度,并将每个字符串作为一行返回的主要内容,如果未能解决你的问题,请参考以下文章