【问题标题】:Expression to find multiple spaces in string在字符串中查找多个空格的表达式
【发布时间】:2018-02-19 16:12:58
【问题描述】:

我们处理大量敏感数据,我想仅使用每个姓名部分的首字母和最后一个字母来屏蔽乘客姓名,并用三个星号 (***) 连接,

例如:名称“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

但是,乘客姓名可以有两个以上的部分,并没有真正的限制。我应该如何找到表达式中所有空格的索引?或者我应该以不同的方式解决这个问题?

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

【问题讨论】:

  • 如果您将整个名称存储在一个列中,那么您正在打一场不可能的战斗。你怎么知道名字的结尾和姓氏的开始?到目前为止,最好的方法是使用两列而不是一列来保存他们的名字。您对单个列所做的任何事情都是脆弱的,并且不会总是有效。
  • 解析名称可能是数据库中最古老的问题。没有包罗万象的解决方案,因为每个解决方案都 100% 依赖于您的数据。
  • 如果某人的名字是'Edgar Allan Poe',你会期待什么?你想输出为'E***r A***n P***e'吗?什么版本的 SQL Server?此外,如果您打算这样做来尝试掩盖人们的姓名,那么有一些非常聪明的应用程序可能会计算出姓名的实际含义(尤其是使用如此简单的算法)。
  • 第一个猜测:选择一个返回结果表的字符串拆分器,应用一个函数来标准化空白(制表符,... -> 单个空白),在空白处拆分, 对X the paragrab 应用函数, 重新组装, 满足地叹息。 (闻起来很糟糕。)

标签: sql-server string tsql


【解决方案1】:

根据 Gordon 在他的回答中的解释,此解决方案可以满足您的要求,但在尝试隐藏个人身份数据时确实是错误的方法。

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

【讨论】:

  • 您链接的 Jeff's 的 8K 分离器远远优于更旧的 4k 版本。
  • @SeanLange 可能是真的,它不能处理 unicode。
  • 我认为 OP 没有使用 nvarchar。如果是的话,我希望他们的示例也能说明他们尝试将nvarchar 用于他们的字符串文字。如:CHARINDEX(N' ', PassengerName) = 0+ N'***'
  • @SeanLange 实际上,您似乎在引用根本不基于的东西。我在上面发布的功能只是使用与 4k 分离器相同的逻辑来处理 nvarchar 值的 8k 分离器。在我 2016 年之前的环境中,我通常会在此处发布一个 8k 和基于 XML 的拆分器,用于处理最适合使用的 max 数据类型。
  • 没错。我不知道 4K 版本在多大程度上跟上了多年来所做的各种性能改进。无论哪种方式,这都是一个很好的解决方案,我绝不想偏离主题。
【解决方案2】:

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

有几种不同的选择。

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

第二种方法是可逆压缩算法。这将允许该值是胡言乱语,但是通过正确的知识,它可以转换回一个有意义的值。典型的方法是 Rivest、Shamir 和 Adelman 设计的公钥加密算法(RSA 编码)。

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

【讨论】:

  • 感谢您的考虑。但是,这只是一个技术问题,而不是从我的角度讨论如何存储敏感信息。所以如果我理解你,我应该去一个用户定义的函数?
【解决方案3】:

这使用了 Jeff Moden 的 DelimitedSplit8K,以及 SQL Server 2017 STRING_AGG 中的新功能。由于我不知道您使用的是什么版本,所以我只是“彻底”并假设您使用的是最新版本。

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 是不可能的。然而,@iamdave 发布了一个与我自己非常相似的答案,只需使用“老式 XML 方式”即可。

【讨论】:

    【解决方案4】:

    根据您的 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
    

    【讨论】:

      【解决方案5】:

      你可以使用这个函数来做到这一点。

      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
      

      【讨论】:

      • A WHILE 将是一个执行速度非常慢的答案,尤其是当它也在标量函数中时。数据集方法将更加有效。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-03-10
      • 2019-09-04
      • 1970-01-01
      • 1970-01-01
      • 2018-05-24
      • 1970-01-01
      相关资源
      最近更新 更多