Oracle 迁移到 OB 过程中的函数改造案例

作者:杨敬博,爱可生 DBA 团队成员,一位会摄影、会铲屎、会打球、会骑车、生活可以自理的 DBA。

审校及补充:胡呈清,官永强,程柳润。

爱可生开源社区出品,原创内容未经授权不得随意使用,转载请联系小编并注明来源。

本文约 1300 字,预计阅读需要 4 分钟。

问题描述

客户源数据库(Oracle)中有使用 XMLAGG 函数对列拼接的需求。通过查询官方文档发现 OceanBase 3.x 版本不支持 XMLAGG 相关函数,故使用 WM_CONCAT 函数进行适配改造。在初步改造后发现实际输出结果并没有排序,通过加 HINT 进行改造优化后,实现与预期一致的结果。

数据库版本

  • OceanBase 3.2.3
  • Oracle11g

分析过程

1. 获取原 SQL

SELECT xmlagg(xmlparse(content tr.inner_rule_file_name || ','
                       wellformed)
             order by tr.inner_rule_file_name)
       .getclobval()
FROM tol_report_user tr;

原输出类型

在 Oracle 中 XML 函数输出为 CLOB 类型 的结果。

适配改造

由于 OceanBase 3.x 不支持该函数,故使用 WM_CONCAT 函数进行适配改造。

SELECT WM_CONCAT(rt.inner_rule_file_name)
FROM (
    SELECT inner_rule_file_name
    FROM tol_report_user
    ORDER BY inner_rule_file_name
) AS rt;

SQL 改造后语意:先在子查询中对需要拼接的字段进行排序,再对排序后的结果进行拼接。可结果发现虽然子查询中加了排序,但是在拼接后却不是排序后的结果。

复现步骤

1. 测试环境复现

--测试表:
create table A 
(id number,
name varchar2(1000),
age number);

--测试数据:
insert into A values (1,'001.txt',29);
insert into A values (2,'002.pdf',19);
insert into A values (1,'001.txt',29);
insert into A values (2,'001.pdf',19);
insert into A values (3,'003.ppt',19);
insert into A values (3,'文件.ppt',19);
insert into A values (3,'文件.ppt',19);
insert into A (id,age)values (4,19);
insert into A (id,age)values (5,19);
commit;

--测试WM_CONCAT函数:
select  WM_CONCAT(a1.name) from (select name from A order by name) a1;

--测试结果
obclient [JINGBO]> select * from A;
+------+------------+------+
| ID   | NAME       | AGE  |
+------+------------+------+
|    1 | 001.txt    |   29 |
|    2 | 002.pdf    |   19 |
|    1 | 001.txt    |   29 |
|    2 | 001.pdf    |   19 |
|    3 | 003.ppt    |   19 |
|    3 | 文件.ppt   |   19 |
|    3 | 文件.ppt   |   19 |
|    4 | NULL       |   19 |
|    5 | NULL       |   19 |
+------+------------+------+
9 rows in set (0.009 sec)
obclient [JINGBO]> select  WM_CONCAT(a1.name) from (select name from A order by name) a1;
+---------------------------------------------------------------+
| WM_CONCAT(A1.NAME)                                            |
+---------------------------------------------------------------+
| 001.txt,002.pdf,001.txt,001.pdf,003.ppt,文件.ppt,文件.ppt     |
+---------------------------------------------------------------+
1 row in set (0.002 sec)
obclient [JINGBO]> select  WM_CONCAT(A.name) from A;
+---------------------------------------------------------------+
| WM_CONCAT(A.NAME)                                             |
+---------------------------------------------------------------+
| 001.txt,002.pdf,001.txt,001.pdf,003.ppt,文件.ppt,文件.ppt     |
+---------------------------------------------------------------+
1 row in set (0.008 sec)

测试后发现,实际输出结果没有排序,有子查询与没有子查询的输出结果是一致的。

2. 对比执行计划

没加子查询的。

obclient [JINGBO]> explain extended select  WM_CONCAT(name) as a from A\G
**** 1. row ****
Query Plan: ========================================
|ID|OPERATOR       |NAME|EST. ROWS|COST|
----------------------------------------
|0 |SCALAR GROUP BY|    |1        |47  |
|1 | TABLE SCAN    |A   |9        |46  |
========================================
Outputs & filters:
-------------------------------------
  0 - output([T_FUN_WM_CONCAT(A.NAME(0x7fbb3fad3b30))(0x7fbb3fad3420)]), filter(nil),
      group(nil), agg_func([T_FUN_WM_CONCAT(A.NAME(0x7fbb3fad3b30))(0x7fbb3fad3420)])
  1 - output([A.NAME(0x7fbb3fad3b30)]), filter(nil),
      access([A.NAME(0x7fbb3fad3b30)]), partitions(p0),
      is_index_back=false,
      range_key([A.__pk_increment(0x7fbb3fba2ac0)]), range(MIN ; MAX)always true
Used Hint:
-------------------------------------
  /*+
  */
Outline Data:
-------------------------------------
  /*+
      BEGIN_OUTLINE_DATA
      FULL(@"SEL$1" "JINGBO.A"@"SEL$1")
      END_OUTLINE_DATA
  */
Plan Type:
-------------------------------------
LOCAL
Optimization Info:
-------------------------------------
A:table_rows:9, physical_range_rows:9, logical_range_rows:9, index_back_rows:0, output_rows:9, est_method:local_storage, optimization_method=cost_based, avaiable_index_name[A], estimation info[table_id:1100611139454009, (table_type:1, version:0-1695319210321548-1695319210321548, logical_rc:0, physical_rc:0), (table_type:0, version:1695319210321548-1695319210321548-9223372036854775807, logical_rc:9, physical_rc:9)]
Parameters
-------------------------------------
1 row in set (0.005 sec)

加了子查询的。

obclient [JINGBO]> explain extended select  WM_CONCAT(a1.n) as a from  (select name as n from A order by name) a1\G
**** 1. row ****
Query Plan: ========================================
|ID|OPERATOR       |NAME|EST. ROWS|COST|
----------------------------------------
|0 |SCALAR GROUP BY|    |1        |47  |
|1 | TABLE SCAN    |A   |9        |46  |
========================================
Outputs & filters:
-------------------------------------
  0 - output([T_FUN_WM_CONCAT(A.NAME(0x7fbb572d4580))(0x7fbb572d6cb0)]), filter(nil),
      group(nil), agg_func([T_FUN_WM_CONCAT(A.NAME(0x7fbb572d4580))(0x7fbb572d6cb0)])
  1 - output([A.NAME(0x7fbb572d4580)]), filter(nil),
      access([A.NAME(0x7fbb572d4580)]), partitions(p0),
      is_index_back=false,
      range_key([A.__pk_increment(0x7fbb573e7bc0)]), range(MIN ; MAX)always true
Used Hint:
-------------------------------------
  /*+
  */
Outline Data:
-------------------------------------
  /*+
      BEGIN_OUTLINE_DATA
      FULL(@"SEL$1" "JINGBO.A"@"SEL$1")
      END_OUTLINE_DATA
  */
Plan Type:
-------------------------------------
LOCAL
Optimization Info:
-------------------------------------
A:table_rows:9, physical_range_rows:9, logical_range_rows:9, index_back_rows:0, output_rows:9, est_method:local_storage, optimization_method=cost_based, avaiable_index_name[A], estimation info[table_id:1100611139454009, (table_type:1, version:0-1695319210321548-1695319210321548, logical_rc:0, physical_rc:0), (table_type:0, version:1695319210321548-1695319210321548-9223372036854775807, logical_rc:9, physical_rc:9)]
Parameters
-------------------------------------
1 row in set (0.008 sec)

分别查看加了子查询与没加子查询的 SQL 执行计划,发现执行计划也是一致的:加了排序的子查询也没有出现排序的算子。

3. HINT 干预

no_rewrite 进行干预,结果正常:

SELECT /*+ NO_REWRITE */ WM_CONCAT(a1.n) as a
FROM (
    SELECT name as n
    FROM A
    ORDER BY name
) a1;

obclient [JINGBO]> select /*+ NO_REWRITE */ WM_CONCAT(a1.n) as a from  (select name as n from A order by name) a1;
+---------------------------------------------------------------+
| A                                                             |
+---------------------------------------------------------------+
| 001.pdf,001.txt,001.txt,002.pdf,003.ppt,文件.ppt,文件.ppt     |
+---------------------------------------------------------------+
1 row in set (0.001 sec)

执行计划中有排序操作(SORT 算子):

obclient [JINGBO]> explain extended select /*+ NO_REWRITE */ WM_CONCAT(a1.n) as a from  (select name as n from A order by name) a1\G
**** 1. row ****
Query Plan: ========================================
|ID|OPERATOR       |NAME|EST. ROWS|COST|
----------------------------------------
|0 |SCALAR GROUP BY|    |1        |52  |
|1 | SUBPLAN SCAN  |A1  |9        |52  |
|2 |  SORT         |    |9        |52  |
|3 |   TABLE SCAN  |A   |9        |46  |
========================================
Outputs & filters:
-------------------------------------
  0 - output([T_FUN_WM_CONCAT(A1.N(0x7fbaa10c0590))(0x7fbaa10bfe80)]), filter(nil),
      group(nil), agg_func([T_FUN_WM_CONCAT(A1.N(0x7fbaa10c0590))(0x7fbaa10bfe80)])
  1 - output([A1.N(0x7fbaa10c0590)]), filter(nil),
      access([A1.N(0x7fbaa10c0590)])
  2 - output([A.NAME(0x7fbaa11d0ae0)]), filter(nil), sort_keys([A.NAME(0x7fbaa11d0ae0), ASC])
  3 - output([A.NAME(0x7fbaa11d0ae0)]), filter(nil),
      access([A.NAME(0x7fbaa11d0ae0)]), partitions(p0),
      is_index_back=false,
      range_key([A.__pk_increment(0x7fbaa11d6590)]), range(MIN ; MAX)always true
Used Hint:
-------------------------------------
  /*+
      NO_REWRITE(@"SEL$1")
  */
Outline Data:
-------------------------------------
  /*+
      BEGIN_OUTLINE_DATA
      FULL(@"SEL$2" "JINGBO.A"@"SEL$2")
      NO_REWRITE(@"SEL$1")
      END_OUTLINE_DATA
  */
Plan Type:
-------------------------------------
LOCAL
Optimization Info:
-------------------------------------
A:table_rows:9, physical_range_rows:9, logical_range_rows:9, index_back_rows:0, output_rows:9, est_method:local_storage, optimization_method=cost_based, avaiable_index_name[A], estimation info[table_id:1100611139454009, (table_type:1, version:0-1695319210321548-1695319210321548, logical_rc:0, physical_rc:0), (table_type:0, version:1695319210321548-1695319210321548-9223372036854775807, logical_rc:9, physical_rc:9)]
Parameters
-------------------------------------
1 row in set (0.134 sec)

加了 /+ NO_REWRITE \/ 符合预期输出:先对子查询中的结果排序,再对排序后的结果拼接。

结论

在 OceanBase 3.x 中使用 WM_CONCAT 函数,会触发 remove order by 改写,导致结果顺序不一致,需要加 HINT 对 SQL 进行改造。

解决方案

SELECT /*+ NO_REWRITE */ WM_CONCAT(rt.inner_rule_file_name)
FROM (
    SELECT inner_rule_file_name
    FROM tol_report_user
    ORDER BY inner_rule_file_name
) AS rt;

OceanBase 4.x 呢?

  1. OceanBase 4.x 版本增加了 XMLAGG 函数。
  2. OceanBase 4.x 版本已经修复了 WM_CONCAT 函数触发 remove order by改写的问题。

更多技术文章,请访问:https://opensource.actionsky.com/

关于 SQLE

SQLE 是一款全方位的 SQL 质量管理平台,覆盖开发至生产环境的 SQL 审核和管理。支持主流的开源、商业、国产数据库,为开发和运维提供流程自动化能力,提升上线效率,提高数据质量。

✨ Github:https://github.com/actiontech/sqle

📚 文档:https://actiontech.github.io/sqle-docs/

💻 官网:https://opensource.actionsky.com/sqle/

👥 微信群:请添加小助手加入 ActionOpenSource

🔗 商业支持:https://www.actionsky.com/sqle

