【发布时间】:2011-04-02 13:33:11
【问题描述】:
有没有一种巧妙的方法可以将掩码应用于 SQL Server 查询中的字符串?
我有两张表,一张将电话号码存储为没有文字 0155567890 的 varchar 和一个电话类型,它具有该电话号码类型的掩码:(##) #### ####
返回字符串(对于合并文档)以便查询返回完全格式化的电话号码的最佳方法是什么:
(01) 5556 7890
【问题讨论】:
标签: sql sql-server
有没有一种巧妙的方法可以将掩码应用于 SQL Server 查询中的字符串?
我有两张表,一张将电话号码存储为没有文字 0155567890 的 varchar 和一个电话类型,它具有该电话号码类型的掩码:(##) #### ####
返回字符串(对于合并文档)以便查询返回完全格式化的电话号码的最佳方法是什么:
(01) 5556 7890
【问题讨论】:
标签: sql sql-server
如评论中所述,如果在大量行中使用,我下面的原始答案将导致糟糕的性能。如果考虑性能,则首选i-one's answer。
我也需要这个,感谢 Sjuul 的伪代码,我能够创建一个函数来执行此操作。
CREATE FUNCTION [dbo].[fx_FormatUsingMask]
(
-- Add the parameters for the function here
@input nvarchar(1000),
@mask nvarchar(1000)
)
RETURNS nvarchar(1000)
AS
BEGIN
-- Declare the return variable here
DECLARE @result nvarchar(1000) = ''
DECLARE @inputPos int = 1
DECLARE @maskPos int = 1
DECLARE @maskSign char(1) = ''
WHILE @maskPos <= Len(@mask)
BEGIN
set @maskSign = substring(@mask, @maskPos, 1)
IF @maskSign = '#'
BEGIN
set @result = @result + substring(@input, @inputPos, 1)
set @inputPos += 1
set @maskPos += 1
END
ELSE
BEGIN
set @result = @result + @maskSign
set @maskPos += 1
END
END
-- Return the result of the function
RETURN @result
END
【讨论】:
以防万一有人需要表值函数。
方法 1(参见 #2 以获得更快的版本)
create function ftMaskPhone
(
@phone varchar(30),
@mask varchar(50)
)
returns table as
return
with ci(n, c, nn) as (
select
1,
case
when substring(@mask, 1, 1) = '#' then substring(@phone, 1, 1)
else substring(@mask, 1, 1)
end,
case when substring(@mask, 1, 1) = '#' then 1 else 0 end
union all
select
n + 1,
case
when substring(@mask, n + 1, 1) = '#' then substring(@phone, nn + 1, 1)
else substring(@mask, n + 1, 1)
end,
case when substring(@mask, n + 1, 1) = '#' then nn + 1 else nn end
from ci where n < len(@mask))
select (select c + '' from ci for xml path(''), type).value('text()[1]', 'varchar(50)') PhoneMasked
GO
然后将其应用为
declare @mask varchar(50)
set @mask = '(##) #### ####'
select pm.PhoneMasked
from Phones p
outer apply ftMaskPhone(p.PhoneNum, @mask) pm
方法 2
出于历史目的,我将保留上述版本。但是,这个性能更好。
CREATE FUNCTION dbo.ftMaskPhone
(
@phone varchar(30),
@mask varchar(50)
)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN
(
WITH v1(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
),
v2(N) AS (SELECT 1 FROM v1 a, v1 b),
v3(N) AS (SELECT TOP (ISNULL(LEN(@mask), 0)) ROW_NUMBER() OVER (ORDER BY @@SPID) FROM v2),
v4(N, C) AS (
SELECT N, ISNULL(SUBSTRING(@phone, CASE WHEN c.m = 1 THEN ROW_NUMBER() OVER (PARTITION BY c.m ORDER BY N) END, 1), SUBSTRING(@mask, v3.N, 1))
FROM v3
CROSS APPLY (SELECT CASE WHEN SUBSTRING(@mask, v3.N, 1) = '#' THEN 1 END m) c
)
SELECT MaskedValue = (
SELECT c + ''
FROM v4
ORDER BY N
FOR XML PATH(''), TYPE
).value('text()[1]', 'varchar(50)')
);
GO
模式绑定,结合这是一个单语句表值函数,使这个版本有资格被查询优化器内联。如上例所示,使用CROSS APPLY 实现该函数,或者对于单个值,如下所示:
SELECT *
FROM dbo.ftMaskPhone('0012345678910', '### (###) ###-####')
结果如下:
MaskedValue 001 (234) 567-8910
【讨论】:
正如@Sean 所说,SQL Server 2012 及更高版本支持FORMAT 函数,几乎可以满足您的需求,但需要注意以下几点:
格式化需要一个数字,而不是VARCHAR。这可以通过使用CAST 来解决。
提供的掩码 ((##) #### ####) 加上 CAST 将删除前导零,留下 (1) 5556 7890。您可以将掩码更新为(0#) #### ####。在你代表Australian phone number的肢体上,似乎前导0总是在那里:
在澳大利亚境内,要访问来电者所在“地区”以外的固定电话“号码”(包括使用“移动”电话的来电者),首先需要拨打澳大利亚“中继接入代码”为 0 加上“区域”代码,后跟“本地”号码。因此,“完整的国家号码”(FNN)有十位数字:0x xxxx xxxx。
但归根结底,我认为 SQL Server 不是处理数据表示/格式化的最佳位置(与日期一样,电话号码也是如此)。我建议使用 Google 的 libphonenumber 之类的东西来做这个客户端。将电话号码输入数据库后,您可以存储电话号码本身及其所属的国家/地区,然后您可以在显示电话号码时使用它们(或执行诸如拨打电话或检查有效性之类的操作)。
【讨论】:
有内置的FORMAT 功能,几乎可以工作。不幸的是,它需要一个 int 作为第一个参数,所以它去掉了前导零:
select format(0155567890 ,'(##) #### ####')
(1) 5556 7890
【讨论】:
我想隐藏一些信息,所以我使用了RIGHT 函数。它仅显示右侧的前 4 个字符。
CONCAT('xxx-xx-', RIGHT('03466045896', 4))
上面的代码会显示“xxx-xx-5896”
【讨论】:
如果您需要“屏蔽”,而不是用另一个隐藏实际值,然后“取消屏蔽”一个字符串,您可以尝试此功能,或扩展它。 :)
【讨论】:
这正是我想到的。我不知道这是否是最好的解决方案,但我认为它应该是可行的。
创建一个名为 applyMask (orso) 的函数
伪代码:
WHILE currentPosition < Length(PhoneNr) AND safetyCounter < Length(Mask)
IF currentSign = "#"
result += Mid(PhoneNr, currentPosition, 1)
currentPosition++
ELSE
result += currentSign
safetyCounter++
END
END
Return result
【讨论】: