简体   繁体   中英

SQL Server - Split column data and retrieve last second value

I have a column name MasterCode in XYZ Table where data is stored in below form.

.105248.105250.104150.111004.

Now first of all I want to split the data into :

105248
105250
104150
111004

Then after to retrieve only last second value from the above.

So In the above given array, value returned should be 104150 .

Use a split string function, but not the built in once since it will return only the values and you will lose the location data.

You can use Jeff Moden's DelimitedSplit8K that will return the item and the item index:

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
;

Then you can use it to split the string and it will return a table like this:

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   

You want only the parts where there actually is an item, so add a where clause, and you want the second from last so add row_number() , and you want the entire thing in a common table expression so that you can query it:

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 <> ''
)

And the query:

SELECT Item
FROM CTE
WHERE rn = 2

Result: 104150

Depending on your version of SQL SERVER, you can also use the STRING_SPLIT function.

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) <> '';

It doesn't return the original position like Jeff's splitter, but does compare very favourably if you check Aaron Bertrand's Article :

Performance Surprises and Assumptions : STRING_SPLIT()

Edit :

Added position, but although works in this case may have issues with duplicate values

If there are always four parts, you can use PARSENAME() :

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

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

You can create a SQL server table valued function with parameters stringvalue and delemeter and call that function for the results as expected.

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
)

To call the function

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

Output

ID  s
1   105248
2   105250
3   104150
4   111004

To get only second value you can write your query as shown below

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

Output

ID  s
3   104150

If you want 1 as value of ID then you can write your query as shown below in last line of query.

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

Then the output will be

ID  S
1   104150

Hope this will help you.

Try this

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

Result

Data
-----
105248
105250
104150
111004

It can also be achieve only using string functions:

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

Result:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM