1.3.2.2.13. 转换为 MySQL

1.3.2.2.13.1. 字符串函数

1.3.2.2.13.1.1. EMPTY_CLOB

语法
EMPTY_CLOB()
描述
EMPTY_CLOB() 是 Oracle 中用于初始化空 CLOB 定位器的函数。Oracle 转 MySQL 8 场景下,统一 SQL 当前并不是把 EMPTY_CLOB() 作为普通函数直接改写为 MySQL 侧函数或字符串表达式,而是仅对 CREATE TABLE 语句中的 CLOB 默认值做兼容处理。
当列定义命中 CLOB DEFAULT empty_clob() 时,转换到 MySQL 8 后会去掉 DEFAULT empty_clob() 默认值,保留既有的 CLOBLONGTEXT 类型映射;如果同时存在 NOT NULL 约束,则保留 NOT NULL
参数解释

警告

  1. 当前支持范围限定在 CREATE TABLE 列定义中的 CLOB DEFAULT empty_clob() 默认值处理,不表示普通表达式、DML 或 DQL 中的 EMPTY_CLOB() 函数已直接支持转换。

  2. DDL 侧不会输出 DEFAULT ''CAST('' AS text)。删除 DEFAULT empty_clob() 是设计行为,用于避免把 Oracle 特有的 LOB 定位器语义扩散到 MySQL 目标端。

  3. 对于 CLOB DEFAULT empty_clob() NOT NULL 场景,DDL 转换后会保留 NOT NULL。若后续 insert 语句省略该列,并且转换链路可通过元数据识别该列默认值来自 empty_clob(),则 insert 补列路径会补写空串 ''

  4. BLOB / empty_blob() 以及非 CLOB DEFAULT empty_clob() 的默认值场景不属于本规则范围。

示例

-- 转换前Oracle SQL:
CREATE TABLE t_clob (
    id NUMBER,
    content CLOB DEFAULT empty_clob()
);

CREATE TABLE t_clob_not_null (
    id NUMBER,
    content CLOB DEFAULT empty_clob() NOT NULL
);

-- 转换后MySQL SQL:
CREATE TABLE t_clob (
    id DECIMAL,
    content LONGTEXT
);

CREATE TABLE t_clob_not_null (
    id DECIMAL,
    content LONGTEXT NOT NULL
);

1.3.2.2.13.1.2. REGEXP_COUNT

语法
REGEXP_COUNT(source_char, pattern [, position [, match_param]])
描述
该函数用于返回 patternsource_char 字符串中出现的次数。Oracle 转 MySQL 8 场景下,统一 SQL 不透传 Oracle 原函数名,而是改写为补丁脚本中的兼容函数 unisql.regexp_count(input_string, pattern, position, match_param)
转换器会把 Oracle 的 2 到 4 个参数统一补齐为 MySQL 侧 4 个参数:未指定 position 时补 1,未指定 match_param 时补空串 ''

参数解释

参数

说明

source_char

用作搜索值的字符表达式,转换后作为 input_string 传入兼容函数。兼容类型为 CHARVARCHAR2NCHARNVARCHAR2CLOB

pattern

正则表达式。兼容类型为 CHARVARCHAR2NCHARNVARCHAR2。字符串字面量会按目标 MySQL 字符串模式做反斜杠保护。

position

可选,正整数,指定从 source_char 的第几个字符开始搜索;缺省补 1。兼容类型为 NUMBER

match_param

可选,匹配参数;缺省补 ''。兼容类型为 CHARVARCHAR2NCHARNVARCHAR2。当前兼容函数只显式处理 icmn