<think>好的,我现在要处理用户的问题,他想了解OceanBaseOracle数据库之间的语法差异。首先,我需要回忆一下之前用户提供的引用内容,特别是引用3中提到OceanBase不支持add constraint这种写法,需要写在括号内部。还有引用2提到OB-SQL语法差异与实践,以及LSM树结构对内存和磁盘的影响。引用1里提到OceanBaseMySQL兼容性对比,可能也有帮助,但用户现在问的是Oracle,所以重点应该在Oracle对比上。 用户可能是在迁移数据库,从Oracle转向OceanBase,遇到了语法上的问题。比如在创建表的时候,Oracle可以分开添加主键约束,而OceanBase需要在建表时内联定义。我需要详细说明这些具体的语法差异,比如数据类型、DDL语句、函数过程性语言的差异,以及事务处理的不同。 接下来,我需要检查是否还有其他差异,比如数据类型的不同,比如Oracle的NUMBER类型对应OceanBase的DECIMAL,NVARCHAR2可能需要换成VARCHAR或VARCHAR2?另外,存储过程和触发器的支持情况如何?OceanBase可能在这方面的功能还不完善,或者语法不同。 还有用户可能关心的分区支持,OceanBase作为分布式数据库,分区语法可能与Oracle不同,需要举例说明。比如Oracle的PARTITION BY RANGE在OceanBase中的实现是否有差异。 另外,引用2提到OceanBase使用LSM树结构,这可能影响存储和内存管理,但用户问的是语法差异,这部分可能不需要深入,除非直接影响语法,比如存储引擎相关的语句。 需要确保回答结构清晰,分点列出,每个部分都有例子,比如建表语句的区别,数据类型对比,约束定义的位置,分区的写法,函数的不同等。同时,生成的相关问题要引导用户进一步了解迁移中的具体问题,比如性能调优、存储过程迁移、数据迁移工具等。 最后,检查是否有遗漏的点,比如事务控制语句,比如Oracle的COMMIT和OceanBase是否有区别,或者序列的使用等。确保引用正确,引用3提到了约束定义的差异,引用2提到了语法差异,引用1可能涉及兼容性对比的结构,但主要参考引用3和2。</think>### OceanBaseOracle数据库语法差异对比 OceanBaseOracle在语法设计上存在显著差异,主要源于架构定位不同(OceanBase为分布式数据库Oracle为集中式数据库)。以下是核心语法差异的详细对比: --- #### 一、数据类型差异 | **Oracle** | **OceanBase** | 说明 | |----------------------|---------------------|----------------------------------------------------------------------| | `NUMBER(19)` | `DECIMAL(20,0)` | OceanBase的数值类型精度定义需显式指定小数位 | | `NVARCHAR2(64)` | `VARCHAR(256)` | OceanBase默认使用UTF8MB4字符集,无需专门定义NVARCHAR2[^1] | | `DATE` | `DATETIME(6)` | OceanBase的日期类型支持微秒级精度 | --- #### 二、DDL语句差异 **1. 主键约束定义** ```sql -- Oracle CREATE TABLE ACT_EVT_LOG (log_nr NUMBER(19)); ALTER TABLE ACT_EVT_LOG ADD CONSTRAINT PK_LOG PRIMARY KEY (log_nr); -- OceanBase CREATE TABLE ACT_EVT_LOG ( log_nr DECIMAL(20,0) PRIMARY KEY -- 必须内联定义主键 ); ``` *OceanBase不支持单独使用`ALTER TABLE`添加主键约束,需在建表时内联定义[^3]。* **2. 索引管理** ```sql -- Oracle CREATE INDEX idx_name ON table_name(column_name) TABLESPACE TS_INDEX; -- OceanBase CREATE INDEX idx_name ON table_name(column_name); -- 不支持TABLESPACE语法 ``` *OceanBase作为分布式数据库,索引存储由系统自动管理,无需指定表空间[^1]。* --- #### 三、过程性语言支持 | **功能** | **Oracle** | **OceanBase** | |-------------------|---------------------|---------------------| | 存储过程 | 完全支持PL/SQL | 部分兼容,语法简化 | | 触发器 | 支持DML/DDL触发器 | 仅支持DML触发器 | | 包(Package) | 支持 | 不支持 | --- #### 四、分区语法差异 **Oracle分区示例:** ```sql CREATE TABLE sales ( id NUMBER, sale_date DATE ) PARTITION BY RANGE (sale_date) ( PARTITION p1 VALUES LESS THAN (TO_DATE('2023-01-01','YYYY-MM-DD')) ); ``` **OceanBase分区示例:** ```sql CREATE TABLE sales ( id DECIMAL(20,0), sale_date DATETIME ) PARTITION BY RANGE COLUMNS(sale_date) ( PARTITION p1 VALUES LESS THAN ('2023-01-01') ); ``` *OceanBase要求分区键必须包含所有主键列,且分区表达式需明确指定`COLUMNS`关键字[^1]。* --- #### 五、函数与运算符差异 | **场景** | **Oracle** | **OceanBase** | |-------------------|-------------------------|-------------------------| | 字符串连接 | `CONCAT()`或`||` | 仅支持`CONCAT()` | | 空值判断 | `NVL()` | `IFNULL()` | | 分页查询 | `ROWNUM`伪列 | `LIMIT offset, count` | --- #### 六、事务控制差异 ```sql -- Oracle隐式提交 INSERT INTO t1 VALUES(1); COMMIT; -- 必须显式提交 -- OceanBase自动提交 SET autocommit=0; -- 需手动关闭自动提交 BEGIN; INSERT INTO t1 VALUES(1); COMMIT; ``` *OceanBase默认开启自动提交模式,分布式事务需通过`BEGIN`显式声明[^2]。* ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值