Oracle 사용자를 MySQL용 Cloud SQL로 마이그레이션: 쿼리, 저장 프로시져, 함수, 트리거

이 문서는 2세대 인스턴스인 MySQL용 Cloud SQL 버전 5.7로의 Oracle® 11g/12c 데이터베이스 마이그레이션 계획 및 수행과 관련된 주요 정보 및 안내를 제공하는 시리즈의 일부입니다. 이 시리즈는 다음 문서로 구성되어 있습니다.

쿼리

Oracle 및 MySQL용 Cloud SQL에서는 ANSI SQL 표준이 지원됩니다. 스칼라 함수 또는 다른 Oracle 확장 기능을 지정하지 않고 기본 구문 요소만 사용해서 SQL 문을 마이그레이션하는 것은 일반적으로 매우 간단합니다. 다음 섹션에서는 일반적인 Oracle 쿼리 요소와 MySQL용 Cloud SQL 해당 요소에 대해 설명합니다.

기본 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 구문을 참조하세요.

  • 인라인 뷰
    • 인라인 뷰(파생된 테이블이라고도 함)는 FROM 절에 있는 SELECT 문이며, 서브 쿼리로 사용됩니다.
    • 인라인 뷰는 복합 계산을 없애거나 조인 연산을 제거하고, 여러 개별 쿼리를 간소화된 단일 쿼리로 압축하여 복잡한 쿼리를 간단하게 만드는 데 도움이 될 수 있습니다.
    • 변환 참고: 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
MySQL용 Cloud SQL 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 문

Oracle JOIN 문은 FULL JOIN 절을 제외하고 MySQL 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
아니요 문제 해결을 위해서는 LEFT RIGHT JOIN 문에 UNION을 사용하는 방법을 고려하세요.
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은 UNIONUNION ALL 함수를 제외하고 Oracle INTERSECTMINUS 함수를 지원하지 않습니다.

  • UNION: 2개 이상의 SELECT 문의 결과 집합을 연결하고 중복 레코드를 없앱니다.
  • UNION ALL: 2개 이상의 SELECT 문의 결과 집합을 연결하고 중복 레코드를 없애지 않습니다.
  • INTERSECT: 한 레코드가 두 데이터 집합 모두에 존재할 경우에만 2개 이상의 SELECT 문의 교집합을 반환합니다.
  • MINUS: 2개 이상의 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)
str1와 연결된 str1을 반환합니다.
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 문자열의 위치(색인)를 반환합니다.
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)
0~225 사이의 숫자 값인 ASCII 코드 값을 해당 문자로 반환합니다.
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)
정규 표현식 패턴에 대해 문자열 위치(색인)를 검색합니다. 아니요 해당 사항 없음 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)
mn으로 나눈 나머지를 반환합니다.
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)
n1n1 소수점 자릿수로 잘라서 반환합니다.
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(날짜 부분) 날짜/시간 또는 간격 표현식에서 지정된 날짜/시간 필드의 값을 반환합니다.
EXTRACT(YEAR FROM DATE '2019-01-31')
= 2019
EXTRACT(날짜 부분) 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
128비트 체크섬에는 MySQL MD5를 사용하고 160비트 체크섬에는 SHA 함수를 사용하여 해시 값을 생성합니다.
변환 함수
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
Oracle과 정확히 동일한 결과를 반환하기 위해서는 MySQL CONVERT 함수에서 구문 및 매개변수를 일부 조정해야 합니다.
CONVERT('Ä Ê Í A B C ' USING utf8)
= Ä Ê Í A B C
TO_CHAR
(문자열/숫자)
이 함수는 숫자 또는 날짜를 문자열로 변환합니다.
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 해당 함수는 null 값을 제공된 문자열로 바꾸는 IFNULL 함수입니다.
IFNULL(null, 'a')
= a
NVL2
지정된 표현식이 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 가져오기
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은 JOIN 문 및 WHERE/AND 절의 필터링에 대해 SELECT 수준의 하위 쿼리를 지원합니다.
-- 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 문의 서로 다른 부분에서 여러 번 사용될 수 있습니다.

