在 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 字符 DESCRIPTION
在 POLINE
中,需要进行一些更改。我将包括一个将每场比赛作为起点的例子。
在此示例中,每个 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:
中),以及一些落入 PONUM
和 SITEID
谓词的填充符。
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 有 DESCRIPTION
s,长度为 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:]]
都可以匹配,但它们会匹配不同的索引。
我正在尝试使用 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 字符 DESCRIPTION
在 POLINE
中,需要进行一些更改。我将包括一个将每场比赛作为起点的例子。
在此示例中,每个 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:
中),以及一些落入 PONUM
和 SITEID
谓词的填充符。
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 有 DESCRIPTION
s,长度为 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
.
结果:
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:]]
都可以匹配,但它们会匹配不同的索引。