在 SQL 中将不可打印的 ascii 字符显示为 :ascii: 或 :print: 不起作用

display non-printable ascii characters in SQL as :ascii: or :print: does not work

我正在尝试使用 TOAD 中的 SQL 从 table 的 DESCRIPTION 字段中获取所有非原则 table ASCII 字符,但是以下查询不起作用。

select 
regexp_instr(a.description,'[^[:ascii:]]') as description from
poline a where a.ponum='XXX' and a.siteid='YYY' and 
regexp_instr(a.description,'[^[:ascii:]]') > 0 

以上查询购买了错误 ORA-127729:正则表达式中的无效字符 class。我试过 :print: 而不是 :ascii: 但是它没有带来任何结果。以下是此记录的描述,其中包含非 printable 字符。

Sherlock 16 x 6.5” Wide Wheelbarrow wheel .M100P.10R – Effluent care bacteria and enzyme formulation

:ascii: 不是有效字符 class,即使是,它似乎也不是您要在此处获取的字符(ascii 确实包含非原则 table 个字符)。可以找到有效的 classes here

实际上,如果您在原始查询中将 :ascii: 替换为 :print:,它确实会 return 每个 POLINE.DESCRIPTION 中的第一个非原则table字符。 (如果return对你来说没什么,那可能是因为你的DESCRIPTION数据其实都是printable。)

但是正如您所说,您想要识别每个 Every non-printable 字符 DESCRIPTIONPOLINE 中,需要进行一些更改。我将包括一个将每场比赛作为起点的例子。

在此示例中,每个 DESCRIPTION 将被分解为其单独的组成字符,并且将检查每个字符的可打印性。 DESCRIPTION 字符串中的位置以及非原则 table 字符的 ASCII number 将被 returned。

此示例假设 POLINE 中的每一行都有一个唯一标识符,此处称为 POLINE_ID

首先,创建测试 table:

CREATE TABLE POLINE(
  POLINE_ID NUMBER PRIMARY KEY,
  PONUM VARCHAR2(32),
  SITEID VARCHAR2(32),
  DESCRIPTION VARCHAR2(256)
);

并加载一些数据。我在您提供的示例 Sherlock 字符串 #23#17 中插入了几个非打印字符。还包括仅由前 64 个 ASCII 字符组成的示例字符串(其中前 31 个字符不在 :print: 中),以及一些落入 PONUMSITEID 谓词的填充符。

INSERT INTO POLINE VALUES (1,'XXX','YYY','Sherlock'||CHR(23)||' 16 x 6.5” Wide Wheelbarrow wheel .M100P.10R –'||CHR(17)||' Effluent care bacteria and enzyme formulation');

DECLARE
  V_STRING VARCHAR2(64) := CHR(1);
BEGIN
  FOR POINTER IN 2..64 LOOP
    V_STRING := V_STRING||CHR(POINTER);
  END LOOP;
  INSERT INTO POLINE VALUES (2, 'XXX','YYY',V_STRING);
  INSERT INTO POLINE VALUES (3, 'AAA','BBB',V_STRING);
END;
/

INSERT INTO POLINE VALUES(4,'XXX','YYY','VOLTRON');

现在我们总共有 4 行。其中三个包含(多个)non-printable 字符,但其中只有两个应符合所有限制。

然后运行一个查询。下面有两个示例查询——第一个在您的初始示例查询中使用 REGEXP_INSTR (用 :cntrl: 代替 :print:)。但是作为替代方案,还包括第二个变体,它只检查每个字符是否在前 31 个 ascii 字符中。

两个示例查询,都会索引每个DESCRIPTION的每个字符,并检查它是否是printable,并收集每个非printable的ascii码和位置每个候选人中的字符 DESCRIPTION。这里的示例 table 有 DESCRIPTIONs,长度为 256 个字符,所以这被用作笛卡尔连接中的最大索引。

请注意,这些效率不高,旨在获得每个匹配.如果您最终只需要第一个匹配项,那么用 :print: 替换的原始查询会执行得更好。此外,这也可以通过放入 PL/SOL 或递归进行调整(如果在您的用例中允许 PL/SQL,或者您是 11gR2+,等等)。此外,此处的一些谓词如 REGEXP_LIKE 不会影响最终结果,仅用于允许初步过滤。根据您的数据集,这些对您来说可能是多余的(或更糟)。

第一个示例,使用正则表达式和 :print:

SELECT
  POLINE_ID,
  STRING_INDEX                                                                     AS NON_PRINTABLE_LOCATION,
  ASCII(REGEXP_SUBSTR(SUBSTR(DESCRIPTION, STRING_INDEX, 1), '[[:cntrl:]]', 1, 1)) AS NON_PRINTABLE_ASCII_NUMBER