转换规则

  1. 仅在 Oracle / Oracle 19c -> MySQL 8 路径启用该专用转换;其他 MySQL 目标版本走通用函数转换逻辑。

  2. 参数数量必须为 2 到 4 个,否则转换阶段报参数数量错误。

  3. 转换输出固定为 4 参兼容函数调用,例如:

    -- 转换前Oracle SQL:
    SELECT REGEXP_COUNT('123123123', '123') FROM dual;
    SELECT REGEXP_COUNT('123123123', '123', 2) FROM dual;
    SELECT REGEXP_COUNT('Oracle ORACLE oracle', 'oracle', 1, 'i') FROM dual;
    
    -- 转换后MySQL SQL:
    SELECT unisql.regexp_count('123123123', '123', 1, '') FROM dual;
    SELECT unisql.regexp_count('123123123', '123', 2, '') FROM dual;
    SELECT unisql.regexp_count('Oracle ORACLE oracle', 'oracle', 1, 'i') FROM dual;
    
  4. pattern 为字符串字面量时,转换器会按目标 MySQL 字符串模式处理反斜杠转义,避免 MySQL 默认字符串解析阶段先消费 \d\n 等反斜杠序列。当前转换重点是保证 SQL 字符串字面量传入 MySQL 正则引擎后仍保留 Oracle 源 SQL 中的反斜杠字符;Oracle 正则语义与 MySQL ICU 正则语义不属于完全等价关系。

  5. source_char 为字符串字面量时也会做目标 MySQL 字符串模式感知的反斜杠保护。例如 Oracle 字面量 '测试123\n测试456' 表示普通反斜杠和字母 n,转换到 MySQL 默认字符串模式时会额外转义,避免变成真实换行。

  6. source_charpatternNULL 字面量时会保留 NULL 语义,不会改写为空串 ''

  7. source_charpatternCAST('...' AS ...) / CAST(NULL AS ...) 形式,且 CAST 内部仍是字面量,也会处理内部字面量,再保留 CAST 结构。

  8. pattern 是列、变量、函数调用或其他动态表达式,则只递归转换表达式本身,不做上述正则字面量改写。

目标侧兼容函数行为

目标侧补丁函数 unisql.regexp_count 的核心实现如下:

  1. input_stringpatternposition 任一为 NULL 时返回 NULL

  2. input_string = ''pattern = '' 时返回 NULL,用于贴近 Oracle 空串按 NULL 处理的语义。

  3. position < 1 时抛出 ORA-01428: argument '...' is out of range 风格错误。

  4. 内部使用 SUBSTRING(input_string, position) 截取待匹配字符串,再通过 MySQL REGEXP_INSTR 循环统计非重叠匹配次数。

  5. match_parami / c 按从左到右顺序处理,后出现的大小写选项覆盖前面的选项;mn 会传递给 MySQL 正则匹配;其他字符当前忽略。

match_param 选项说明

选项

说明

i

指定大小写不敏感匹配。兼容函数会将 i 传递给 MySQL REGEXP_INSTRmatch_type 参数;如果同一个 match_param 中后续又出现 c,则以 c 为准。

c

指定大小写敏感匹配。兼容函数会将 c 传递给 MySQL REGEXP_INSTRmatch_type 参数;如果同一个 match_param 中后续又出现 i,则以 i 为准。

m

指定多行匹配模式。兼容函数会将 m 传递给 MySQL REGEXP_INSTRmatch_type 参数,^$ 按目标 MySQL ICU 正则的多行规则匹配行边界。

n

指定点号匹配换行模式。兼容函数会将 n 传递给 MySQL REGEXP_INSTRmatch_type 参数,. 按目标 MySQL ICU 正则规则匹配换行符。

match_param 会从左到右归一化:重复的 icmn 只保留一个;ic 互斥,后出现者覆盖先出现者;未识别字符当前忽略,不按 Oracle 的 x 扩展空白模式处理。

兼容性说明

