我想从相当复杂的字符串返回整数,这些字符串将-
和.
等unicode字符与字符和整数组合在一起。
我在实现这一目标方面取得了很大进展,但我仍然遇到了一些更复杂结构的问题。例如:
DECLARE @Tabl as table
(
dats nvarchar(15)
)
INSERT INTO @Tabl VALUES
('103-P705hh'),
('115-xxx-44'),
('103-705.13'),
('525-hheef4')
select LEFT(SUBSTRING(REPLACE(REPLACE(dats, '.',''),'-',''), PATINDEX('%[0-9.-]%', REPLACE(REPLACE(dats, '.',''),'-','')), 8000),
PATINDEX('%[^0-9.-]%', SUBSTRING(REPLACE(REPLACE(dats, '.',''),'-',''), PATINDEX('%[0-9.-]%', REPLACE(REPLACE(dats, '.',''),'-','')), 8000) + 'X')-1)
from @tabl
给出
Raw Input Actual return: Desired return:
103-P705hh 103 103705
115-xxx-44 115 11544
103-705.13 10370513 10370513
525-hheef4 525 5254
我昨天有一个关于这个问题的主题,以涵盖多个-
或.
存在的情况,但正如回程中所见,这实际上已经得到了解决。但是,扩展我使用的数据库时遇到了更复杂的字符串,例如我在这里提到的字符串。
当字符串和整数在字符串中“混淆”时,有没有人知道该怎么做?
此致 Cenderze
答案 0 :(得分:4)
我已经看到大量的解决方案使用带有循环的标量udf,但我不喜欢这些东西,所以用不同的方法将我的帽子扔进戒指。
在数字表的帮助下,您可以将每个值解构为单个字符,删除非数字字符,然后使用FOR XML
重新构建它以连接行,例如
WITH Numbers (Number) AS
( SELECT ROW_NUMBER() OVER(ORDER BY N1.N)
FROM (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) AS N1 (N) -- 100
CROSS JOIN (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) AS N2 (N) -- 100
CROSS JOIN (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) AS N3 (N) -- 1,000
--CROSS JOIN (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) AS N4 (N) -- 10,000
--CROSS JOIN (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) AS N5 (N) -- 100,000
--COMMENT OR UNCOMMENT ROWS AS NECESSARY DEPENDING ON YOU MAX STRING LENGTH
)
SELECT t.dats,
Stripped = x.data.value('.', 'INT')
FROM @tabl AS t
CROSS APPLY
( SELECT SUBSTRING(t.dats, n.Number, 1)
FROM Numbers n
WHERE n.Number <= LEN(t.dats)
AND SUBSTRING(t.dats, n.Number, 1) LIKE '[0-9]'
ORDER BY n.Number
FOR XML PATH(''), TYPE
) x (data);
给出:
dats Stripped
----------------------
103-P705hh 103705
115-xxx-44 11544
103-705.13 10370513
525-hheef4 5254
我还没有完成任何测试,因此可能会将每个字符串扩展为单个字符并重新构建它的额外开销实际上比带有循环的UDF要多得多。
我决定对此进行基准测试
<强> 1。设置功能
CREATE FUNCTION dbo.ExtractNumeric_TVF (@Input VARCHAR(8000))
RETURNS TABLE
AS
RETURN
( WITH Numbers (Number) AS
( SELECT TOP (LEN(@Input)) ROW_NUMBER() OVER(ORDER BY N1.N)
FROM (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) AS N1 (N) -- 100
CROSS JOIN (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) AS N2 (N) -- 100
CROSS JOIN (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) AS N3 (N) -- 1,000
CROSS JOIN (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) AS N4 (N) -- 10,000
)
SELECT Stripped = x.data.value('.', 'VARCHAR(MAX)')
FROM ( SELECT SUBSTRING(@Input, n.Number, 1)
FROM Numbers n
WHERE n.Number <= LEN(@Input)
AND SUBSTRING(@Input, n.Number, 1) LIKE '[0-9]'
ORDER BY n.Number
FOR XML PATH(''), TYPE
) x (data)
);
GO
create function dbo.ExtractNumeric_UDF(@s varchar(8000))
returns varchar(8000)
as
begin
declare @out varchar(max) = ''
declare @c char(1)
while len(@s) > 0 begin
set @c = left(@s,1)
if @c like '[0123456789]' set @out += @c
set @s = substring(@s, 2, len(@s) -1)
end
return @out
end
GO
<强> 2。创建第一组样本数据和日志表
CREATE TABLE dbo.T (Value VARCHAR(8000) NOT NULL);
INSERT dbo.T (Value)
SELECT TOP 1000 LEFT(NEWID(), CEILING(RAND(CHECKSUM(NEWID())) * 36))
FROM sys.all_objects a
CROSS JOIN sys.all_objects b;
CREATE TABLE dbo.TestLog (Fx VARCHAR(255), NumberOfRows INT, TimeStart DATETIME2(7), TimeEnd DATETIME2(7))
第3。运行测试
GO
DECLARE @T TABLE (Val VARCHAR(8000));
INSERT dbo.TestLog (fx, NumberOfRows, TimeStart)
VALUES ('dbo.ExtractNumeric_UDF', 1000, SYSDATETIME());
INSERT @T (Val)
SELECT dbo.ExtractNumeric_UDF(Value)
FROM dbo.T;
UPDATE dbo.TestLog
SET TimeEnd = SYSDATETIME()
WHERE TimeEnd IS NULL;
GO 100
DECLARE @T TABLE (Val VARCHAR(8000));
INSERT dbo.TestLog (fx, NumberOfRows, TimeStart)
VALUES ('dbo.ExtractNumeric_TVF', 1000, SYSDATETIME());
INSERT @T (Val)
SELECT f.Stripped
FROM dbo.T
CROSS APPLY dbo.ExtractNumeric_TVF(Value) f;
UPDATE dbo.TestLog
SET TimeEnd = SYSDATETIME()
WHERE TimeEnd IS NULL;
GO 100
<强> 4。获得结果
SELECT Fx,
NumberOfRows,
RunTime = AVG(DATEDIFF(MILLISECOND, TimeStart, TimeEnd))
FROM dbo.TestLog
GROUP BY fx, NumberOfRows;
我做了以下(仅使用NEWID()
,因此最多只有36个字符)超过1,000和10,000行,结果是:
Fx NumberOfRows RunTime
--------------------------------------------------------
dbo.ExtractNumeric_TVF 1000 31
dbo.ExtractNumeric_UDF 1000 56
dbo.ExtractNumeric_TVF 10000 280
dbo.ExtractNumeric_UDF 10000 510
所以TVF的进入时间不到UDF的一半。
我想测试边缘情况,所以要放1000行更长的字符串(5,400个字符)
TRUNCATE TABLE dbo.T;
INSERT dbo.T (Value)
SELECT TOP 1000
REPLICATE(CONCAT(NEWID(), NEWID(), NEWID(), NEWID(), NEWID()), 30)
FROM sys.all_objects a
CROSS JOIN sys.all_objects b;
这就是TVF自成一体的速度,速度提高了5倍以上:
Fx NumberOfRows RunTime
------------------------------------------------
dbo.ExtractNumeric_TVF 1000 2485
dbo.ExtractNumeric_UDF 1000 12955
答案 1 :(得分:2)
我也不喜欢循环解决方案所以我决定尝试一下。这是使用预定义的计数表,但与已在此处发布的其他表非常相似。
这是我的理货表。我把它作为我系统的一个视图。
create View [dbo].[cteTally] as
WITH
E1(N) AS (select 1 from (values (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))dt(n)),
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
(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
)
select N from cteTally
GO
因为我不喜欢循环,所以我决定使用表值函数方法,这样我就可以轻松地在其他查询中重用这个功能。这是编写这样一个函数的一种方法。
create function GetOnlyNumbers
(
@SearchVal varchar(8000)
) returns table as return
with MyValues as
(
select substring(@SearchVal, N, 1) as number
, t.N
from cteTally t
where N <= len(@SearchVal)
and substring(@SearchVal, N, 1) like '[0-9]'
)
select distinct NumValue = STUFF((select number + ''
from MyValues mv2
order by mv2.N
for xml path('')), 1, 0, '')
from MyValues mv
看起来不错,但证据就在于布丁。让我们用我们的样本数据来解决这个问题并将轮胎踢掉几次。
DECLARE @Tabl as table
(
dats nvarchar(15)
)
INSERT INTO @Tabl VALUES
('103-P705hh'),
('115-xxx-44'),
('103-705.13'),
('525-hheef4')
select *
from @Tabl t
cross apply dbo.GetOnlyNumbers(t.dats) x
当然看起来很干净整洁。我对这里发布的其他几个解决方案进行了测试,没有进行深度测试,这似乎比目前发布的其他方法快得多。
答案 2 :(得分:1)
你能用udf吗?如果是这样,试试这个
create alter function numerals(@s varchar(max))
returns varchar(max)
as
begin
declare @out varchar(max) = ''
declare @c char(1)
while len(@s) > 0 begin
set @c = left(@s,1)
if @c like '[0123456789]' set @out += @c
set @s = substring(@s, 2, len(@s) -1)
end
return @out
end
在临时桌上使用它......
select dbo.numerals(dats) from @Tabl
另一种解决方案,它不使用UDF,但只有在您的表具有主键时才会起作用,使用递归CTE。它是:
DECLARE @Tabl as table
(pk int identity not null, -- <=== added a primary key
dats nvarchar(max) )
INSERT INTO @Tabl VALUES
('103-P705hh'),
('115-xxx-44'),
('103-705.13'),
('525-hheef4');
with newVals(pk, pos, newD) as
(select pk, 1,
case when left(Dats,1) like '[0123456789]'
then left(Dats,1) else '' end
from @tabl
Union All
Select t.pk, pos + 1, n.newD +
case when substring(dats, pos+1, 1) like '[0123456789]'
then substring(dats, pos+1, 1) else '' end
from @tabl t join newVals n on n.pk = t.pk
where pos+1 <= len(dats) )
Select newD from newVals x
where pos = (Select Max(pos)
from newVals
where pk = x.pk)
答案 3 :(得分:1)
DECLARE @Tabl as table
(
ID INT,
dats nvarchar(15)
)
INSERT INTO @Tabl VALUES
(1, '103-P705hh'),
(2, '115-xxx-44'),
(3, '103-705.13'),
(4, '525-hheef4')
SELECT T.ID, t.dats
,(
SELECT SUBSTRING(tt.dats,V.number,1)
FROM @Tabl tt
JOIN master.dbo.spt_values V ON V.type='P' AND V.number BETWEEN 1 AND LEN(tt.dats)
WHERE tt.ID=T.ID AND SUBSTRING(TT.dats,V.number,1) LIKE '[0-9]'
ORDER BY V.number
FOR XML PATH('')
) S
FROM @Tabl t
ORDER BY T.ID;