本文档是系列文章中的一篇,该系列提供了关键的信息和指导来帮助计划和执行 Oracle® 11g/12c 数据库到 Cloud SQL for MySQL 5.7 版第二代实例的迁移。本系列文章包含以下部分:
- 将 Oracle 用户迁移到 Cloud SQL for MySQL:术语和功能
- 将 Oracle 用户迁移到 Cloud SQL for MySQL:数据类型、用户和表
- 将 Oracle 用户迁移到 Cloud SQL for MySQL:查询、存储过程、函数和触发器(本文档)
- 将 Oracle 用户迁移到 Cloud SQL for MySQL:安全、操作、监控和日志记录
查询
Oracle 和 Cloud SQL for MySQL 支持 ANSI SQL 标准。通常,仅使用基本语法元素(例如,不指定任何标量函数或任何其他 Oracle 扩展功能)即可迁移 SQL 语句。以下部分讨论了常见的 Oracle 查询元素及其对应的 Cloud SQL for MySQL 等效元素。
基本的 SELECT 和 FROM 语法
Oracle 功能名称或语法名称 | Oracle 概览或实现 | MySQL 支持 | MySQL 对应或替代解决方案 |
---|---|---|---|
用于数据检索的 SQL 基本语法 | SELECT FROM WHERE GROUP BY HAVING ORDER BY |
是 | SELECT FROM WHERE GROUP BY HAVING ORDER BY |
SELECT (用于显示输出内容) |
SELECT 1 FROM DUAL |
是 | SELECT 1 OR SELECT 1 FROM DUAL |
列别名 | SELECT COL1 AS C1 |
是 | SELECT COL1 AS C1 OR SELECT COL1 C1 |
表名称是否 区分大小写 |
不区分大小写 (例如,表名称可以是 orders 和/或 ORDERS )。 |
否 | 根据定义的表名称,区分大小写(例如,表名称只能是 orders 或 ORDERS )。 |
您可以详细了解 MySQL 的 SELECT
语法。
- 内嵌视图
- 内嵌视图(也称为“派生表”)是
SELECT
语句,位于FROM
子句中且用作子查询。 - 内嵌视图可以通过移除复合计算或消除联接运算来帮助简化复杂查询,同时将多个单独的查询压缩为单个简化的查询。
- 转换说明:Oracle 内嵌视图不要求使用别名,而 MySQL 要求为每个内嵌视图提供特定的别名。
- 内嵌视图(也称为“派生表”)是
下表显示了从 Oracle 到 MySQL 的内嵌视图转换示例。
Oracle 11g/12c |
---|
SQL> SELECT FIRST_NAME, DEPARTMENT_ID, SALARY, DATE_COL FROM EMPLOYEES, (SELECT SYSDATE AS DATE_COL FROM DUAL); 输出类似于以下内容: FIRST_NAME DEPARTMENT_ID SALARY DATE_COL -------------------- ------------- ---------- --------- Steven 90 24000 30-JUL-19 Neena 90 17000 30-JUL-19 Lex 90 17000 30-JUL-19 |
Cloud SQL for MySQL 5.7 |
没有内嵌视图别名:mysql> SELECT FIRST_NAME, DEPARTMENT_ID, SALARY, DATE_COL FROM EMPLOYEES, (SELECT SYSDATE() AS DATE_COL FROM DUAL); 向内嵌视图添加别名: mysql> SELECT FIRST_NAME, DEPARTMENT_ID, SALARY, DATE_COL FROM EMPLOYEES, (SELECT SYSDATE() AS DATE_COL FROM DUAL) AS A1; 输出类似于以下内容: +-------------+---------------+----------+---------------------+ | FIRST_NAME | DEPARTMENT_ID | SALARY | DATE_COL | +-------------+---------------+----------+---------------------+ | Steven | 90 | 23996.00 | 2019-07-30 09:28:00 | | Neena | 90 | 22627.00 | 2019-07-30 09:28:00 | | Lex | 90 | 22627.00 | 2019-07-30 09:28:00 | |
JOIN 语句
MySQL JOIN
语句支持 Oracle JOIN
语句,但 FULL JOIN
子句除外。此外,MySQL JOIN
语句支持使用备用语法,例如 USING
子句、WHERE
子句(而不是 ON
子句)以及在 JOIN
语句中使用 SUBQUERY
。
下表显示了 JOIN 转换示例。
Oracle JOIN 类型 | 受 MySQL 支持 | MySQL JOIN 语法 |
---|---|---|
INNER JOIN |
是 | SELECT E.FIRST_NAME, D.DEPARTMENT_NAME FROM EMPLOYEES E JOIN DEPARTMENTS D ON E.DEPARTMENT_ID = D.DEPARTMENT_ID; |
CROSS JOIN |
是 | SELECT E.FIRST_NAME, D.DEPARTMENT_NAME FROM EMPLOYEES E CROSS JOIN DEPARTMENTS D |
FULL JOIN |
否 | 如需解决此问题,请考虑将 UNION 与 LEFT 和 RIGHT JOIN 语句搭配使用。 |
LEFT JOIN [ OUTER ] |
是 | SELECT E.FIRST_NAME, D.DEPARTMENT_NAME FROM EMPLOYEES E LEFT JOIN DEPARTMENTS D ON E.DEPARTMENT_ID = D.DEPARTMENT_ID; |
RIGHT JOIN [ OUTER ] |
是 | SELECT E.FIRST_NAME, D.DEPARTMENT_NAME FROM EMPLOYEES E RIGHT JOIN DEPARTMENTS D ON E.DEPARTMENT_ID = D.DEPARTMENT_ID; |
SUBQUERY |
是 | SELECT E.FIRST_NAME, D.DEPARTMENT_NAME FROM EMPLOYEES E JOIN (SELECT * FROM DEPARTMENTS)D ON E.DEPARTMENT_ID = D.DEPARTMENT_ID; |
UNION、UNION ALL、INTERSECT、MINUS
MySQL 不支持 Oracle INTERSECT
和 MINUS
函数,但 UNION
和 UNION ALL
函数除外:
UNION
:附加两个或更多SELECT
语句的结果集并消除重复记录。UNION ALL
:附加两个或更多SELECT
语句的结果集,不消除重复记录。INTERSECT
:仅当两个数据集中都存在某条记录时,才返回两个或更多SELECT
语句的交集。MINUS
:比较两个或更多SELECT
语句,仅返回第一个查询中未由其他语句返回的不同行。
转换说明
从 Oracle INTERSECT
和 MINUS
函数转换为 MySQL 时,请使用 JOIN
语句以及 IN
和 EXISTS
作为替代解决方案。
示例
Oracle 函数 | Oracle 实现 | MySQL 支持 | MySQL 对应或替代解决方案 |
---|---|---|---|
UNION |
SELECT COL1 FROM TBL1 UNION SELECT COL1 FROM TBL2 |
是 | SELECT COL1 FROM TBL1 UNION SELECT COL1 FROM TBL2 |
UNION ALL |
SELECT COL1 FROM TBL1 UNION ALL SELECT COL1 FROM TBL2 |
是 | SELECT COL1 FROM TBL1 UNION ALL SELECT COL1 FROM TBL2 |
INTERSECT |
SELECT COL1 FROM TBL1 INTERSECT SELECT COL1 FROM TBL2 |
否 | SELECT COL1 FROM TBL1 WHERE COL1 IN (SELECT COL1 FROM TBL2) |
MINUS |
SELECT COL1 FROM TBL1 MINUS SELECT COL1 FROM TBL2 |
否 | SELECT A.COL1 FROM TBL1 A LEFT JOIN TBL2 B ON USING(COL1) WHERE B.COL1 IS NULL |
标量(单行)和分组函数
MySQL 提供了一个详尽的标量(单行)和聚合函数列表。一些 MySQL 函数与其对应的 Oracle 函数类似(在名称和功能方面类似,或使用不同的名称但功能类似)。虽然 MySQL 函数在名称上可能与其对应的 Oracle 函数相同,但它们可能展示不同的功能。
下表按名称和功能描述了 Oracle 和 MySQL 等效的情况(由“是”指明),以及建议转换的情况(除了“是”以外的所有情况)。
字符函数
Oracle 函数 | Oracle 函数规范或实现 | MySQL 等效情况 | MySQL 对应函数 | MySQL 函数规范或实现 |
---|---|---|---|---|
CONCAT(str1,str2) |
返回 str1 与 str2 的串联结果:CONCAT('A', 1) = A1 |
是 | CONCAT |
等效于 Oracle:CONCAT('A', 1) = A1 |
LOWER/UPPER |
返回字符,所有字母一律小写或一律大写:LOWER('SQL') = sql |
是 | LOWER/UPPER |
等效于 Oracle:LOWER('SQL') = sql |
LPAD/RPAD(expr1,n,expr2) |
返回 expr1,它是使用 expr2 中的字符序列向左或向右填充 n 个字符后得到的结果:LPAD('A',3,'*') = **A |
是 | LPAD/RPAD |
等效于 Oracle:LPAD('A',3,'*') = **A |
SUBSTR(char,p,n) |
返回 char 的一部分,从字符位置 p 开始,子字符串长度为 n 个字符:SUBSTR('MySQL', 3, 3) = SQL |
是 | SUBSTR(char,p,n) |
等效于 Oracle:SUBSTR('MySQL', 3, 3) = SQL |
INSTR(index,str) |
返回字符串 str 的位置 (index):INSTR('MySQL', 'y') = 2 |
是 | INSTR |
等效于 Oracle:INSTR('MySQL', 'y') = 2 |
REPLACE(char,str1,str2) |
返回给定字符中出现的每一个搜索字符串均替换为替换字符串后的字符:REPLACE('ORADB', 'ORA', 'MySQL') |
是 | REPLACE(char,str1,str2) |
等效于 Oracle:REPLACE('ORADB', 'ORA', 'MySQL') |
TRIM(str) |
去除字符串的开头和/或结尾字符:TRIM(both '-' FROM '-MySQL-') = MySQL |
是 | TRIM(str) |
等效于 Oracle:TRIM(both '-' FROM '-MySQL-') = MySQL |
LTRIM/RTRIM(str) |
从字符串的左侧或右侧移除搜索中出现的所有字符:LTRIM(' MySQL', ' ') = MySQL |
部分 | LTRIM/RTRIM(str) |
Oracle R/LTRIM 函数,参数(空白或字符串)替换除外。MySQL R/LTRIM 仅消除空白,并且仅接受输入字符串:LTRIM(' MySQL') = MySQL |
ASCII(char) |
返回 char 的第一个字符的数据库字符集中的十进制表示法:ASCII('A') = 65 |
是 | ASCII(char) |
等效于 Oracle:ASCII('A') = 65 |
CHR(char) |
将 ASCII 码值(介于 0-225 之间的数字值)转换为字符:CHR(65) = A |
部分等效;函数名称不同 | CHAR(char) |
MySQL 使用 CHAR 函数实现相同的功能;因此,您必须修改函数名称:CHAR(65) = A |
LENGTH(str) |
返回给定字符串的长度:LENGTH ('MySQL') = 5 |
是 | LENGTH(str) |
等效于 Oracle:LENGTH('MySQL') = 5 |
REGEXP_REPLACE(str1,expr,str2) |
在字符串中搜索正则表达式模式:REGEXP_REPLACE('John', '[hn].', '1') = Jo1 |
否 | 不适用 | 仅受 MySQL 版本 8 及更高版本支持;如需解决此问题,请尽可能使用 REPLACE 函数,或将该功能转换为应用层 |
REGEXP_SUBSTR(str,expr) |
通过在字符串中搜索正则表达式模式来扩展 SUBSTR 函数的功能:REGEXP_SUBSTR('https://console.cloud.google.com/sql/instances','https://([[:alnum:]]+\.?){3,4}/?') = https://console.cloud.google.com/ |
否 | 不适用 | 仅受 MySQL 版本 8 及更高版本支持。如需解决此问题,请尽可能使用 SUBSTR 函数,或将该功能转换为应用层。 |
REGEXP_COUNT(str,expr) |
返回模式在源字符串中出现的次数。 | 否 | 不适用 | 如需寻求替代解决方案,请将该功能转换为应用层。 |
REGEXP_INSTR(index,expr) |
在字符串位置 (index) 中搜索正则表达式模式。 | 否 | 不适用 | 仅受 MySQL 版本 8 及更高版本支持。 |
REVERSE(str) |
返回倒序字符串REVERSE('MySQL') = LQSyM |
是 | REVERSE |
等效于 Oracle:REVERSE('MySQL') = LQSyM |
数值函数
Oracle 函数 | Oracle 函数规范或实现 | MySQL 等效情况 | MySQL 对应函数 | MySQL 函数规范或实现 |
---|---|---|---|---|
ABS(n) |
n 的绝对值:ABS(-4.6) = 4.6 |
是 | ABS |
等效于 Oracle:ABS(-4.6) = 4.6 |
CEIL(n) |
返回大于或等于 n 的最小整数:CEIL(21.4) = 22 |
是 | CEIL |
等效于 Oracle:CEIL(21.4) = 22 |
FLOOR(n) |
返回等于或小于 n 的最大整数:FLOOR(-23.7) = -24 |
是 | FLOOR |
等效于 Oracle:FLOOR(-23.7) = -24 |
MOD(m,n) |
返回 m 除以 n 所得的余数:MOD(10, 3) = 1 |
是 | MOD(m,n) |
等效于 Oracle:MOD(10,3) = 1 |
ROUND(m,n) |
返回 m 四舍五入到小数点右侧 n 个整数位的结果:ROUND(1.39,1) = 1.4 |
是 | ROUND |
等效于 Oracle:ROUND(1.39,1) = 1.4 |
TRUNC(n1, n2) |
返回 n1 截断到 n2 个小数位的结果:TRUNC(99.999) = 99 TRUNC(99.999,0) = 99 |
部分等效;函数名称不同 | TRUNCATE(n1, n2) |
MySQL TRUNCATE 函数必须接受一个输入数字和一个用于指定小数点右侧精确度的整数:TRUNCATE(99.999,0) = 99 |
日期和时间函数
Oracle 函数 | Oracle 函数规范或实现 | MySQL 等效情况 | MySQL 对应函数 | MySQL 函数规范或实现 |
---|---|---|---|---|
SYSDATE |
返回针对数据库服务器所在操作系统设置的当前日期和时间:SELECT SYSDATE FROM DUAL = 31-JUL-2019 |
部分 | SYSDATE() |
MySQL SYSDATE() 必须包含括号,并返回与 Oracle SYSDATE 函数不同的日期/时间格式:SELECT SYSDATE() FROM DUAL; = 2019-01-31 10:01:01.0 请注意,您可以在会话级别更改日期/时间格式 |
SYSTIMESTAMP |
返回系统日期,包括小数秒和时区:SELECT SYSTIMESTAMP FROM DUAL = 01-JAN-19 07.37.11.622187000 AM +00:00 |
部分等效;函数名称不同 | CURRENT_TIMESTAMP |
MySQL 与 Oracle 返回的日期/时间格式不同。日期格式(或不同的日期函数)必须与原始日期/时间格式匹配:SELECT CURRENT_TIMESTAMP FROM DUAL = 2019-01-31 06:55:07 |
LOCAL_TIMESTAMP |
以数据类型 TIMESTAMP 的值返回会话时区的当前日期和时间:SELECT LOCAL_TIMESTAMP FROM DUAL = 01-JAN-19 10.01.10.123456 PM |
部分等效;日期/时间格式不同。 | LOCAL_TIMESTAMP |
MySQL 与 Oracle 返回的日期/时间格式不同。日期/时间格式(或不同的日期函数)必须与原始日期/时间格式匹配:SELECT LOCAL_TIMESTAMP FROM DUAL = 2019-01-01 10:01:01.0 |
CURRENT_DATE |
返回会话时区的当前日期:SELECT CURRENT_DATE FROM DUAL = 31-JAN-19 |
部分等效;日期/时间格式不同 | CURRENT_DATE |
MySQL 与 Oracle 返回的日期/时间格式不同。日期/时间格式(或不同的日期函数)必须与原始日期/时间格式匹配:SELECT CURRENT_DATE FROM DUAL = 2019-01-31 |
CURRENT_TIMESTAMP |
返回会话时区的当前日期和时间:SELECT CURRENT_TIMESTAMP FROM DUAL = 31-JAN-19 06.54.35.543146 AM +00:00 |
部分等效;日期/时间格式不同 | CURRENT_TIMESTAMP |
MySQL 与 Oracle 返回的日期/时间格式不同。日期/时间格式(或使用不同的日期函数)必须与原始日期/时间格式匹配:SELECT CURRENT_TIMESTAMP FROM DUAL = 2019-01-31 06:55:07 |
ADD_MONTHS |
返回日期加整数月数:ADD_MONTHS(SYSDATE, 1) = 31-JAN-19 |
部分等效;函数名称不同 | ADDDATE |
为了实现相同的功能,MySQL 使用 ADDDATE 函数:ADDDATE(SYSDATE(), 1) = 2019-08-01 06:42:49.0 默认情况下,MySQL 与 Oracle 返回的日期/时间和范围/格式不同。日期/时间格式(或不同的日期函数)必须与原始日期/时间格式匹配。 |
EXTRACT (date part) |
从日期/时间或时间间隔表达式中返回指定日期/时间字段的值:EXTRACT(YEAR FROM DATE '2019-01-31') = 2019 |
是 | EXTRACT (date part) |
等效于 Oracle:EXTRACT(YEAR FROM DATE '2019-01-31') = 2019 |
LAST_DAY |
返回当月最后一天的日期:LAST_DAY('01-JAN-2019') = 31-JAN-19 |
部分等效;日期/时间格式不同 | LAST_DAY |
MySQL 与 Oracle 返回的日期/时间格式不同。日期/时间格式(或不同的日期函数)必须与原始日期/时间格式匹配:LAST_DAY('2019-01-01') = 2019-01-31 |
MONTH_BETWEEN |
返回日期 date1 和 date2 之间的月数:MONTHS_BETWEEN( SYSDATE, SYSDATE-60) = 1.96 |
部分等效;函数名称不同 | PERIOD_DIFF(date1,date2) |
为计算月数之差值,MySQL PERIOD_DIFF 函数会计算两个时间段(格式为 YYMM 或 YYYYMM )的整数差值,然后返回此差值:PERIOD_DIFF( '201903', '201901') = 2 若要获得与 Oracle MONTH_BETWEEN 函数返回的值相同的值,需要进行更具体的转换 |
TO_CHAR (日期/时间) |
将日期/时间或时间戳数据类型转换为 VARCHAR2 数据类型的值(采用日期格式所指定的格式):TO_CHAR( SYSDATE,'DD-MM-YYYY HH24:MI:SS') = 01-01-2019 10:01:01 |
部分等效;函数名称不同 | DATE_FORMAT |
MySQL DATE_FORMAT 函数会按照日期格式定义所指定的格式来设置日期格式:DATE_FORMAT( SYSDATE(),'%d-%m-%Y %H:%i:%s') = 01-01-2019 10:01:01 |
编码和解码函数
Oracle 函数 | Oracle 函数规范或实现 | MySQL 等效情况 | MySQL 对应函数 | MySQL 函数规范或实现 |
---|---|---|---|---|
DECODE |
使用 IF-THEN-ELSE 语句的功能将表达式与每个搜索值逐一进行比较 |
否 | CASE |
使用 MySQL CASE 语句实现类似功能。 |
DUMP |
返回一个 VARCHAR2 值,此值包含给定表达式的数据类型代码、长度(字节数)和内部表示法。 |
否 | 不适用 | 不受支持。 |
ORA_HASH |
计算给定表达式的哈希值。 | 否 | MD5/SHA |
使用 MySQL MD5 (对于 128 位校验和)或 SHA 函数(对于 160 位校验和)以生成哈希值 |
转换函数
Oracle 函数 | Oracle 函数规范或实现 | MySQL 等效情况 | MySQL 对应函数 | MySQL 函数规范或实现 |
---|---|---|---|---|
CAST |
将一个内置数据类型或集合类型值转换为另一个内置数据类型或集合类型值:CAST('1' as int) + 1 = 2 |
部分 | CAST |
MySQL CAST 函数与 Oracle 功能类似,但在某些情况下,您必须根据需要进行显式还是隐式转换来调整该函数:CAST('1' AS SIGNED) + 1 = 2 |
CONVERT |
将字符串从一个字符集转换为另一个字符集:CONVERT('Ä Ê Í Õ Ø A B C D E ', 'US7ASCII', 'WE8ISO8859P1') = ?? ?? ?? A B C |
部分 | CONVERT |
MySQL CONVERT 函数需要对语法和参数进行一些调整,才能返回与 Oracle 所返回的结果相同的结果:CONVERT('Ä Ê Í A B C ' USING utf8) = Ä Ê Í A B C |
TO_CHAR (string/numeric) |
该函数会将数字或日期转换为字符串:TO_CHAR(22.73,'$99.9') = $22.7 |
否 | FORMAT |
MySQL FORMAT 函数会执行数字的“#,###.##”格式,并将其舍入到某一小数位,然后以字符串形式返回结果;该函数的功能与 Oracle 的功能有所不同:CONCAT('$', FORMAT(22.73, 1)) = $22.7 |
TO_DATE |
Oracle TO_DATE 函数会按来源特定的日期/时间格式将字符串转换为日期:TO_DATE( '2019/01/01', 'yyyy-mm-dd') = 01-JAN-2019 |
部分等效;函数名称和日期/时间格式不同 | STR_TO_DATE |
MySQL STR_TO_DATE 函数接受一个字符串,并按照日期/时间格式所指定的格式返回日期:STR_TO_DATE( '2019/01/01', '%Y/%m/%d') = 2019-01-01 |
TO_NUMBER |
将表达式转换为 NUMBER 数据类型的值:TO_NUMBER('01234') = 1234 |
否 | CAST |
若要寻求替代解决方案,请使用 MySQL CAST 函数返回与 Oracle TO_NUMBER 所返回的结果相同的结果:CAST('01234' as SIGNED) = 1234 |
条件式 SELECT 函数
Oracle 函数 | Oracle 函数规范或实现 | MySQL 等效情况 | MySQL 对应函数 | MySQL 函数规范或实现 |
---|---|---|---|---|
CASE |
CASE 语句会从一系列条件中进行选择,并使用以下语法运行相应的语句:CASE WHEN condition THEN result [WHEN ...] [ELSE result] END |
是 | CASE |
除了 CASE 函数之外,MySQL 还支持在 SELECT 语句中使用 IF/ELSE 条件式处理功能:CASE WHEN condition THEN result [WHEN ...] [ELSE result] END |
Null 函数
Oracle 函数 | Oracle 函数规范或实现 | MySQL 等效情况 | MySQL 对应函数 | MySQL 函数规范或实现 |
---|---|---|---|---|
COALESCE |
返回表达式列表中的第一个非 null 表达式:COALESCE( null, '1', 'a') = a |
是 | COALESCE |
等效于 Oracle:COALESCE( null, '1', 'a') = 1 |
NULLIF |
比较 expr1 和 expr2m。如果它们相等,则函数会返回 null。如果它们不相等,则函数会返回 expr1:NULLIF('1', '2') = a |
是 | NULLIF |
等效于 Oracle:NULLIF('1', '2') = a |
NVL |
在查询结果中将 null(返回为空)替换为字符串:NVL(null, 'a') = a |
否 | IFNULL |
MySQL 等效函数是 IFNULL 函数,该函数将 null 值替换为给定字符串:IFNULL(null, 'a') = a |
NVL2 |
根据指定的表达式是 null 还是非 null 确定 查询返回的值。 |
否 | CASE |
CASE 语句 会从一系列条件中进行选择,并运行相应的语句: CASE WHEN condition THEN result [WHEN ...] [ELSE result] END |
环境和标识符函数
Oracle 函数 | Oracle 函数规范或实现 | MySQL 等效情况 | MySQL 对应函数 | MySQL 函数规范或实现 |
---|---|---|---|---|
SYS_GUID |
生成并返回由 16 个字节组成的全局唯一标识符(RAW 值):SELECT SYS_GUID() FROM DUAL = 8EFA4A31468B4C6DE05011AC0200009E |
否 | REPLACE 和 UUID |
若要解决此问题,请使用 MySQL REPLACE 和 UUID 函数来模拟 Oracle SYS_GUID 函数:REPLACE( UUID(), '-', '') |
UID |
返回一个整数,该整数唯一标识会话用户 (已登录的用户): SELECT UID FROM DUAL = 43 |
否 | 不适用 | 不适用 |
USER |
返回当前会话用户名:SELECT USER FROM DUAL = UserName |
部分 | USER + INSTR + SUBSTR |
MySQL USER 函数返回用户名和连接服务器 (root@IP )。如需仅检索用户名,请使用其他支持函数:SELECT SUBSTR(USER(), 1, INSTR(USER(), '@') -1) FROM DUAL = root |
USERENV |
返回当前用户会话和当前参数配置的相关信息:SELECT USERENV('LANGUAGE') FROM DUAL = ENGLISH_AMERICA.AL32UTF8 |
否 | SHOW SESSION VARIABLES |
使用 MySQL SHOW SESSION VARIABLES 语句查看当前会话的设置: SHOW SESSION VARIABLES LIKE '%collation%'; = utf8_general_ci |
ROWID |
Oracle 服务器会为每个表的每一行分配唯一的 ROWID ,以标识表中的行。ROWID 是数据对象编号、行的数据块、行位置以及数据文件所在行的地址。 |
否 | 不适用 | 如果可能,请尝试使用其他 MySQL 函数来模拟相同的功能。 |
ROWNUM |
返回一个数字,该数字表示 Oracle 从表或联接表中选择行的顺序。 | 否 | 不适用 | 如果可能,请尝试使用其他 MySQL 函数或会话变量模拟相同的功能。 |
聚合(分组)函数
Oracle 函数 | Oracle 函数规范或 实现 |
MySQL 等效情况 |
MySQL 对应函数 | MySQL 函数规范或实现 |
---|---|---|---|---|
AVG |
返回列或表达式的平均值。 | 是 | AVG |
等效于 Oracle |
COUNT |
返回查询所返回的行数。 | 是 | COUNT |
等效于 Oracle |
COUNT (DISTINCT) |
返回列或表达式中唯一值的数量。 | 是 | COUNT (DISTINCT) |
等效于 Oracle |
MAX |
返回列或表达式的最大值。 | 是 | MAX |
等效于 Oracle |
MIN |
返回列或表达式的最小值。 | 是 | MIN |
等效于 Oracle |
SUM |
返回列或表达式的值之和。 | 是 | SUM |
等效于 Oracle |
LISTAGG |
通过串联测量列的值,按 ORDER BY 子句中指定的一行显示每组中的数据:SELECT LISTAGG( DEPARTMENT_NAME, ', ') WITHIN GROUP (ORDER BY DEPARTMENT_NAME) DEPT FROM DEPARTMENTS; |
否 | GROUP_CONCAT |
使用 MySQL GROUP_CONCAT 函数返回与 Oracle 结果类似的结果,但在某些情况下会出现语法差异:SELECT GROUP_CONCAT( DEPARTMENT_NAME ORDER BY DEPARTMENT_NAME SEPARATOR ', ') DEPT FROM DEPARTMENTS; |
Oracle 12c Fetch
Oracle 函数 | Oracle 函数规范或 实现 |
MySQL 等效情况 |
MySQL 对应函数 | MySQL 函数规范或实现 |
---|---|---|---|---|
FETCH |
从多行查询的结果集中检索数据行:SELECT * FROM EMPLOYEES FETCH FIRST 10 ROWS ONLY; |
否 | LIMIT | 使用 MySQL LIMIT 子句仅检索一组特定的记录:SELECT * FROM EMPLOYEES LIMIT 10; |
基本过滤、运算符和子查询
在转换期间,基本过滤、运算符函数和子查询相对简单,几乎不需要执行额外的操作。
转换说明
检查日期格式并解决相关问题,因为 Oracle 和 MySQL 格式会返回不同的默认结果:
- Oracle
SYSDATE
函数默认返回01-AUG-19
。 - MySQL
SYSDATE()
函数默认返回2019-08-01 12:04:05
。 - 您可以使用 MySQL
[DATE_FORMAT](https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_date-format)
或[STR_TO_DATE](https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_str-to-date)
函数设置日期和时间格式。
Oracle 函数或子查询 | MySQL 等效情况 | MySQL 对应函数或子查询 | MySQL 函数规范或实现 |
---|---|---|---|
EXISTS/NOT EXISTS |
是 | EXISTS/NOT EXISTS |
SELECT * FROM DEPARTMENTS D WHERE EXISTS (SELECT 1 FROM EMPLOYEES E WHERE E.DEPARTMENT_ID = D.DEPARTMENT_ID); |
IN/NOT IN |
是 | IN/NOT IN |
SELECT * FROM DEPARTMENTS D WHERE DEPARTMENT_ID IN (SELECT DEPARTMENT_ID FROM EMPLOYEES E); |
LIKE/NOT LIKE |
是 | LIKE/NOT LIKE |
SELECT * FROM EMPLOYEES WHERE FIRST_NAME LIKE '_e_n%'; |
BETWEEN/NOT BETWEEN |
是 | BETWEEN/NOT BETWEEN |
SELECT * FROM EMPLOYEES WHERE EXTRACT(YEAR FROM HIRE_DATE) NOT BETWEEN 2001 and 2004; |
AND/OR |
是 | AND/OR |
SELECT * FROM EMPLOYEES WHERE DEPARTMENT_ID IN(100, 101) AND (SALARY >= 1000 OR HIRE_DATE <= '2006-02-05'); |
SubQuery |
是 | SubQuery |
MySQL 支持在 SELECT 级别、JOIN 语句和 WHERE/AND 子句的过滤中使用子查询:-- SELECT SubQuery SELECT D.DEPARTMENT_NAME, (SELECT AVG(SALARY) AS AVG_SAL FROM EMPLOYEES E WHERE E.DEPARTMENT_ID = D.DEPARTMENT_ID) AVG_SAL FROM DEPARTMENTS D; |
运算符 | 是 | 运算符 | MySQL 支持所有基本运算符:> | >= | < | <= | = | <> | != |
分析函数(或窗口函数和排名函数)
通过提供基于一组行来计算聚合值的功能,Oracle 分析函数可以扩展标准 SQL 分析函数的功能。这些函数可应用于单个查询表达式范围内的逻辑分区结果集。它们通常与商业智能报告和分析结合使用,有可能提高查询性能,替代使用更复杂的非分析 SQL 代码获得相同的结果。
转换说明
- MySQL 5.7 版不提供分析函数来支持直接的 SQL 语句转换。但是,MySQL 版本 8 中部分添加了此功能,导致您需要考虑转换分析函数,可能必须在迁移过程中执行手动操作。
- 可选的解决方案是重写代码以避免使用分析函数,从而恢复使用更传统的 SQL 代码解决方案,或将此逻辑移动到应用层。
下表列出了 Oracle 常见分析函数。
函数系列 | 相关函数 | 受 MySQL 5.7 支持 |
---|---|---|
分析和排名 | RANK AVERAGE_RANK DENSE_RANK RANK ROW_NUMBER PERCENT_RANK CUME_DIST NTILE FIRST_VALUE LAST_VALUE OVER (PARTITION BY...) |
否 |
分层 | CONNECT BY HIER_ANCESTOR HIER_CHILD_COUNT HIER_DEPTH HIER_LEVEL HIER_ORDER HIER_PARENT HIER_TOP |
否 |
延迟 | LAG LAG_VARIANCE LAG_VARIANCE_PERCENT LEAD LEAD_VARIANCE LEAD_VARIANCE_PERCENT |
否 |
通用表表达式 (CTE)
CTE 提供了一种方法来实现顺序代码的逻辑,以重复使用可能过于复杂或对多次使用效率不高的 SQL 代码。您可以为 CTE 命名,然后使用 WITH
子句在 SQL 语句的不同部分多次使用 CTE。
转换说明
- MySQL 5.7 版不支持 CTE,但 MySQL 版本 8 支持 CTE。
- 如需寻求替代解决方案,请使用派生表或子查询,或者重写 SQL 语句以消除 CTE 功能。
示例
Oracle |
---|
WITH DEPT_COUNT (DEPARTMENT_ID, DEPT_COUNT) AS (SELECT DEPARTMENT_ID, COUNT(*) FROM EMPLOYEES GROUP BY DEPARTMENT_ID) |
MySQL |
SELECT * FROM ( SELECT CONCAT(E.FIRST_NAME, ' ', E.LAST_NAME) AS EMP_NAME, (SELECT COUNT(*) FROM EMPLOYEES D WHERE E.DEPARTMENT_ID = D.DEPARTMENT_ID GROUP BY DEPARTMENT_ID) AS EMP_DEPT_COUNT FROM EMPLOYEES E ORDER BY 2 DESC) TBL WHERE EMP_DEPT_COUNT IS NOT NULL; |
MERGE 语句
MERGE
(或 UPSERT
)语句提供了一种方法,用于指定单条 SQL 语句,这些语句有条件地在一个 MERGE
操作中执行 DML 操作,而不是像单个 DML 操作那样单独运行。它会从源表中选择记录,然后通过指定逻辑结构自动对目标表执行多个 DML 操作。此功能可帮助您避免使用多个插入、更新或删除操作。请注意,MERGE
是一条确定性语句,这意味着 MERGE
语句一旦处理了一行,您就无法再次使用同一 MERGE
语句处理该行。
转换说明
MySQL 5.7 版不支持 MERGE
功能,这一点与 Oracle 不同。为了部分模拟 MERGE
功能,MySQL 提供了 REPLACE
和 INSERT… ON DUPLICATE KEY UPDATE
语句:
REPLACE
:工作原理与INSERT
语句基本相同,不同之处在于,如果表中的旧行与PRIMARY KEY
或UNIQUE
索引的新行具有相同的值,则系统在插入新行之前会删除旧行。INSERT… ON DUPLICATE KEY UPDATE
:如果插入的行会导致PRIMARY KEY
或UNIQUE
索引中出现重复的值,则系统会对旧行执行UPDATE
操作,以消除重复键异常,例如:INSERT INTO tbl (a,b,c) VALUES (1,2,3) ON DUPLICATE KEY UPDATE c=c+1; UPDATE tbl SET c=c+1 WHERE a=1;
另一种解决方案是使用 INSERT
、UPDATE
、DELETE
命令以及异常和重复处理功能,将 MERGE
功能转换为存储过程来管理 DML 操作。
SQL 语句提示
Oracle 提供了大量 SQL 查询提示,可让用户影响优化器行为及其决策,从而生成更高效的查询执行计划。Oracle 支持 60 多种不同的数据库提示。MySQL 提供了一组有限的查询提示。
通常,MySQL 5.7 版支持两种类型的查询提示:OPTIMIZER
HINTS
和 INDEX HINTS
。MySQL 优化器提示提供了在单条 SQL 语句中控制优化器行为的功能,例如:
SELECT /*+ NO_RANGE_OPTIMIZATION(tbl PRIMARY, col1_idx) */ col1 FROM tbl;
MySQL MySQL
提示名称 | 提示概览 | 适用范围 |
---|---|---|
BKA, NO_BKA |
影响批量键访问联接处理 | 查询块、表 |
BNL, NO_BNL |
影响块嵌套循环联接处理 | 查询块、表 |
MAX_EXECUTION_TIME |
限制语句执行时间 | 全球 |
MRR, NO_MRR |
影响多范围读取优化 | 表、索引 |
NO_ICP |
影响索引条件下推优化 | 表、索引 |
NO_RANGE_OPTIMIZATION |
影响范围优化 | 表、索引 |
QB_NAME |
为查询块指定名称 | 查询块 |
SEMIJOIN, NO_SEMIJOIN |
影响半联接策略 | 查询块 |
SUBQUERY |
影响实体化、IN 到 EXISTS 子查询策略。 |
查询块 |
MySQL 索引提示可以为优化器提供有关如何在查询处理期间选择索引的信息。USE
、FORCE
或 IGNORE
关键字用于控制优化器索引的使用过程,例如:
SELECT * FROM tbl USE INDEX (col1_index, col2_index);
-- OR
SELECT * FROM tbl IGNORE INDEX (col1_index, col2_index);
转换说明
由于 Oracle 和 MySQL 优化器之间存在基本差异,并且由于 Oracle 和 MySQL 查询提示之间存在一些重叠现象,因此我们建议您转换在目标 MySQL 数据库上包含未指定查询提示的任何 Oracle SQL 语句。
通过 MySQL 工具(例如用于实时性能信息中心的 MySQL Workbench)和功能(例如使用执行计划检查查询以及根据使用场景调整实例或会话参数)对 MySQL 进行性能调整。
执行计划
执行计划的主要目的是让您深入了解查询优化器为访问数据库数据所做的选择。查询优化器为数据库用户生成 SELECT
、INSERT
、UPDATE
、DELETE
语句的执行计划,还可以让管理员更好地查看特定查询和 DML 操作。如果您需要对查询进行性能调整(例如,为了确定索引性能或确定是否需要创建缺少的索引),则这些计划特别有用。
执行计划可能会受到数据量、数据统计信息和实例参数(全局或会话参数)的影响。
转换注意事项
执行计划不是需要迁移的数据库对象;相反,它们是工具,用于分析 Oracle 和 MySQL 在针对相同数据集运行相同语句时的性能差异。
MySQL 不支持 Oracle 所用的执行计划语法、功能或输出。
示例
Oracle 执行计划 |
---|
SQL> EXPLAIN PLAN FOR SELECT * FROM EMPLOYEES WHERE EMPLOYEE_ID = 105; |
MySQL 执行计划 |
mysql> EXPLAIN SELECT * FROM EMPLOYEES WHERE EMPLOYEE_ID = 105; |
存储过程、函数和触发器
PL/SQL 是 Oracle 扩展过程语言,用于在数据库中创建、存储和应用基于代码的解决方案。通常,数据库存储过程和函数是由 ANSI SQL 和 SQL 扩展过程语言(例如 PL/SQL for Oracle、PL/pgSQL for PostgreSQL 以及适用于 MySQL 的 MySQL 过程语言)组成的代码元素。MySQL 将数据库所用的名称用于自己的扩展过程语言。
这些存储过程和函数的目的是为更适合从数据库内部(而不是应用)运行的要求(例如性能、兼容性和安全性)提供解决方案。虽然存储过程和函数都使用 PL/SQL,但存储过程主要用于执行 DDL/DML 操作,而函数主要用于执行计算以返回特定结果。
从 PL/SQL 迁移到 MySQL 过程语言
从 Oracle PL/SQL 到 MySQL 代码迁移的角度来看,MySQL 过程实现与 Oracle 过程实现有所不同。因此,您必须进行代码迁移才能将 Oracle 的 PL/SQL 功能转换为 MySQL 存储过程和函数。此外,MySQL 不支持 Oracle Package 和 Package Body,因此在进行代码转换时,请将这些元素转换(或解析)为单个 MySQL 代码单元。请注意,MySQL 存储过程和函数也称为例程。routines
代码对象所有者
在 Oracle 中,存储过程或函数的所有者是特定用户。在 MySQL 中,所有者是特定架构(由数据库用户在数据库中创建)。
代码对象特权和安全性
在 Oracle 中,若要创建存储过程或函数,用户必须拥有 CREATE PROCEDURE
系统特权(若要在其他不同用户名义下创建过程或函数,数据库用户必须拥有 CREATE
ANY PROCEDURE
特权)。若要执行存储过程或函数,数据库用户必须拥有 EXECUTE
特权。
在 MySQL 中,若要创建代码元素,用户必须拥有 CREATE
ROUTINE
特权和 EXECUTE
特权才能运行。MySQL DEFINER
子句定义代码对象的用户创建者,并且用户必须拥有适当的特权,例如 CREATE ROUTINE
。
MySQL 存储过程和函数语法
以下示例展示了 MySQL 存储过程和函数语法:
CREATE
[DEFINER = user]
PROCEDURE sp_name ([proc_parameter[,...]])
[characteristic ...] routine_body
CREATE
[DEFINER = user]
FUNCTION sp_name ([func_parameter[,...]])
RETURNS type
[characteristic ...] routine_body
proc_parameter:
[ IN | OUT | INOUT ] param_name type
func_parameter:
param_name type
type:
Any valid MySQL data type
characteristic:
COMMENT 'string'
| LANGUAGE SQL
| [NOT] DETERMINISTIC
| { CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA }
| SQL SECURITY { DEFINER | INVOKER }
routine_body:
Valid SQL routine statement