警告

  1. 该实现依赖目标库已安装 unisql.regexp_count 补丁函数;如果补丁函数不存在,转换后的 SQL 会在目标库执行时报函数不存在。

  2. 当前已验证的兼容类型为:source_char 支持 CHARVARCHAR2NCHARNVARCHAR2CLOBpattern 支持 CHARVARCHAR2NCHARNVARCHAR2position 支持 NUMBERmatch_param 支持 CHARVARCHAR2NCHARNVARCHAR2

  3. 实现中先执行 SUBSTRING(input_string, position),再在截取后的字符串上匹配。已验证 ^position 组合存在差异:REGEXP_COUNT('abcabc', '^abc', 4) 在 Oracle 返回 0,转换后在 MySQL 返回 1

  4. match_param 当前只显式处理 icmn。Oracle 的 x 扩展匹配参数不会按 Oracle 语义完整实现。

  5. MySQL ICU 正则引擎与 Oracle 正则引擎不等价。已验证差异包括连续量词报错、多字节字符计数、零长度匹配推进、Unicode escape 范围、单词边界,以及目标库 collation 参与大小写匹配。

  6. pattern 是动态表达式时不会做正则字面量反斜杠保护;动态模式本身需要符合目标库正则语法。

  7. 目标 MySQL 的 collation 会影响正则大小写敏感性;未显式指定 match_paramc 时,大小写匹配结果与 Oracle 默认行为不同。

已知差异示例

以下示例基于 Oracle 转 MySQL 8 的 plain SQL 验证结果整理,用于说明已验证的不一致原因。迁移时以业务 SQL、目标库字符集和 collation 为准复核。

场景

示例

差异说明

多字节字符与 . / \D / \S

REGEXP_COUNT('测试123!', '.')REGEXP_COUNT('订单金额:9999.00元', '\D')

已验证案例中,Oracle 对多字节中文字符按字节或编码单元计数;MySQL 8 ICU 正则按 Unicode 字符匹配,因此 MySQL 计数小于 Oracle。

可匹配空串的量词

REGEXP_COUNT('测试123', '\d*')

* 允许零长度匹配。已验证案例中,Oracle 与 MySQL ICU 对零长度匹配后的推进单位不同,匹配次数不一致。

Unicode escape 字符范围

REGEXP_COUNT('北京市朝阳区建国路88号', '[\u4e00-\u9fa5]+')

MySQL ICU 按 Unicode escape 范围识别中文连续片段;Oracle 对 \uXXXX 写法的解释不等同于 MySQL ICU,匹配段数不同。

默认大小写敏感性

REGEXP_COUNT('OrderService', 'orderservice')

Oracle 默认大小写敏感;MySQL 受目标列或字符串 collation 影响按大小写不敏感规则匹配。需要大小写敏感时显式使用 match_param => 'c' 并复核目标库 collation。

单词边界

REGEXP_COUNT('ORDER-20260119-001 ORDER-20260119-002', '\bORDER-\d+\b')REGEXP_COUNT('ORDER-20260119-001', '\B\d+\B')

MySQL ICU 支持 \b / \B 作为单词边界/非单词边界;Oracle 对这些转义的解释不完全相同,已验证案例中 Oracle 返回 0 而 MySQL 返回非 0。

CHAR / NCHAR / NVARCHAR2 作为 pattern

REGEXP_COUNT('192.168.1.100', CAST('\d+\.\d+' AS CHAR(20)))REGEXP_COUNT(N'张三', CAST(N'张三' AS NCHAR(2)))

Oracle 固定长度字符类型带入补空格、national character set 或长度语义;MySQL 转换后成为 CHAR 字符串参与 ICU 正则,pattern 实际含义不同。

复杂重复量词

(\d+|\D+)\s*(\w+|\W+)\.[^0-9]*?+*

MySQL ICU 对连续量词执行严格语法校验,目标库执行时报 Syntax error in regular expression

示例

-- 转换前Oracle SQL:
SELECT REGEXP_COUNT('abc123def456', '\d+') AS count_digits FROM dual;
SELECT REGEXP_COUNT('Oracle ORACLE oracle', 'oracle', 1, 'i') AS param_i FROM dual;
SELECT REGEXP_COUNT('123123123', '123', 2) AS pos_mid FROM dual;

-- 转换后MySQL SQL:
SELECT unisql.regexp_count('abc123def456', '\\d+', 1, '') AS count_digits FROM dual;
SELECT unisql.regexp_count('Oracle ORACLE oracle', 'oracle', 1, 'i') AS param_i FROM dual;
SELECT unisql.regexp_count('123123123', '123', 2, '') AS pos_mid FROM dual;