表达式在字符串中查找多个空格

时间:2018-02-19 16:12:59

标签: sql-server string tsql

我们处理了很多敏感数据,我想仅使用每个名称部分的第一个和最后一个字母来掩盖乘客姓名,并将这些名称加入三个星号(***),

  

例如:名称'John Doe'将成为'J *** n D *** e'

对于由两部分组成的名称,可以通过使用表达式查找空格来实现:

LEFT(CardHolderNameFromPurchase, 1) + 
 '***' + 
 CASE WHEN CHARINDEX(' ', PassengerName) = 0 
      THEN RIGHT(PassengerName, 1) 
      ELSE SUBSTRING(PassengerName, CHARINDEX(' ', PassengerName) -1, 1) +
           ' ' + 
           SUBSTRING(PassengerName, CHARINDEX(' ', PassengerName) +1, 1) +
           '***' + 
           RIGHT(PassengerName, 1) 
 END

但是,乘客名称可以有两个以上的部分,没有实际限制。我怎样才能找到表达式中所有空格的索引?或者我应该以不同的方式解决这个问题?

非常感谢任何帮助或指针!

5 个答案:

答案 0 :(得分:5)

此解决方案可以满足您的需求,但在尝试隐藏可识别个人身份的数据时,这种方法确实是错误的,正如戈登在答案中的解释所示。

SQL:

declare @t table(n nvarchar(20));
insert into @t values('John Doe')
,('JohnDoe')
,('John Doe Two')
,('John Doe Two Three')
,('John O''Neill');

select n
      ,stuff((select ' ' + left(s.item,1) + '***' + right(s.item,1)
              from dbo.fn_StringSplit4k(t.n,' ',null) as s
              for xml path('')
              ),1,1,''
              ) as mask
from @t as t;

输出:

+--------------------+-------------------------+
|         n          |          mask           |
+--------------------+-------------------------+
| John Doe           | J***n D***e             |
| JohnDoe            | J***e                   |
| John Doe Two       | J***n D***e T***o       |
| John Doe Two Three | J***n D***e T***o T***e |
| John O'Neill       | J***n O***l             |
+--------------------+-------------------------+

基于Jeff Moden's Tally Table approach的字符串拆分功能:

create function [dbo].[fn_StringSplit4k]
(
  @str nvarchar(4000) = ' '    -- String to split.
 ,@delimiter as nvarchar(1) = ','  -- Delimiting value to split on.
 ,@num as int = null      -- Which value to return, null returns all.
)
returns table
as
return
     -- Start tally table with 10 rows.
 with n(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)

     -- Select the same number of rows as characters in @str as incremental row numbers.
     -- Cross joins increase exponentially to a max possible 10,000 rows to cover largest @str length.
  ,t(t)   as (select top (select len(isnull(@str,'')) a) row_number() over (order by (select null)) from n n1,n n2,n n3,n n4)

     -- Return the position of every value that follows the specified delimiter.
  ,s(s)   as (select 1 union all select t+1 from t where substring(isnull(@str,''),t,1) = @delimiter)

     -- Return the start and length of every value, to use in the SUBSTRING function.
     -- ISNULL/NULLIF combo handles the last value where there is no delimiter at the end of the string.
  ,l(s,l) as (select s,isnull(nullif(charindex(@delimiter,isnull(@str,''),s),0)-s,4000) from s)

 select rn
          ,item
 from(select row_number() over(order by s) as rn
    ,substring(@str,s,l) as item
  from l
  ) a
 where rn = @num
  or @num is null;
GO

答案 1 :(得分:2)

如果您将PassengerName视为敏感信息,则不应将其以明文形式存储在通常可访问的表中。周期。

有几种不同的选择。

一个是提供敏感信息的参考表。引用它的任何表都将具有id而不是名称。中提琴。如果没有访问参考表,就无法获得敏感信息,这将受到严格限制。

第二种方法是可逆压缩算法。这将使得价值变得胡言乱语,但是如果有正确的知识,它可以转化为有意义的价值。典型的方法是由Rivest,Shamir和Adelman(RSA编码)设计的公钥加密算法。

如果你想做名字的第一个和最后一个字母,我会非常小心亚洲名字。当用拉丁文写成时,其中许多由两到三个字母组成。这并不是很隐蔽。 SQL Server没有简单的机制来执行此操作。您可以使用循环编写用户定义的函数来管理进程。但是,我认为这是最不安全和最不可取的方法。

答案 2 :(得分:2)

这使用了Jeff Moden的DelimitedSplit8K,以及SQL Server 2017 STRING_AGG中的新功能。因为我不知道你正在使用什么版本,所以我已经离开了#34;整个生猪"并且假设您使用的是最新版本。

Jeff的功能在这里非常宝贵,因为它返回了序数位置,这是微软从他们自己的函数中忽略了愚蠢的东西,STRING_SPLIT(并且没有2017年加入)。顺序位置是关键,因此我们无法使用内置函数。

WITH VTE AS(
    SELECT *
    FROM (VALUES ('John Doe'),('Jane Bloggs'),('Edgar Allan Poe'),('Mr George W. Bush'),('Homer J Simpson')) V(FullName)),
Masking AS (
    SELECT *,
           ISNULL(STUFF(Item, 2, LEN(item) -2,'***'), Item) AS MaskedPart
    FROM VTE V
         CROSS APPLY dbo.delimitedSplit8K(V.Fullname, ' '))
SELECT STRING_AGG(MaskedPart,' ') AS MaskedFullName
FROM Masking
GROUP BY Fullname;

编辑:没关系,OP评论说他们正在使用2008,所以STRING_AGG是不可能的。然而,@ amddave发布了一个与我自己非常相似的答案,只需按照旧式的XML方式进行操作即可。

答案 3 :(得分:2)

根据您的SQL Server版本,您可以使用内置字符串拆分为名称中空格的行,执行字符串格式设置,然后使用XML路径回滚到名称级别。

create table dataset (id int identity(1,1), name varchar(50));
insert into dataset (name) values
('John Smith'),
('Edgar Allen Poe'),
('One Two Three Four');

with split as (
select id, cs.Value as Name
from dataset
cross apply STRING_SPLIT (name, ' ') cs
),
formatted as (
select
  id,
  name,
  left(name, 1) + '***' + right(name, 1) as out
from split
)
SELECT 
   id, 
   (SELECT ' ' + out 
    FROM formatted b
    WHERE a.id = b.id
    FOR XML PATH('')) [out_name]
FROM formatted a
GROUP BY id

结果:

id   out_name
1    J***n S***h
2    E***r A***n P***e
3    O***e T***o T***e F***r

答案 4 :(得分:2)

您可以使用此功能执行此操作。

create function [dbo].[fnMaskName] (@var_name varchar(100))
RETURNS varchar(100)
WITH EXECUTE AS CALLER
AS
BEGIN
    declare @var_part varchar(100)
    declare @var_return varchar(100)
    declare @n_position smallint

    set @var_return = ''
    set @n_position = 1

    WHILE @n_position<>0
    BEGIN
        SET @n_position = CHARINDEX(' ', @var_name)
        IF @n_position = 0
            SET @n_position = LEN(@var_name)

        SET @var_part = SUBSTRING(@var_name, 1, @n_position)
        SET @var_name = SUBSTRING(@var_name, @n_position+1, LEN(@var_name))
        if @var_part<>''
            SET @var_return = @var_return + stuff(@var_part, 2, len(@var_part)-2, replicate('*',len(@var_part)-2)) + ' ' 
    END
    RETURN(@var_return)

END