来自 (SELECT VALUE FROM STRING_SPLIT()) 的矛盾结果

时间:2021-05-11 18:06:06

标签: sql sql-server tsql

我正在尝试在 SQL Server 2018 中拆分选项值对,到目前为止结果很好,但现在我在 SELECT VALUE FROM STRING_SPLIT() 中遇到了一些奇怪的行为。

代码如下:

SELECT VALUE o FROM STRING_SPLIT('AgingFactorCorr:25',':')
SELECT VALUE o FROM STRING_SPLIT('AlarmBuzSignal:NA',':') 
SELECT VALUE o FROM STRING_SPLIT('AgingFactorCorr:25',':') ORDER BY VALUE OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY
SELECT VALUE o FROM STRING_SPLIT('AlarmBuzSignal:NA',':') ORDER BY VALUE OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY

在运行前两个 SELECT 时,两个对的选项位于第一行,值位于第二行。

然而,当应用 ORDER BY VALUE OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY 时,结果是相反的。

对于第一个我选择“25”,对于第二个“AlarmBuzSignal”,需要第二个。

这种行为的原因是什么?除了数据类型之外,看不出任何区别,但我认为这无关紧要。

问题隐藏在 order by 子句中,在添加 LEN(VALUE) DESC 之后,我都得到了想要的结果,但对我来说似乎不是一个优雅的解决方案。

有没有更好的方法来实现这一目标?

任何帮助将不胜感激。

3 个答案:

答案 0 :(得分:0)

当您不提供 order by 语句时,行将随机返回,并且行的顺序没有保证。 所以对于你的前两个 select 语句,不能保证行将如何显示

如果您对第一个查询进行排序,结果中的顺序将始终相同

<块引用>
SELECT VALUE o FROM STRING_SPLIT('AgingFactorCorr:25',':')ORDER BY VALUE 
SELECT VALUE o FROM STRING_SPLIT('AlarmBuzSignal:NA',':') ORDER BY VALUE 
SELECT VALUE o FROM STRING_SPLIT('AgingFactorCorr:25',':') ORDER BY VALUE OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY
SELECT VALUE o FROM STRING_SPLIT('AlarmBuzSignal:NA',':') ORDER BY VALUE OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY
<块引用>
| o               |
| :-------------- |
| 25              |
| AgingFactorCorr |

| o              |
| :------------- |
| AlarmBuzSignal |
| NA             |

| o  |
| :- |
| 25 |

| o              |
| :------------- |
| AlarmBuzSignal |

db<>fiddle here

来自微软文档:

<块引用>

输出行可以按任何顺序排列。该顺序不能保证与输入字符串中子字符串的顺序匹配。您可以通过在 SELECT 语句(ORDER BY 值)上使用 ORDER BY 子句来覆盖最终排序顺序。

答案 1 :(得分:0)

不幸的是The output rows might be in any order. The order is not guaranteed to match the order of the substrings in the input string

您可能想改用手工制作的分割线。它返回一个包含它们位置的子字符串表

CREATE FUNCTION [dbo].[DelimitedSplit8K_LEAD]
        (@pString VARCHAR(8000), @pDelimiter CHAR(1))
--WARNING!!! DO NOT USE MAX DATA-TYPES HERE!  IT WILL KILL PERFORMANCE!
RETURNS TABLE WITH SCHEMABINDING AS
 RETURN
--===== "Inline" CTE Driven "Tally Table" produces values from 1 up to 10,000...
     -- enough to cover VARCHAR(8000)
  WITH E1(N) AS (
                  SELECT N FROM (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) t(N)
                ),                          --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
                )
--===== 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 s.N1),
        Item       = SUBSTRING(@pString, s.N1, ISNULL(NULLIF((LEAD(s.N1,1,1) OVER (ORDER BY s.N1)) - 1,0) - s.N1,8000))
   FROM cteStart s
;

借自https://www.sqlservercentral.com/articles/reaping-the-benefits-of-the-window-functions-in-t-sql-2

答案 2 :(得分:0)

我找到了适合我的问题的解决方案。

我不处理大数据并且选项值对总是两行,所以我添加了|OPT|在每个选项之前,然后用 replace(o,'|OPT|','') 删除它。

正如答案中提到的,可能有更好的方法来分隔字符串。

我知道我的解决方案在性能方面很重。 将尽我所能改进这一点。