SQL Server - 删除所有不可打印的 ASCII 字符

Posted

技术标签:

【中文标题】SQL Server - 删除所有不可打印的 ASCII 字符【英文标题】:SQL Server - Remove all non-printable ASCII characters 【发布时间】:2017-08-26 04:52:00 【问题描述】:

我们最近从 SQL Server 2012 迁移到 SQL Server 2014,我们所有的 FOR XML 代码开始抛出有关不可打印的 ASCII 字符的错误。 我写了这个 horrible 函数来删除不可打印的 ASCII 字符作为快速修复。我想用更清洁的东西代替它。有没有办法做到这一点?

ALTER FUNCTION [dbo].[remove_non_printable_chars]
(@input_string nvarchar(max))
RETURNS nvarchar(max)
BEGIN
    RETURN
    REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
    REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
    REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
    REPLACE(REPLACE(@input_string,
        CHAR(1), ''),CHAR(2), ''),CHAR(3), ''),CHAR(4), ''),CHAR(5), ''),CHAR(6), ''),CHAR(7), ''),CHAR(8), ''),CHAR(9), ''),CHAR(10), ''),
        CHAR(11), ''),CHAR(12), ''),CHAR(13), ''),CHAR(14), ''),CHAR(15), ''),CHAR(16), ''),CHAR(17), ''),CHAR(18), ''),CHAR(19), ''),CHAR(20), ''),
        CHAR(21), ''),CHAR(22), ''),CHAR(23), ''),CHAR(24), ''),CHAR(25), ''),CHAR(26), ''),CHAR(27), ''),CHAR(28), ''),CHAR(29), ''),CHAR(30), ''),
        CHAR(31), ''), NCHAR(0) COLLATE Latin1_General_100_BIN2, '')
END

这是损坏的FOR XML 代码。 (这不是我写的。它已经在代码库中了)。

SELECT @htmlTableData =
(
    SELECT  HTMLRows 
    FROM (
        SELECT N'<tr>' + HTMLRow + N'</tr>' AS HTMLRows 
        FROM @HTMLRowData
    ) mi            
    FOR XML PATH(''), TYPE
).value('/', 'NVARCHAR(MAX)')

【问题讨论】:

这有点麻烦,但嵌套替换是删除这些特定字符所必须发生的事情。嵌套替换也超级快。您可能会考虑将其转换为内联表值函数而不是标量函数。 FWIW,我不认为这个功能很可怕。需要很多丑陋的代码……首先是功能的重点。 :) 我会在 codereview.stackexchange.com 上发布这个 网页搜索结果:sqlservercentral.com/Forums/Topic860321-338-1.aspx @SeanLange 谢谢你的好话 :) 这句话让我思考:我们最近从 SQL Server 2012 迁移到 SQL Server 2014,我们所有的 FOR XML 代码开始抛出有关不可打印的 ASCII 字符的错误为什么以及哪些错误? 您是如何创建 XML 的?这不应该发生...阅读XY-Problem 可能有助于展示一些在 SS12 中有效但不适用于 SS14 的示例。我很确定,问题 - 及其解决方案! - 在别的地方... 【参考方案1】:

另一个选项。

此函数将替换控制字符并更正任何残留的重复空格。例如Jane Smith13was here 不会返回为Jane Smithwas here,而是Jane Smith was here

CREATE FUNCTION [dbo].[udf-Str-Strip-Control](@S varchar(max))
Returns varchar(max)
Begin
    ;with  cte1(N) As (Select 1 From (Values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) N(N)),
           cte2(C) As (Select Top (32) Char(Row_Number() over (Order By (Select NULL))-1) From cte1 a,cte1 b)
    Select @S = Replace(@S,C,' ')
     From  cte2

    Return ltrim(rtrim(replace(replace(replace(@S,' ','†‡'),'‡†',''),'†‡',' ')))
End
--Select [dbo].[udf-Str-Strip-Control]('Michael        '+char(13)+char(10)+'LastName')  --Returns: Michael LastName

【讨论】:

谢谢。我明天试试这个。我不确定是否要添加空格,因为控制字符可能在一个单词中。 @Munir 足够公平,然后只需将 Select @S = Replace(@S,C,' ') 更改为 Select @S = Replace(@S,C,'') 但是,您运行连接单词的风险 @scsimon 你能怪我吗?当我第一次看到它时,我的第一个想法是“嗯……”然后我突然想到……F'n太棒了! 拍摄编号。我仍然记得那个答案并且被震撼了,因此我现在记得它! 所以,如果我理解正确,cte1(N) 创建一个包含 10 个 1 的表,cte2 将它们转换为 char(0) 到 char(100) 并选择前 32 个,选择替换所有return 语句中的控制字符和表达式将所有多个空格转换为单个空格。由于我想按原样保留输入,因此我返回 @S 而不做任何更改。这对我来说也比嵌套替换要快得多,所以我会使用它。谢谢!【参考方案2】:

在线版本:

create function [dbo].[remove_non_printable_chars] (@input_string nvarchar(max))
returns table with schemabinding as return (
  select 
    replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(
    replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(
    replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(
    replace(replace(@input_string collate latin1_general_100_bin2,
        char(1), ''),char(2), ''),char(3), ''),char(4), ''),char(5), ''),char(6), ''),char(7), ''),char(8), ''),char(9), ''),char(10), ''),
        char(11), ''),char(12), ''),char(13), ''),char(14), ''),char(15), ''),char(16), ''),char(17), ''),char(18), ''),char(19), ''),char(20), ''),
        char(21), ''),char(22), ''),char(23), ''),char(24), ''),char(25), ''),char(26), ''),char(27), ''),char(28), ''),char(29), ''),char(30), ''),
        char(31), ''), char(0) , '') 
     as clean_string
);
go

然后像这样使用它:

select c.clean_string
from dbo.remove_non_printable_chars(@dirtystring) c

select ...
  , c.clean_string
from t
  cross apply dbo.remove_non_printable_chars(t.dirty_string) c

参考:

When is a SQL function not a function? "If it’s not inline, it’s rubbish." - Rob Farley Inline Scalar Functions - Itzik Ben-Gan Scalar functions, inlining, and performance: An entertaining title for a boring post - Adam Machanic TSQL User-Defined Functions: Ten Questions You Were Too Shy To Ask - Robert Sheldon

【讨论】:

谢谢...我会等待其他答案。如果没有,我将切换到表值函数。 看起来你被投反对票的同一个人投反对票。我想知道他知道我们错过了什么。 :) @JohnCappelletti 我想我们永远不会知道 能否详细说明collate latin1_general_100_bin2的用法?起初,我试图省略它,因为我的数据库有不同的排序规则,但我注意到对于约 3000 行的查询,该函数要快得多 with collate latin1_general_100_bin2(2 秒对 30 秒)...我不明白为什么。 @ChristianSpecht 我担心即使没有排序规则开关,查询也需要 30 秒才能运行超过 3,000 行。您使用的这些字符串有多大?我没想到会有这么大的不同。 Guidelines for Using BIN and BIN2 Collations 和 Paul White's answer about binary collation performance【参考方案3】:

只是稍微扩展了之前的答案

白名单字符以下的字符将被清除

