【问题标题】:SQL - Remove pattern from varchar stringSQL - 从 varchar 字符串中删除模式
【发布时间】:2021-04-19 07:18:57
【问题描述】:

我们的一个应用程序在我们的一个列的末尾附加了注释,我们现在已将其从应用程序中删除,并希望从数据中删除现有的。 我在 Varchar (2000) String 中有一个模式:

  • 2019 年 8 月 11 日收到信用(USERNAME @ 08/11/2019)未删除记录
  • 请求删除发往地址的记录未由 (USERNAME @ 20/09/2019) 删除

注意事项:

  • 模式可以在字符串中的任何位置
  • 模式可以在字符串中多次出现
  • 用户名不同
  • 日期变更

不确定最好的方法是什么?

【问题讨论】:

  • 所以它的“模式”总是“由 ({USERNAME} @ {date}) 取消删除的记录”如果是这样,你可以很容易地实现这一点,而无需模式匹配,但字符串匹配(使用 CHARINDEX )。如果不是,那么还有哪些其他“模式”? SQL Server 的强项是 not 字符串操作,所以如果你要支持这个复杂的(目前还没有),那么最好的方法是使用支持 REGEX 的语言。
  • 模式可以多次出现的事实是一个“问题”。这样的东西很容易更适合模式替换;哪些 T-SQL 本身不支持。
  • 如果你在CLR中添加一个regexReplace函数,这变得像单个更新表一样简单
  • @Larnu - 没有其他这样的模式,可能有很多其他文本 - 忘了提到这是在 SQL 2005 服务器上。但是,是的,“由 ({USERNAME} @ {date}) 取消删除的记录”是一致的模式。关于它出现多次 - 我想我可以多次运行脚本?
  • “忘了说这是在 SQL 2005 服务器上” 这是你应该在开始时在标签中提到的;这真的把我的每一个想法都抛到了窗外。您需要尽快更新 5 年报废产品。我只能建议尝试找到一个适用于 2005 的 CLR 函数。祝你好运……使用 SQL Server 2005 既是安全风险,也是一个巨大的限制因素。

标签: sql-server tsql sql-server-2005


【解决方案1】:

正如我所说,使用这样的 UDF:

CREATE FUNCTION dbo.F_REMOVE_RECORD_UNDELETE (@ROW VARCHAR(max))
RETURNS VARCHAR(max)
AS
BEGIN
   DECLARE @RESULT VARCHAR(max) = '';
   DECLARE @POSITION INT = CHARINDEX('record undeleted by (', @ROW);
   WHILE @POSITION > 0
   BEGIN
      SET @RESULT = @RESULT + LEFT(@ROW, @POSITION - 1);
      SET @ROW = RIGHT(@ROW, LEN(@ROW) - CHARINDEX(')', @ROW, @POSITION + 1));
      SET @POSITION = CHARINDEX('record undeleted by (', @ROW);
   END
   RETURN @RESULT;
END;
GO

修改很容易与 LIKE 组合:

CREATE TABLE T_TEST_STRING_TST
(TST_ID       INT IDENTITY PRIMARY KEY,
 TST_STRING   VARCHAR(8000));
GO

INSERT INTO T_TEST_STRING_TST VALUES
(' noting '),
('Credit received 08/11/2019 record undeleted by (USERNAME @ 08/11/2019) bla bla bla'),
('anyting record undeleted ()'),
('Request removal of ship to adress record undeleted by (USERNAME @ 20/09/2019)bolo bolo'),
('Request removal of ship to adress record undeleted by (USERNAME @ 20/09/2019)bolo bolo record undeleted by (USERNAME @ 11/12/2020) bolo bolo'),
('anyting record undeleted ( @ 08/11/2019)');

UPDATE T_TEST_STRING_TST
SET    TST_STRING =  dbo.F_REMOVE_RECORD_UNDELETE(TST_STRING)
WHERE  TST_STRING LIKE '%record undeleted by (% @ [0-3][0-9]/[0-1][0-9]/[1-2][0-9][0-9][0-9])%';

对于 SQL 2005,您必须将 UDF 重写为:

CREATE FUNCTION dbo.F_REMOVE_RECORD_UNDELETE (@ROW VARCHAR(max))
RETURNS VARCHAR(max)
AS
BEGIN
   DECLARE @RESULT VARCHAR(max);
   SET @RESULT = '';
   DECLARE @POSITION INT;
   SET POSITION = CHARINDEX('record undeleted by (', @ROW)
   WHILE @POSITION > 0
   BEGIN
      SET @RESULT = @RESULT + LEFT(@ROW, @POSITION - 1);
      SET @ROW = RIGHT(@ROW, LEN(@ROW) - CHARINDEX(')', @ROW, @POSITION + 1));
      SET @POSITION = CHARINDEX('record undeleted by (', @ROW);
   END
   RETURN @RESULT;
END;
GO

【讨论】:

  • 这已按需要工作。我需要稍微调整一下以解决“无法为 SQL 中的局部变量分配默认值”错误 (2005)
  • 如果您要从同一个字符串中删除两次匹配项(如表 T_TEST_STRING_TST 的第 5 行),它会起作用吗?
  • @AhmedHuq 是的,测试一下!
【解决方案2】:

你已经被告知,SQL Server v2005 是你真正应该改变的东西。

我不确定这是否适用于 v2005,没有机会在任何地方进行测试,但您可以尝试一下:

DECLARE @tbl TABLE(ID INT IDENTITY, YourString NVARCHAR(MAX));
INSERT INTO @tbl(YourString) VALUES
 ('Credit received 08/11/2019 record undeleted by (USERNAME @ 08/11/2019) Some more to come record undeleted by (SOMEONEELSE @ 10/11/2019)')
,('Request removal of ship to adress record undeleted by (ONEMORE @ 20/09/2019)');

SELECT t.ID
      ,CAST(REPLACE(REPLACE(YourString,' record undeleted by','<!--'),')','-->') AS XML).value('.','nvarchar(max)')
FROM @tbl t;

简而言之:

  • 我们使用CAST() 和一些字符串方法来
    • 将其转换为 XML 并
    • 将不需要的文本包装为 XML 注释。
  • 现在我们可以使用.value() 来读取XML 的内容。注释被省略。

一个中间结果如下所示

Credit received 08/11/2019<!-- (USERNAME @ 08/11/2019--> Some more to come<!-- (SOMEONEELSE @ 10/11/2019-->

您可以看到,XML 注释中包含任何不需要的内容。

提示 1:假设使用 ) 作为替换字符是不够的。但在结果字符串中用) 重新替换--&gt; 可能就足够了。

提示 2:从 v2008 开始,STUFF() 可以用注释结束标记替换(=覆盖)@ 之后给定数量的字符。在 v2005 中,您将不得不循环...

提示 3:如果 立即 CAST().value() 语法在 v2005 中不受支持,请尝试在没有 .value() 的情况下运行它。然后你可以分两步进行...

更新

对于我的“提示 1”,这将有所帮助:

SELECT t.ID
      ,     REPLACE(
       CAST(REPLACE(
            REPLACE(YourString,' record undeleted by','<!--')
                                                     ,')','-->') AS XML).value('.','nvarchar(max)')
                                                     ,'-->',')')
FROM @tbl t;

【讨论】:

  • 从我发布的表测试中,结果是不正确的离开:“任何未删除的记录(-->”和“未删除的任何记录(@ 08/11/2019-->”)。另一点是使用 XML 的可怕代价!
  • @SQLpro,请仔细查看...在您的示例中,您有时会错过“by”。这就是为什么某些结果返回不正确的原因。您可以使用我在REPLACE() 中不带“by”的代码作为快速检查。此外,在我的“提示 1”中,我建议使用另一个 REPLACE(),以防右括号可能被转换为 --&gt;。而且 - 最后但并非最不重要的一点 - OP 告诉我们,这是一个 one-timer。所以性能并不重要......
  • @SQLpro,顺便说一句:调用 WHILE 循环的标量函数也不会很快 :-)
【解决方案3】:

第二次尝试更新: 这次我创建了以下 UDF,它试图找出用户名并基于它替换模式。由于它是 UDF,因此它适用于模式的多次出现。我认为从速度上来说这不是一个好的解决方案。

GO

CREATE FUNCTION dbo.test(@input varchar(2000))
RETRUNS varchar(2000)
AS
BEGIN

DECLARE
@start_pos int = 0
,@end_pos1 int = 0
,@r varchar(2000) = ''
,@r1 varchar(2000) = ''
,@to_del varchar(2000) = ''
,@user_name varchar(2000) = ''