FROM POLINE
  CROSS JOIN (SELECT LEVEL AS STRING_INDEX
              FROM DUAL
              CONNECT BY LEVEL < 257) CANDIDATE_LOCATION
WHERE PONUM = 'XXX'
      AND SITEID = 'YYY'
      AND REGEXP_LIKE(DESCRIPTION, '[[:cntrl:]]')
      AND REGEXP_INSTR(SUBSTR(DESCRIPTION, STRING_INDEX, 1), '[[:cntrl:]]', 1, 1, 0) > 0
      AND STRING_INDEX <= LENGTH(DESCRIPTION)
ORDER BY 1 ASC, 2 ASC;

第二个例子,使用 ASCII 数字:

SELECT
  POLINE_ID,
  STRING_INDEX                                AS NON_PRINTABLE_LOCATION,
  ASCII(SUBSTR(DESCRIPTION, STRING_INDEX, 1)) AS NON_PRINTABLE_ASCII_NUMBER
FROM POLINE
  CROSS JOIN (SELECT LEVEL AS STRING_INDEX
              FROM DUAL
              CONNECT BY LEVEL < 257) CANDIDATE_LOCATION
WHERE PONUM = 'XXX'
      AND SITEID = 'YYY'
      AND REGEXP_LIKE(DESCRIPTION, '[[:cntrl:]]')
      AND ASCII(SUBSTR(DESCRIPTION, STRING_INDEX, 1)) BETWEEN 1 AND 31
      AND STRING_INDEX <= LENGTH(DESCRIPTION)
ORDER BY 1 ASC, 2 ASC;

在我们的测试数据中,这些查询将产生等效的输出。我们应该期望它在 Sherlock DESCRIPTION 中有两次命中(对于字符 17 和 23),对于第一个 64-ascii DESCRIPTION.

有 31 次命中

结果:

POLINE_ID  NON_PRINTABLE_LOCATION  NON_PRINTABLE_ASCII_NUMBER  
1          9                       23                          
1          56                      17                          
2          1                       1                           
2          2                       2                           
2          3                       3                           
2          4                       4                           
2          5                       5                           
2          6                       6                           
2          7                       7                           
2          8                       8                           
2          9                       9                           
2          10                      10                          
2          11                      11                          
2          12                      12                          
2          13                      13                          
2          14                      14                          
2          15                      15                          
2          16                      16                          
2          17                      17                          
2          18                      18                          
2          19                      19                          
2          20                      20                          
2          21                      21                          
2          22                      22                          
2          23                      23                          
2          24                      24                          
2          25                      25                          
2          26                      26                          
2          27                      27                          
2          28                      28                          
2          29                      29                          
2          30                      30                          
2          31                      31                       

33 rows selected. 

编辑 作为对评论的回应,这里详细阐述了我们对 [[:cntrl:]][^[:cntrl:]] 以及 regexp_instr 的期望。

[[:cntrl:]] 将匹配前 31 个 ascii 字符中的任何一个,而 [^[:cntrl:]][[:cntrl:]] 的逻辑非,因此它将匹配除前 31 个 ascii 字符之外的任何字符。
为了比较这些,我们可以从只有一个字符的最简单的情况开始,ascii #31。由于只有一个字符,结果只能是匹配或未命中。人们会期望以下 return 1 匹配:

SELECT REGEXP_INSTR(CHR(31),'[[:cntrl:]]',1,1,0) AS MATCH_INDEX FROM DUAL;

MATCH_INDEX
1

但是 0 表示未命中 [^[:cntrl:]] :

SELECT REGEXP_INSTR(CHR(31),'[^[:cntrl:]]',1,1,0) AS MATCH_INDEX FROM DUAL;

MATCH_INDEX
0

现在,如果我们包含两个(或更多)字符,它们是 printable 和不可打印的混合字符,则有更多可能的结果。 [[:cntrl:]][^[:cntrl:]]都可以匹配,但只能匹配不同的东西。如果我们从仅 ascii #31 移动到 ascii #64#31,我们仍然期望 [[:cntrl:]] 匹配(因为在第二个位置有一个非 printable 字符)但是它现在应该 return 2,因为非原则table 处于第二个位置。

SELECT REGEXP_INSTR(CHR(64)||CHR(31),'[[:cntrl:]]',1,1,0) AS MATCH_INDEX FROM DUAL;

MATCH_INDEX
2

而现在[^[:cntrl:]]有机会匹配(在第一个位置):

SELECT REGEXP_INSTR(CHR(64)||CHR(31),'[^[:cntrl:]]',1,1,0) AS MATCH_INDEX FROM DUAL;

MATCH_INDEX
1

当混合使用printable和控制字符时,[[:cntrl:]][^[:cntrl:]]都可以匹配,但它们会匹配不同的索引。