将 Oracle 用户迁移到 Cloud SQL for MySQL:查询、存储过程、函数和触发器

本文档是系列文章中的一篇,该系列提供了关键的信息和指导来帮助计划和执行 Oracle® 11g/12c 数据库到 Cloud SQL for MySQL 5.7 版第二代实例的迁移。本系列文章包含以下部分:

查询

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)。
根据定义的表名称,区分大小写(例如,表名称只能是 ordersORDERS)。

您可以详细了解 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);

ERROR 1248 (42000): Every derived table must have its own alias

向内嵌视图添加别名:
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
如需解决此问题,请考虑将 UNIONLEFT 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 INTERSECTMINUS 函数,但 UNIONUNION ALL 函数除外:

  • UNION:附加两个或更多 SELECT 语句的结果集并消除重复记录。
  • UNION ALL:附加两个或更多 SELECT 语句的结果集,不消除重复记录。
  • INTERSECT:仅当两个数据集中都存在某条记录时,才返回两个或更多 SELECT 语句的交集。
  • MINUS:比较两个或更多 SELECT 语句,仅返回第一个查询中未由其他语句返回的不同行。

转换说明

从 Oracle INTERSECTMINUS 函数转换为 MySQL 时,请使用 JOIN 语句以及 INEXISTS 作为替代解决方案。

示例

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)
返回 str1str1 的串联结果:
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,它是使用 expr1 中的字符序列向左或向右填充 expr1 个字符后得到的结果:
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')

= MySQLDB
REPLACE(char,str1,str2)
等效于 Oracle:
REPLACE('ORADB', 'ORA', 'MySQL')

= MySQLDB
TRIM(str)
去除字符串的开头和/或结尾字符:
TRIM(both '-' FROM '-MySQL-')
= MySQL

TRIM(' MySQL ') = MySQL
TRIM(str)
等效于 Oracle:
TRIM(both '-' FROM '-MySQL-')
= MySQL

TRIM(' 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 截断到 n1 个小数位的结果:
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 函数会计算两个时间段(格式为 YYMMYYYYMM)的整数差值,然后返回此差值:
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
REPLACEUUID 若要解决此问题,请使用 MySQL REPLACEUUID 函数来模拟 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;

-- Single line results
= Accounting, Administration, Benefits, Construction
GROUP_CONCAT
使用 MySQL GROUP_CONCAT 函数返回与 Oracle 结果类似的结果,但在某些情况下会出现语法差异:
SELECT GROUP_CONCAT(
DEPARTMENT_NAME ORDER BY DEPARTMENT_NAME SEPARATOR ', ') DEPT
FROM DEPARTMENTS;

-- Single line results
= Accounting, Administration, Benefits, Construction
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);

-- OR
SELECT * FROM EMPLOYEES WHERE (EMPLOYEE_ID, DEPARTMENT_ID) IN((100, 90));
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;

-- JOIN SubQuery SELECT FIRST_NAME, LAST_NAME, SALARY FROM EMPLOYEES E JOIN (SELECT * FROM DEPARTMENTS WHERE LOCATION_ID = 2700) D ON E.DEPARTMENT_ID = D.DEPARTMENT_ID;
-- Filtering Subquery SELECT FIRST_NAME, LAST_NAME, SALARY FROM EMPLOYEES WHERE SALARY < (SELECT AVG(SALARY) FROM EMPLOYEES);
运算符 运算符 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)

SELECT E.FIRST_NAME ||' '|| E.LAST_NAME AS EMP_NAME, D.DEPT_COUNT AS EMP_DEPT_COUNT FROM EMPLOYEES E JOIN DEPT_COUNT D USING (DEPARTMENT_ID) ORDER BY 2 DESC;
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 提供了 REPLACEINSERT… ON DUPLICATE KEY UPDATE 语句:

  • REPLACE:工作原理与 INSERT 语句基本相同,不同之处在于,如果表中的旧行与 PRIMARY KEYUNIQUE 索引的新行具有相同的值,则系统在插入新行之前会删除旧行。

  • INSERT… ON DUPLICATE KEY UPDATE:如果插入的行会导致 PRIMARY KEYUNIQUE 索引中出现重复的值,则系统会对旧行执行 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;
    

另一种解决方案是使用 INSERTUPDATEDELETE 命令以及异常和重复处理功能,将 MERGE 功能转换为存储过程来管理 DML 操作。

SQL 语句提示

Oracle 提供了大量 SQL 查询提示,可让用户影响优化器行为及其决策,从而生成更高效的查询执行计划。Oracle 支持 60 多种不同的数据库提示。MySQL 提供了一组有限的查询提示。

通常,MySQL 5.7 版支持两种类型的查询提示:OPTIMIZER HINTSINDEX HINTSMySQL 优化器提示提供了在单条 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
影响实体化、INEXISTS 子查询策略。 查询块

MySQL 索引提示可以为优化器提供有关如何在查询处理期间选择索引的信息。USEFORCEIGNORE 关键字用于控制优化器索引的使用过程,例如:

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 进行性能调整。

执行计划

执行计划的主要目的是让您深入了解查询优化器为访问数据库数据所做的选择。查询优化器为数据库用户生成 SELECTINSERTUPDATEDELETE 语句的执行计划,还可以让管理员更好地查看特定查询和 DML 操作。如果您需要对查询进行性能调整(例如,为了确定索引性能或确定是否需要创建缺少的索引),则这些计划特别有用。

执行计划可能会受到数据量、数据统计信息和实例参数(全局或会话参数)的影响。

转换注意事项

执行计划不是需要迁移的数据库对象;相反,它们是工具,用于分析 Oracle 和 MySQL 在针对相同数据集运行相同语句时的性能差异。

MySQL 不支持 Oracle 所用的执行计划语法、功能或输出。

示例

Oracle 执行计划
SQL> EXPLAIN PLAN FOR
     SELECT * FROM EMPLOYEES WHERE EMPLOYEE_ID = 105;

SQL> SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY (FORMAT=>'ALL +OUTLINE'));
Plan hash value: 1833546154 --------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | --------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 69 | 1 (0)| 00:00:01 | | 1 | TABLE ACCESS BY INDEX ROWID| EMPLOYEES | 1 | 69 | 1 (0)| 00:00:01 | |* 2 | INDEX UNIQUE SCAN | EMP_EMP_ID_PK | 1 | | 0 (0)| 00:00:01 | ---------------------------------------------------------------------------------------------
MySQL 执行计划
mysql> EXPLAIN SELECT * FROM EMPLOYEES WHERE EMPLOYEE_ID = 105;

+----+-------------+-----------+------------+-------+---------------+---------+---------+-------+------+----------+-------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-----------+------------+-------+---------------+---------+---------+-------+------+----------+-------+ | 1 | SIMPLE | EMPLOYEES | NULL | const | PRIMARY | PRIMARY | 3 | const | 1 | 100.00 | NULL | +----+-------------+-----------+------------+-------+---------------+---------+---------+-------+------+----------+-------+

存储过程、函数和触发器

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 存储过程和函数也称为例程。

代码对象所有者

在 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