SQL Server-拆分列数据并检索倒数第二个值

时间:2018-11-01 10:32:58

标签: sql sql-server sql-server-2017

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

.105248.105250.104150.111004.

现在我首先要将数据拆分为:

105248
105250
104150
111004

之后,仅从上面检索倒数第二个值。

因此,在上述给定的数组中,返回的值应为104150

6 个答案:

答案 0 :(得分:2)

使用分割字符串函数,但不要一次使用内置函数,因为它将仅返回值,并且您将丢失位置数据。

您可以使用Jeff Moden的DelimitedSplit8K来返回商品和商品索引:

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
;

然后您可以使用它来分割字符串,它将返回一个如下表:

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(),并希望整个表都在一个公共表表达式中,以便您可以查询它:

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

答案 1 :(得分:1)

根据您的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的拆分器那样返回原始位置,但是如果您查看Aaron Bertrand的Article,它的确非常有利:

Performance Surprises and Assumptions : STRING_SPLIT()

编辑

添加了位置,但尽管在这种情况下可行,但可能存在重复值的问题

答案 2 :(得分:1)

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

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

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

答案 3 :(得分:0)

您可以使用参数stringvalue和delemeter创建一个SQL Server表值函数,并按预期的结果调用该函数。

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

如果您希望ID为1,则可以在查询的最后一行中编写查询,如下所示。

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

然后输出将为

ID  S
1   104150

希望这会对您有所帮助。

答案 4 :(得分:0)

尝试一下

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

答案 5 :(得分:0)

也可以仅使用字符串函数来实现:

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