set @r = @input

WHILE patindex('%record undeleted by_(%_@_[0-3][0-9]/[0-1][0-9]/[0-9][0-9][0-9][0-9])%', @r) > 0
BEGIN
    set @start_pos = 0; set @r1 = ''; set @user_name = ''; set @end_pos1 = 0 set @to_del = '';
    
    set @start_pos = len('record undeleted by_(') + patindex('%record undeleted by_(%_@_[0-3][0-9]/[0-1][0-9]/[0-9][0-9][0-9][0-9])%', @r) 
    set @r1 = substring(@r, @start_pos, len(@r))
    set @user_name = ltrim(rtrim(substring(@r1, 1, patindex('%_@_[0-3][0-9]/[0-1][0-9]/[0-9][0-9][0-9][0-9])%', @r1))))
    set @end_pos1 = LEN(@user_name) + 15
    set @to_del = 'record undeleted by ('+ ltrim(rtrim(substring(@r1, 1, @end_pos1)))
    set @r = replace(@r, @to_del, '')
END
    RETURN @r
END

GO

以下是使用 UDF 的测试表和 SELECT 语句的代码。

declare @t table
(
id int identity(1,1)
,dscr varchar(2000)
)

insert into @t(dscr)
select 'Credit received 08/11/2019 record undeleted by (@ahmedhuq @ 08/11/2019)' union
select 'Request removal of ship to address record undeleted by (group1\user1 @ 20/09/2019)' union
select 'Request removal of ship to address record undeleted by (group1\user1 @ 20/09/2019) and the quick brown fox' union
select 'Request removal of ship to address record undeleted by (dir1\user2 @ 20/09/2019) and Credit received 08/11/2019 record undeleted by (@ahmedhuq @ 08/11/2019)' union
select 'Request removal of ship to address record undeleted' 

select * , dbo.test(dscr)
from @t where dscr like '%record undeleted by_(%_@_[0-3][0-9]/[0-1][0-9]/[0-9][0-9][0-9][0-9])%'

结果如下:

忽略尝试 1: 下面的脚本不能用作工作解决方案。如果模式只出现一次,它就可以正常工作。对于多次出现的模式,数据会损坏!

declare @t table
(
id int identity(1,1)
,dscr varchar(2000)
)

insert into @t(dscr)
select 'Credit received 08/11/2019 record undeleted by (@ahmedhuq @ 08/11/2019)' union
select 'Request removal of ship to address record undeleted by (group1\user1 @ 20/09/2019)' union
select 'Request removal of ship to address record undeleted by (group1\user1 @ 20/09/2019) and the quick brown fox' union

select 'Request removal of ship to address record undeleted by (dir1\user2 @ 20/09/2019) and Credit received 08/11/2019 record undeleted by (@ahmedhuq @ 08/11/2019)' union
select 'Request removal of ship to address record undeleted' 

select * from @t
where dscr like '%record undeleted by_(%_@___/__/____)%'

;with a as
(
    select * 
    ,patindex( '%record undeleted by_(%_@___/__/____)%', dscr) as start_pos
    ,patindex( reverse('%record undeleted by_(%_@___/__/____)%'), reverse(dscr)) as end_pos_reverse
    ,len(dscr) - patindex( reverse('%record undeleted by_(%_@___/__/____)%'), reverse(dscr)) + 2 as end_pos
    from 
    @t
)
,a1 as
(
    select *
    ,end_pos - start_pos as start_end_len
    from
    a
)
,a2 as
(
    select 
    *,substring(dscr, start_pos, start_end_len) replace_str
    from
    a1
)
select
*
,replace(dscr, replace_str, '') as result1
from
a2

【讨论】:

  • STUFF() 是在 v2008 之前引入的......(afaik)。
  • 感谢@Shnugo 指出这一点。我已修改脚本以使用子字符串和替换,保留 CTE 以显示步骤。不幸的是,它不适用于多模式,不仅如此,它还会破坏数据。这么看……
  • 我已将我的第二次尝试添加为 UDF,这似乎工作正常。
猜你喜欢
  • 2022-01-20
  • 2020-06-09
  • 1970-01-01
  • 2014-03-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-04-13
  • 2012-07-31
相关资源
最近更新 更多