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
我在 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