변환 메모

  • MySQL 버전 5.7에서는 CTE가 지원되지 않고 MySQL 버전 8에서 지원됩니다.
  • 다른 솔루션을 위해서는 파생된 테이블 또는 하위 쿼리를 사용하거나 CTE 기능이 제거되도록 SQL 문을 다시 작성합니다.

예시

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) 문은 개별적으로 실행되는 단일 DML 문과 달리 하나의 MERGE 작업으로 DML 작업을 조건부로 수행하는 단일 SQL 문을 지정할 수 있는 방법을 제공합니다. 소스 테이블에서 레코드를 선택한 후 논리적 구조를 지정하여 대상 테이블에서 여러 DML 작업을 자동으로 수행합니다. 이 기능은 삽입, 업데이트, 삭제를 여러 번 사용하지 않도록 도와줍니다. MERGE는 결정적 문입니다. 즉, MERGE 문으로 행이 처리된 다음에는 동일한 MERGE 문을 사용해서 이를 다시 처리할 수 없습니다.

변환 메모

MySQL 버전 5.7에서는 Oracle과 달리 MERGE 기능이 지원되지 않습니다. MERGE 기능을 부분적으로 시뮬레이션하기 위해 MySQL은 REPLACEINSERT… 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 명령어를 사용해서 DML 작업을 관리하도록 MERGE 기능을 저장 프로시져로 변환하는 것입니다.

SQL 문 힌트

Oracle은 사용자가 옵티마이저 동작에 영향을 줄 수 있게 해주는 대규모 SQL 쿼리 힌트 모음과 보다 효율적인 쿼리 실행 계획을 만들기 위한 의사결정 기능을 제공합니다. Oracle은 60개 이상의 데이터베이스 힌트를 지원합니다. MySQL은 제한적인 쿼리 힌트 집합을 제공합니다.

일반적으로 MySQL 버전 5.7은 OPTIMIZER HINTSINDEX 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 쿼리 힌트 간에 겹치는 부분이 있더라도 제한적이기 때문에, 지정되지 않은 쿼리 힌트가 포함된 모든 Oracle SQL 문을 대상 MySQL 데이터베이스로 변환하는 것이 좋습니다.

사용 사례에 따라 실행 계획을 사용하고 인스턴스 또는 세션 파라미터를 조정하여 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;

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과 Oracle용 PL/SQL, PostgreSQL용 PL/pgSQL, MySQL용 MySQL 절차적 언어와 같은 SQL 확장 절차적 언어로 구성되는 코드 요소입니다. MySQL은 자체의 고유 확장 절차적 언어에 대해 데이터베이스와 동일한 이름을 사용합니다.

이러한 저장 프로시져 및 함수의 목적은 성능, 호환성, 보안 등 애플리케이션 대신 데이터베이스 내에서 실행하기에 더 적합한 경우에 대한 솔루션을 제공하는 것입니다. 저장 프로시져와 함수 모두 PL/SQL이 사용되지만 저장 프로시져는 주로 DDL/DML 작업을 수행하기 위해 사용되고, 함수는 주로 특정 결과 반환을 위한 계산을 수행하기 위해 사용됩니다.

PL/SQL과 MySQL 절차적 언어

Oracle PL/SQL에서 MySQL로의 코드 마이그레이션 관점에서 MySQL 절차적 구현은 Oracle과 방식이 다릅니다. 따라서 Oracle의 PL/SQL 기능을 MySQL 저장 프로시져 및 함수로 변환하기 위해 코드 마이그레이션이 필요합니다. 또한 Oracle Package 및 Package Body가 MySQL에서 지원되지 않으므로, 코드 변환을 수행할 때 이러한 요소를 단일 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