SQL 服务器 - 拆分列数据并检索最后一秒的值

SQL Server - Split column data and retrieve last second value

我在 XYZ Table 中有一个列名称 MasterCode,其中数据以下面的形式存储。

.105248.105250.104150.111004.

现在首先我想将数据分成:

105248
105250
104150
111004

然后仅从上面检索最后一秒的值。

所以在上面给出的数组中,返回值应该是104150.

使用拆分字符串函数,但不要使用内置函数一次,因为它 return 仅会 return 值,您将丢失位置数据。

您可以使用 Jeff Moden 的 DelimitedSplit8K,它将 return 项目和项目索引:

CREATE FUNCTION [dbo].[DelimitedSplit8K]
--===== Define I/O parameters
        (@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 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
;

然后你可以用它来分割字符串,它会 return 一个 table 像这样:

DECLARE @string varchar(100) = '.105248.105250.104150.111004.';

SELECT *
FROM [dbo].[DelimitedSplit8K](@string, '.')

ItemNumber  Item
1   
2           105248
3           105250
4           104150
5           111004
6   

您只需要实际有一个项目的部分,所以添加一个 where 子句,您想要倒数第二个,所以添加 row_number(),并且您希望整个部分都在一个公共 table 表达式,以便您可以查询它:

DECLARE @string varchar(100) = '.105248.105250.104150.111004.';

WITH CTE AS
(
    SELECT Item, ROW_NUMBER() OVER(ORDER BY ItemNumber DESC) As rn
    FROM [dbo].[DelimitedSplit8K](@string, '.')
    WHERE Item <> ''
)

以及查询:

SELECT Item
FROM CTE
WHERE rn = 2

结果:104150

根据您的 SQL SERVER 版本,您还可以使用 STRING_SPLIT 功能。

DECLARE @string varchar(100) = '.105248.105250.104150.111004.';

SELECT  value,
        ROW_NUMBER() OVER (ORDER BY CHARINDEX('.' + value + '.', '.' + @string + '.')) AS Pos
FROM    STRING_SPLIT(@string,'.')  
WHERE   RTRIM(value) <> '';

它不像 Jeff 的分离器那样 return 原始位置,但如果您查看 Aaron Bertrand 的文章,它确实比较有利:

Performance Surprises and Assumptions : STRING_SPLIT()

编辑:

已添加位置,但尽管在这种情况下有效可能存在重复值问题

您可以使用参数 stringvalue 和 delemeter 创建一个 SQL 服务器 table 值函数,并按预期调用该函数以获得结果。

ALTER function [dbo].[SplitString] 
(
@str nvarchar(4000), 
@separator char(1)
)
returns table
AS
return (
with tokens(p, a, b) AS (
select 
1, 
1, 
charindex(@separator, @str)
union all
select
p + 1, 
b + 1, 
charindex(@separator, @str, b + 1)
from tokens
where b > 0
)
select
p-1 ID,
substring(
@str, 
a, 
case when b > 0 then b-a ELSE 4000 end) 
AS s
from tokens
)

调用函数

SELECT * FROM [DBO].[SPLITSTRING] ('.105248.105250.104150.111004.', '.') WHERE ISNULL(S,'') <> ''

输出

ID  s
1   105248
2   105250
3   104150
4   111004

要仅获取第二个值,您可以按如下所示编写查询

DECLARE @MaxID INT
SELECT @MaxID = MAX (ID) FROM (SELECT * FROM [DBO].[SPLITSTRING] ('.105248.105250.104150.111004.', '.') WHERE ISNULL(S,'') <> '') A

SELECT TOP 1 @MaxID =  MAX (ID) FROM (
SELECT * FROM [DBO].[SPLITSTRING] ('.105248.105250.104150.111004.', '.') WHERE ISNULL(S,'') <> ''
)a where ID < @MaxID

SELECT * FROM [DBO].[SPLITSTRING] ('.105248.105250.104150.111004.', '.') WHERE ISNULL(S,'') <> '' AND ID = @MaxID

输出

ID  s
3   104150

如果您想要 1 作为 ID 的值,那么您可以在查询的最后一行中如下所示编写查询。

SELECT 1 AS ID , S FROM [DBO].[SPLITSTRING] ('.105248.105250.104150.111004.', '.') WHERE ISNULL(S,'') <> '' AND ID = @MaxID

那么输出将是

ID  S
1   104150

希望对您有所帮助。

如果总是有四个部分,可以使用PARSENAME():

DECLARE @s varchar(64) = '.105248.105250.104150.111004.';

SELECT PARSENAME(SUBSTRING(@s, 2, LEN(@s)-2),2);

试试这个

DECLARE @DATA AS TABLE (Data nvarchar(1000))
INSERT INTO @DATA
SELECT '.105248.105250.104150.111004.'
;WITH CTE
AS
(
SELECT Data,ROW_NUMBER()OVER(ORDER BY Data DESC) AS Rnk
FROM
(
    SELECT Split.a.value('.','nvarchar(100)') Data
    FROM(
    SELECT CAST('<S>'+REPLACE(Data,'.','</S><S>')+'</S>' AS XML ) As Data
    FROM @DATA
)DT 
CROSS APPLY Data.nodes('S') AS Split(a)
) AS Fnl
WHERE Fnl.Data <>''

)
SELECT Data FROM CTE
WHERE Rnk=2

结果

Data
-----
105248
105250
104150
111004

也可以只用字符串函数实现:

IF OBJECT_ID('tempdb..#temp') IS NOT NULL
    DROP TABLE #temp

SELECT '.105248.105250.104150.111004.' code INTO #temp UNION ALL 
SELECT '.205248.205250.204150.211004.' 

SELECT 
REVERSE(LEFT(
            REVERSE(LEFT(code,  LEN(code) - CHARINDEX('.', REVERSE(code), 2)))
            ,  CHARINDEX('.',REVERSE(LEFT(code,  LEN(code) - CHARINDEX('.', REVERSE(code), 2)))) -1
            )
        )   second_last_value
FROM #temp

结果:

second_last_value
-----------------------------
104150
204150