[ !`"#$%&amp;'()\*+,\-\./0123456789:;&lt;=&gt;?@ABCDEFGHIJKLMNOPQRSTUVWXYZ\[\]^``\\_abcdefghijklmnopqrstuvwxyz|~µº°¡¢£¤¥¦§¨©ª«¬­®¯±²³´¶·¸¹»¼½¾¿×÷ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿ]

我知道它很难看,但它确实有效。

CREATE FUNCTION [dbo].REPLACE_UNPRINT_CHARS(@VarString nvarchar(256))  
RETURNS nvarchar(256) 
AS    
BEGIN  
    RETURN REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(@VarString, CHAR(0), ''), CHAR(1), ''), CHAR(2), ''), CHAR(3), ''), CHAR(4), ''), CHAR(5), ''), CHAR(6), ''), CHAR(7), ''), CHAR(8), ''), CHAR(9), ''), CHAR(10), ''), CHAR(11), ''), CHAR(12), ''), CHAR(13), ''), CHAR(14), ''), CHAR(15), ''), CHAR(16), ''), CHAR(17), ''), CHAR(18), ''), CHAR(19), ''), CHAR(20), ''), CHAR(21), ''), CHAR(22), ''), CHAR(23), ''), CHAR(24), ''), CHAR(25), ''), CHAR(26), ''), CHAR(27), ''), CHAR(28), ''), CHAR(29), ''), CHAR(30), ''), CHAR(31), ''), CHAR(127), ''), CHAR(128), ''), CHAR(129), ''), CHAR(130), ''), CHAR(131), ''), CHAR(132), ''), CHAR(133), ''), CHAR(134), ''), CHAR(135), ''), CHAR(136), ''), CHAR(137), ''), CHAR(138), ''), CHAR(139), ''), CHAR(140), ''), CHAR(141), ''), CHAR(142), ''), CHAR(143), ''), CHAR(144), ''), CHAR(145), ''), CHAR(146), ''), CHAR(147), ''), CHAR(148), ''), CHAR(149), ''), CHAR(150), ''), CHAR(151), ''), CHAR(152), ''), CHAR(153), ''), CHAR(154), ''), CHAR(155), ''), CHAR(156), ''), CHAR(157), ''), CHAR(158), ''), CHAR(159), ''), CHAR(160), ''); 
END;

用于数据清理

UPDATE [dnName].[dbo].[tableName] 
SET FieldDirtyData= dbo.REPLACE_UNPRINT_CHARS(FieldDirtyData)
WHERE PATINDEX('%['+CHAR(1)+CHAR(2)+CHAR(3)+CHAR(4)+CHAR(5)+CHAR(6)+CHAR(7)+CHAR(8)+CHAR(9)+CHAR(10)+CHAR(11)+CHAR(12)+
CHAR(13)+CHAR(14)+CHAR(15)+CHAR(16)+CHAR(17)+CHAR(18)+CHAR(19)+CHAR(20)+
CHAR(21)+CHAR(22)+CHAR(23)+CHAR(24)+CHAR(25)+CHAR(26)+CHAR(27)+CHAR(28)+CHAR(29)+CHAR(30)+CHAR(31)+CHAR(127)+
CHAR(128)+CHAR(129)+CHAR(130)+CHAR(131)+CHAR(132)+CHAR(133)+CHAR(134)+CHAR(135)+CHAR(136)+CHAR(137)+CHAR(138)+
CHAR(139)+CHAR(140)+CHAR(141)+CHAR(142)+CHAR(143)+CHAR(144)+CHAR(145)+CHAR(146)+CHAR(147)+CHAR(148)+CHAR(149)+CHAR(150)+
CHAR(151)+CHAR(152)+CHAR(153)+CHAR(154)+CHAR(155)+CHAR(156)+CHAR(157)+CHAR(158)+CHAR(159)+CHAR(160)+']%', FieldDirtyData) <> 0

根据需要调整您的数据类型(nvarchar 或 varchar + max)

如果要添加更多字符以清除,请使用“select ASCII('char to remove here')”MSSQL 命令以获取字符的 ASCII 码并将其放入替换指令中

i.g SELECT ASCII('¢') 返回 162

所以在 "RETURN" 和 "CHAR(162), '')" 之后再添加一个 "REPLACE(" 在行尾但在 ";" 之前签名。

【讨论】:

现在我们只希望只有拉丁字母语言的用户才能使用该应用程序。【参考方案4】:

这是此问题的前一个内联表值函数答案 (https://***.com/a/43148897/2864740) 的类似答案。主要的变化是它首先使用patindex 保护,当只有一小部分行包含需要替换的字符时,速度要快得多

因此,原始标量函数的两个巨大改进:

使用内联表值函数。这要快得多,因为它允许 SQL Server 直接内联查询计划中的代码。我尽量避免在旨在扩展的查询中使用标量函数,因为普通的标量函数可能会造成巨大的性能损失(即使使用模式绑定)并且会阻止并行性等优化。

使用patindex 进行初始保护检查。这会将在没有要替换的控制字符时 SQL 必须检查的字符数从 O(n * num_replace_calls) 更改为 ~O(n)。由于大多数数据数据(在我的情况下)不包含控制字符,因此可以显着提高性能。

-- Only accepts VARCHAR(8000) to avoid a conversion to VARCHAR(MAX);
-- use the suitable input type, which might even be NVARCHAR(MAX).
CREATE FUNCTION DropControlCharactersTv(@str VARCHAR(8000))
RETURNS TABLE
WITH SCHEMABINDING
AS
    RETURN SELECT CleanedString = CASE
    -- No-op.
    WHEN @str IS NULL or @str = '' THEN @str
    -- If any of the non-wanted characters are found then go through the string and replace each occurrence of every character.
    -- This guard significantly improves the performance when very few strings need to be corrected.
    WHEN PATINDEX (
        '%[' + CHAR(0) + CHAR(1) + CHAR(2) + CHAR(3) + CHAR(4) + CHAR(5) + CHAR(6) + CHAR(7) + CHAR(8) + CHAR(11) + CHAR(12) + CHAR(14) + CHAR(15) + CHAR(16) + CHAR(17) + CHAR(18) + CHAR(19) + CHAR(20) + CHAR(21) + CHAR(22) + CHAR(23) + CHAR(24) + CHAR(25) + CHAR(26) + CHAR(27) + CHAR(28) + CHAR(29) + CHAR(30) + CHAR(31) + CHAR(127) + ']%',
        @str COLLATE Latin1_General_BIN) <> 0 THEN
        -- Replace, nested.
        -- See https://www.sqlservercentral.com/forums/topic/how-to-remove-characters-char0-to-char31
        REPLACE(
        REPLACE(
        REPLACE(
        REPLACE(
        REPLACE(
        REPLACE(
        REPLACE(
        REPLACE(
        REPLACE(
        REPLACE(
        REPLACE(
        REPLACE(
        REPLACE(
        REPLACE(
        REPLACE(
        REPLACE(
        REPLACE(
        REPLACE(
        REPLACE(
        REPLACE(
        REPLACE(
        REPLACE(
        REPLACE(
        REPLACE(
        REPLACE(
        REPLACE(
        REPLACE(
        REPLACE(
        REPLACE(
        REPLACE(
        REPLACE(
        REPLACE(
        REPLACE(
        @str
        ,CHAR(0),'') COLLATE Latin1_General_BIN
        ,CHAR(1),'') COLLATE Latin1_General_BIN
        ,CHAR(2),'') COLLATE Latin1_General_BIN
        ,CHAR(3),'') COLLATE Latin1_General_BIN
        ,CHAR(4),'') COLLATE Latin1_General_BIN
        ,CHAR(5),'') COLLATE Latin1_General_BIN
        ,CHAR(6),'') COLLATE Latin1_General_BIN
        ,CHAR(7),'') COLLATE Latin1_General_BIN
        ,CHAR(8),'') COLLATE Latin1_General_BIN
        ,CHAR(9),'') COLLATE Latin1_General_BIN
        ,CHAR(10),'') COLLATE Latin1_General_BIN
        ,CHAR(11),'') COLLATE Latin1_General_BIN
        ,CHAR(12),'') COLLATE Latin1_General_BIN
        ,CHAR(13),'') COLLATE Latin1_General_BIN
        ,CHAR(14),'') COLLATE Latin1_General_BIN
        ,CHAR(15),'') COLLATE Latin1_General_BIN
        ,CHAR(16),'') COLLATE Latin1_General_BIN
        ,CHAR(17),'') COLLATE Latin1_General_BIN
        ,CHAR(18),'') COLLATE Latin1_General_BIN
        ,CHAR(19),'') COLLATE Latin1_General_BIN
        ,CHAR(20),'') COLLATE Latin1_General_BIN
        ,CHAR(21),'') COLLATE Latin1_General_BIN
        ,CHAR(22),'') COLLATE Latin1_General_BIN
        ,CHAR(23),'') COLLATE Latin1_General_BIN
        ,CHAR(24),'') COLLATE Latin1_General_BIN
        ,CHAR(25),'') COLLATE Latin1_General_BIN
        ,CHAR(26),'') COLLATE Latin1_General_BIN
        ,CHAR(27),'') COLLATE Latin1_General_BIN
        ,CHAR(28),'') COLLATE Latin1_General_BIN
        ,CHAR(29),'') COLLATE Latin1_General_BIN
        ,CHAR(30),'') COLLATE Latin1_General_BIN
        ,CHAR(31),'') COLLATE Latin1_General_BIN
        ,CHAR(127),'') COLLATE Latin1_General_BIN
    -- Did not match pattern: inherently valid
    ELSE @str END

在查询中:

select
    Plucker.CleanedString
from Goose d
cross apply DropControlCharactersTv(d.Turkey) as Plucker

【讨论】:

以上是关于SQL Server - 删除所有不可打印的 ASCII 字符的主要内容,如果未能解决你的问题,请参考以下文章

数据库SQL server规则的创建查看修改和规则的绑定与松绑删除

SQL Server:使用聚合运算符 COUNT() 打印左表中的所有行

sqlserver数据库表数据误删除了 怎么恢复

Sql Server 删除所有表

sql 删除SQL Server的所有存储过程

sql 删除所有非聚集索引 - SQL Server