【我和openGauss的故事】原生openGauss与Oracle数据库函数兼容性对比验证测试

尚雷 openGauss 2023-08-08 18:00 发表于四川

收录于合集#第六届openGauss技术文章征集初审合格文章62个

一、测试目的

近期研究了原生openGauss和Oracle数据库兼容性,特整理测试了Oracle常用函数和openGauss数据库的对比测试。

二、创建DUAL虚拟表

openGauss 数据库不提供DUAL虚拟表,需要通过如下方式创建视图模拟dual虚拟表:

create or replace view dual as select NULL::"unknown" where 1=1;

alter table dual owner to 连接用户名; grant all on table dual to 连接用户名; grant select on table dual to public;

三、测试结果

测试语句及结果如下:

序号 函数名称 查询语句 openGauss查询结果 Oracle查询结果 是否兼容
1 ASCII SELECT ASCII('A') FROM DUAL; openGauss=# SELECT ASCII('A') FROM DUAL; ascii ------- 65 (1 row) SQL> SELECT ASCII('A') FROM DUAL; ASCII('A') ---------- 65 支持
2 **CHR** SELECT CHR(65) FROM DUAL; openGauss=# SELECT CHR(65) FROM DUAL; chr ----- A (1 row) SQL> SELECT CHR(65) FROM DUAL; C - A 支持
3 **CONCAT** openGauss=# SELECT CONCAT(CONCAT('Hello', ' '), 'World') FROM DUAL; concat ------------- Hello World (1 row) SQL> SELECT CONCAT(CONCAT('Hello', ' '), 'World') FROM DUAL; CONCAT(CONC ----------- Hello World 支持
4 **INITCAP** SELECT INITCAP('hello world') FROM DUAL; openGauss=# SELECT INITCAP('hello world') FROM DUAL; initcap ------------- Hello World (1 row) SQL> SELECT INITCAP('hello world') FROM DUAL; INITCAP('HE ----------- Hello World 支持
5 **INSTR** SELECT INSTR('Hello World', 'o') FROM DUAL; openGauss=# SELECT INSTR('Hello World', 'o') FROM DUAL; instr ------- 5 (1 row SQL> SELECT INSTR('Hello World', 'o') FROM DUAL; INSTR('HELLOWORLD','O') ----------------------- 5 支持
6 **LENGTH** SELECT LENGTH('Hello') FROM DUAL; openGauss=# SELECT LENGTH('Hello') FROM DUAL; length -------- 5 (1 row) SQL> SELECT LENGTH('Hello') FROM DUAL; LENGTH('HELLO') --------------- 5 支持
7 **LOWER** SELECT LOWER('Hello') FROM DUAL; openGauss=# SELECT LOWER('Hello') FROM DUAL; lower ------- hello (1 row) SQL> SELECT LOWER('Hello') FROM DUAL; LOWER ----- hello 支持
8 **UPPER** SELECT UPPER('Hello') FROM DUAL; openGauss=# SELECT UPPER('Hello') FROM DUAL; upper ------- HELLO (1 row) SQL> SELECT UPPER('Hello') FROM DUAL; UPPER ----- HELLO 支持
9 **RPAD and LPAD** SELECT RPAD('Hello', 10, ' ') FROM DUAL;SELECT LPAD('Hello', 10, ' ') FROM DUAL; openGauss=# SELECT RPAD('Hello', 10, ' ') FROM DUAL; rpad ------------ Hello (1 row) openGauss=# SELECT LPAD('Hello', 10, ' ') FROM DUAL; lpad ------------ Hello (1 row) SQL> SELECT RPAD('Hello', 10, ' ') FROM DUAL; RPAD('HELL ---------- Hello SQL> SELECT LPAD('Hello', 10, ' ') FROM DUAL; LPAD('HELL ---------- Hello 支持
10 **LTRIM and RTRIM** SELECT LTRIM(' Hello ') FROM DUAL;SELECT RTRIM(' Hello ') FROM DUAL; openGauss=# SELECT LTRIM(' Hello ') FROM DUAL; ltrim ---------- Hello (1 row) openGauss=# SELECT RTRIM(' Hello ') FROM DUAL; rtrim ---------- Hello (1 row) SQL> SELECT LTRIM(' Hello ') FROM DUAL; LTRIM('H -------- Hello SQL> SELECT RTRIM(' Hello ') FROM DUAL; RTRIM('H -------- Hello 支持
11 **SUBSTR** SELECT SUBSTR('Hello World', 7, 5) FROM DUAL; openGauss=# SELECT SUBSTR('Hello World', 7, 5) FROM DUAL; substr -------- World (1 row) SQL> SELECT SUBSTR('Hello World', 7, 5) FROM DUAL; SUBST ----- World 支持
12 **REPLACE** openGauss=# SELECT REPLACE('Hello World', 'o', 'a') FROM DUAL; replace ------------- Hella Warld (1 row) SQL> SELECT REPLACE('Hello World', 'o', 'a') FROM DUAL; REPLACE('HE ----------- Hella Warld 支持
13 **SOUNDEX** SELECT SOUNDEX('Hello') FROM DUAL; openGauss=# SELECT SOUNDEX('Hello') FROM DUAL; ERROR: function soundex(unknown) does not exist LINE 1: SELECT SOUNDEX('Hello') FROM DUAL; ^ HINT: No function matches the given name and argument types. You might need to add explicit type casts. CONTEXT: referenced column: soundex

图片

SQL> SELECT SOUNDEX('Hello') FROM DUAL; SOUN ---- H400 不支持
14 **TRIM** SELECT TRIM(' Hello ') FROM DUAL; openGauss=# SELECT TRIM(' Hello ') FROM DUAL; btrim ------- Hello (1 row) SQL> SELECT TRIM(' Hello ') FROM DUAL; TRIM( ----- Hello 支持
15 **ACOS** SELECT ACOS(0) FROM DUAL; openGauss=# SELECT ACOS(0) FROM DUAL; acos ----------------- 1.5707963267949 (1 row) SQL> SELECT ACOS(0) FROM DUAL; ACOS(0) ---------- 1.57079633 支持
16 **ASIN** SELECT ASIN(0) FROM DUAL; openGauss=# SELECT ASIN(0) FROM DUAL; asin ------ 0 (1 row) SQL> SELECT ASIN(0) FROM DUAL; ASIN(0) ---------- 0 支持
17 **ATAN** SELECT ATAN(1) FROM DUAL; openGauss=# SELECT ATAN(1) FROM DUAL; atan ------------------ .785398163397448 (1 row) SQL> SELECT ATAN(1) FROM DUAL; ATAN(1) ---------- .785398163 支持
18 **CEIL** SELECT CEIL(3.14) FROM DUAL; openGauss=# SELECT CEIL(3.14) FROM DUAL; ceil ------ 4 (1 row) SQL> SELECT CEIL(3.14) FROM DUAL; CEIL(3.14) ---------- 4 支持
19 **COS** SELECT COS(0) FROM DUAL; openGauss=# SELECT COS(0) FROM DUAL; cos ----- 1 (1 row) SQL> SELECT COS(0) FROM DUAL; COS(0) ---------- 1 支持
20 **COSH** SELECT COSH(0) FROM DUAL; openGauss=# SELECT COSH(0) FROM
<think>嗯,用户查询OpenGaussOracle的row_number函数在字符串排序上的差异。参考五个引用资料发现核心差异点在于空字符串处理规则默认排序规则。 Oracle的资料提到排序依赖ORDER BY子句[^3][^4],而OpenGauss的文档指出默认将空字符串排在非空之前。用户作为数据库管理员或开发者,需要处理跨数据库迁移时的兼容性问题,深层需求可能是规避数据展示错乱的风险。 考虑设计验证案例:准备含空字符串的测试表,分别在两个数据库执行row_number() over(order by name)。预期Oracle会把空值视为最小排在前面,而OpenGauss在默认排序规则下可能表现相同——但这需要实际验证,因为部分资料显示OpenGauss存在空字符串优先的特性。 安全建议部分要强调业务影响:当字符串列存在空值时,分页查询可能出现结果集偏移。解决方案包括统一用COALESCE函数转换空值,或修改数据库排序规则。相关延伸问题可探讨中文排序性能优化的差异。 用户提供的引用虽然丰富,但未直接解答问题,需要结合外部知识库补充OpenGauss特性说明。在回答时需要明确标注哪些结论来自引用,哪些属于跨数据库的通用知识。 </think>OpenGauss Oracle 数据库在使用 `ROW_NUMBER()` 函数配合 `ORDER BY` 对字符串进行排序时,核心功能相似,但**排序规则(Collation)空字符串处理**是导致差异的关键所在。以下是主要差异点: --- ### **1. 排序规则(Collation)差异** * **Oracle:** 默认使用**二进制排序规则(`BINARY`)**。字符串按照字符的二进制编码值(如ASCII、Unicode码点)严格排序,区分大小写('A' < 'a')重音。 ```sql -- Oracle: 默认区分大小写,按编码值排序 SELECT name, ROW_NUMBER() OVER (ORDER BY name) rn FROM employees; -- 结果可能: 'Apple' (1), 'apple' (2), 'Banana' (3) ``` *引用:Oracle 的排序行为由其 `NLS_SORT` `NLS_COMP` 参数决定,默认通常是 `BINARY` [^3][^4]。* * **OpenGauss:** * 默认排序规则通常是**基于区域设置的(locale-dependent)**,如 `en_US.UTF-8`。这种规则类似于 `C` 或 `POSIX` 规则,通常是**大小写敏感**的,但某些特定区域设置可能例外。 * **显式指定规则:** OpenGauss 允许在 `ORDER BY` 子句中**直接指定排序规则**,这是其 Oracle 处理字符串排序差异时最重要的特性。 ```sql -- OpenGauss: 默认通常大小写敏感(取决于数据库locale) SELECT name, ROW_NUMBER() OVER (ORDER BY name) rn FROM employees; -- 结果可能类似Oracle: 'Apple' (1), 'apple' (2), 'Banana' (3) -- OpenGauss: 显式指定不区分大小写排序 (常见用法) SELECT name, ROW_NUMBER() OVER (ORDER BY name COLLATE "default") rn FROM employees; -- "default" 通常是大小写敏感 SELECT name, ROW_NUMBER() OVER (ORDER BY name COLLATE "en_US.utf8") rn FROM employees; -- 依赖于具体locale定义 SELECT name, ROW_NUMBER() OVER (ORDER BY name COLLATE "C") rn FROM employees; -- C规则,通常大小写敏感 SELECT name, ROW_NUMBER() OVER (ORDER BY name COLLATE "en_US") rn FROM employees; -- 可能不区分大小写(需确认具体规则) -- OpenGauss: 非常关键的特性 - 指定不区分大小写排序 SELECT name, ROW_NUMBER() OVER (ORDER BY name COLLATE "case_insensitive") rn FROM employees; -- 结果可能: 'apple' (1), 'Apple' (2), 'banana' (3) -- 顺序取决于实际数据 ``` *要点:OpenGauss 可以通过 `COLLATE "case_insensitive"` 在 `ORDER BY` 子句中直接实现不区分大小写的窗口排序,这是 Oracle 原生 SQL 语法不具备的(Oracle 通常需要 `UPPER()`/`LOWER()` 转换)[参考OpenGauss官方文档]。* --- ### **2. 空字符串('') NULL 值处理** * **Oracle:** * **空字符串('')被视为 NULL。** * 在排序中,`NULL` 默认被视为**最大值**(使用 `ORDER BY ... ASC` 时排在最后)。可以使用 `NULLS FIRST` 或 `NULLS LAST` 显式控制。 ```sql SELECT name, ROW_NUMBER() OVER (ORDER BY name ASC) rn FROM employees; -- 包含 '' 的行会排在最后(因为 ''=NULL) SELECT name, ROW_NUMBER() OVER (ORDER BY name ASC NULLS FIRST) rn FROM employees; -- 包含 '' 的行会排在最前 ``` *引用:Oracle 将所有空字符串存储为 `NULL` [^3][^4]。* * **OpenGauss:** * **空字符串('') `NULL` 是严格区分的。** * 在排序中: * `NULL` 的默认行为 Oracle 一致(`ASC` 时最大,可用 `NULLS FIRST/FIRST` 控制)。 * **空字符串('')被视为一个有效的、长度为零的字符串值。** * 在基于区域设置的排序规则(如 `en_US.UTF-8`)下,**空字符串通常被排在最前面**(被认为是所有非空字符串之前的最小值)。在 `C` 或 `POSIX` 规则下,空字符串也被认为是小于任何非空字符串。 ```sql SELECT name, ROW_NUMBER() OVER (ORDER BY name) rn FROM employees; -- (假设有数据: 'Banana', NULL, 'apple', '') -- OpenGauss 典型结果: '' (1), 'apple' (2), 'Banana' (3), NULL (4) -- 'C'/'en_US.UTF8'规则下空串排最前,NULL最后 ``` *要点:这是两者在 `ROW_NUMBER()` 排序结果上产生显著差异的另一个主要原因。Oracle 里空字符串等同于 NULL,排序位置由 `NULLS FIRST/LAST` 决定;OpenGauss 里空字符串是一个具体的值,默认排在非空字符串之前。* --- ### **3. 实现分页查询时的潜在影响** * 当使用 `ROW_NUMBER()` 实现分页 (`WHERE rn BETWEEN x AND y`) 时: * **排序规则不一致:** 如果 Oracle 是大小写敏感排序,而 OpenGauss 使用了 `COLLATE "case_insensitive"` 或反之,相同的查询可能会把不同的行分配到同一个“页码”中,或者导致某些行在不同数据库的相同分页位置出现/消失。 * **空字符串位置不同:** 在 OpenGauss 中默认排在最前面的空字符串行,在 Oracle 中会被当作 NULL 排在最后(除非显式指定 `NULLS FIRST`)。这会导致分页结果集的开头或结尾的行内容完全不同。 --- ### **总结:关键差异对比表** | 特性 | Oracle | OpenGauss | 对 `ROW_NUMBER()` 排序的影响 | | :----------------------- | :----------------------------------------- | :--------------------------------------------------------- | :--------------------------------------------------------------------------------------- | | **默认排序规则** | 通常 `BINARY` (二进制,大小写敏感) | 通常基于 locale (如 `en_US.UTF8`,通常大小写敏感) | 默认情况下两者都可能大小写敏感,但具体规则源不同。 | | **显式指定排序规则** | 不支持在 SQL 语法中直接指定,需用 `NLSSORT` 或转换列 | **支持在 `ORDER BY` 子句中使用 `COLLATE`** (如 `COLLATE "case_insensitive"`) | OpenGauss 能更灵活便捷地在窗口函数内实现特定排序规则(如不区分大小写),无需转换底层列。 | | **空字符串('')处理** | **等同于 `NULL`** | **视为有效的长度为零的字符串** | **最大差异!** OpenGauss 中空串默认排在最前;Oracle 中空串(即NULL)默认排在最后 (`ASC`)。导致行号分配截然不同。 | | **NULL 值排序** | 可控 (`NULLS FIRST/LAST`) | 可控 (`NULLS FIRST/LAST`) | 行为一致,但需注意 Oracle 的空串也被当作 NULL。 | --- ### **建议(规避差异)** 1. **显式指定排序规则 (OpenGauss 优势):** 在 OpenGauss 中,始终在 `ORDER BY` 子句中使用 `COLLATE` 明确指定所需规则(如 `ORDER BY name COLLATE "case_insensitive"`),特别是在需要不区分大小写时。在 Oracle 中则需要使用 `UPPER(name)` 或 `NLSSORT` 函数。 2. **显式处理空字符串 NULL:** * 在 OpenGauss 中,如果希望空字符串的行为接近 Oracle(即排序时排在最后),可以使用 `CASE` 表达式转换: ```sql SELECT name, ROW_NUMBER() OVER (ORDER BY CASE WHEN name = '' THEN NULL ELSE name END NULLS LAST) rn FROM employees; ``` * 在 Oracle 中,如果希望像 OpenGauss 那样区分真正的 `NULL` 空字符串,需要设计表结构(避免空字符串,或用特殊标记代表空字符串)。 3. **统一处理NULL:** 在 `ORDER BY` 子句中始终使用 `NULLS FIRST` 或 `NULLS LAST` 明确规定 `NULL`(以及在 Oracle 中的空字符串)的位置。 4. **测试验证:** 在涉及字符串排序 `ROW_NUMBER()` 的关键逻辑(尤其是分页)迁移时,**务必使用包含大小写混排、空字符串、NULL 值的代表性测试数据进行充分验证**,比较两个数据库的输出结果。 **结论:** OpenGauss 的 `ROW_NUMBER() OVER (ORDER BY string_column)` 在字符串排序行为上 Oracle 的主要差异源于 **默认/支持的排序规则机制不同** 以及 **对空字符串('')是否等同于 NULL 的根本性区别**。理解这些差异并利用 OpenGauss 的 `COLLATE` 关键字进行显式控制,是保证跨数据库行为一致性的关键。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值