컴퓨터 공부/📗 CS

[데이터베이스] 10장 - SQL 에서 stored function 이란? + 언제 사용하면 좋을지에 대해

letzgorats 2024. 1. 12. 01:17

stored function 의 뜻과 예제

: 사용자가 정의한 함수

: DBMS에 저장되고 사용되는 함수

: SQL의 select, insert, update, delete statement에서 사용할 수 있다.

 

stored function을 만들 때, delimiter 라는 키워드를 사용한다.

 

delimiter 는 구분자를 의미하는데, 기본적으로 SQL 에서 사용되는 delimiter는 (;)세미콜론이다.

하지만, stored program의 정의를 서버로 보내기 위해서는 delimiter를 일시적으로 재정의 해주어야만 한다.

이 때, 주의해야 할 점은 DELIMITER를 재정의하고 stored program을 정의한 후에 delimiter를 기존의 세미콜론(;)으로 바꿔주는 것이 좋다. (계속해서 DELIMITER를 사용자가 재정의한 것으로 사용할게 아니라면 말이다.)

또한, 사용자가 위 코드처럼 DELIMITER를 $$ 가 아닌 어떤 문자나 문자열을 한글자 혹은 여러글자로 구성해서 선언할 수도 있다. 하지만, 백슬래시(\)만큼은 피해야 한다. 왜나하면 백슬래시(\) MySQL에서 escape문이기 때문이다.
만약 stored program 내부에 ;(세미콜론)이 사용되지 않는다면 굳이 불필요하게 DELIMETER를 사용하지 않아도 된다.

 

대략적인 구문은 이렇다.

delimiter 를 지정해주고 시작한다.
CREATE FUNCTION
키워드를 통해 함수를 만들고 함수 이름을 지정한다. 파라미터가 있다면, (파라미터 자료형) 순으로 적어줘야한다.
RETURNS 키워드 뒤에는 반환하는 타입을 작성해줘야 한다.(s 붙인다)
body 부분BEGIN 으로 시작하고 END 로 끝난다. ( 이 때는 RETURN 문으로 값을 반환한다.-s 안 붙인다)
지정했던 delimiter 로 함수생성완료를 닫아주고, 다시 세미콜론(;)으로 바꿔준다.

※ 그럼 이제, stored function 을 작성하고 각 예제를 보면서 차근차근 살펴보도록 하자.

 

stored function 예제 1)

- 임직원의 ID 를 열자리 정수로 랜덤하게 발급하고 싶다.
- ID의 맨 앞자리는 1로 고정이다.

 

 

delimiter $$
CREATE FUNCTION id_generator()
RETURNS int
NO SQL
BEGIN
    RETURN (1000000000 + floor(rand() * 1000000000));
END
$$
delimiter ;

 

함수 생성

 

rand 함수는 0부터 1미만까지의 수를 랜덤해서 뽑아내는 함수이다. 이 수에 10억을 곱해주면 9자리의 정수부를 가진 숫자가 반환될 것 이다.

그 후, floor 라는 내장함수를 통해 전체 이 값을 내림시켜줘서 정수만 가져올 수 있도록 한다. 그리고 1000000000 과 더해서 그 값을 리턴하면 우리가 원하는 1로 고정된 열자리 정수를 뽑아낼 수 있다.

 

여기서 NO SQL 은 뭘까?

NO SQL
→ MySQL의 stored program을 정의할 때 사용되는 키워드로, 해당 stored program이 SQL 데이터를 읽거나 변경하지 않음을 명시하는 것이다. 즉, 이 함수나 프로시저 내에서는 데이터베이스의 데이터를 조회, 삽입, 수정, 삭제하는 어떤 SQL 문도 실행되지 않는다는 것을 데이터베이스 시스템에 알려주는 것이다.

 

그럼 우린 이제 id_generator() 라는 함수를 만든 것이다.

 

이제, 이 함수를 통해 EMPLOYEE 테이블에 값을 넣어보자. 현재 EMPLOYEE 테이블은 아래와 같다.

현재 EMPLOYEE 테이블

 

'JENNY' 라는 이름의 직원을 EMPLOYEE 테이블에 추가한다. 이 때, id_generator() 를 사용하면, 어떻게 값이 들어갈지 살펴보자.

INSERT INTO EMPLOYEE
VALUES (id_generator(),'JENNY','1991-08-04','F','PO',160000000,1005);

EMPLOYEE 테이블에 추가된 JENNY

 

id 값이 1로 시작하는 랜덤한 열자리 정수로 발급된 것을 확인할 수 있다.

 


 

 

다른 예제를 살펴보자. stored function 예제 2)

- 부서의 ID를 파라미터로 받으면 해당 부서의 평균 연봉을 알려주는 함수를 작성하자.

 

 

delimiter $$
CREATE FUNCTION dept_avg_salary(d_id int)
    RETURNS int
    READS SQL DATA
    BEGIN
        DECLARE avg_sal int;
        SELECT AVG(salary) INTO avg_sal
                           FROM EMPLOYEE
                           WHERE dept_id = d_id;
        RETURN avg_sal;
    END
    $$
delimiter ;

함수 생성

 

dept_avg_salary 함수는 매개변수로 부서ID(d_id) 에 해당하는 직원들의 평균 연봉을 계산한다.

'DECLARE' 문을 사용해서 로컬 변수 'avg_sal' 와 타입을 선언하고,

'SELECT ... INTO' 구문을 사용해 평균 연봉을 계산하여 이 변수에 할당한 후, 이 값을 반환하는 로직이다.

'RETURN' 문을 사용해 BEGIN END 사이에서 정해진 값을 반환한다.

 

물론, DECLARE를 안쓰고 한 번에 값을 할당하고 싶으면 '@'를 사용하면 된다. 하지만, 이 때는 스코프가 확장되어서 해당 함수가 아닌 외부 쿼리에서도 참조가 가능하다.

delimiter $$
CREATE FUNCTION dept_avg_salary(d_id int)
    RETURNS int
    READS SQL DATA
    BEGIN
        SELECT AVG(salary) INTO @avg_sal
                           FROM EMPLOYEE
                           WHERE dept_id = d_id;
        RETURN @avg_sal;
    END
    $$
delimiter ;

 

여기서 사용된 '@avg_sal' 은 사용자 변수를 의미한다. 사용자 변수는 세션 레벨에서 설정되며, 함수 내부 뿐만 아니라, 해당 세션의 다른 쿼리에서도 참조할 수 있다.

로컬 변수를 사용하는 대신 사용자 변수를 사용하는 것은 함수나 프로시저 내부에서만 유효한 값을 저장하는 것보다 더 넓은 스코프에서 값을 유지하고 싶을 때 유용하다.

그러나 일반적인 경우에는 stored program 내에서만 유효한 로컬변수를 사용하는 것이 더 바람직할 수 있다.

 

 

 여기서 READS SQL DATA 는 뭘까?

READS SQL DATA
→ MySQL의 stored program을 정의할 때 사용되는 키워드로, 해당 함수가 데이터베이스의 데이터를 읽을 수 있지만 변경하지 않는다는 것을 명시한다. 즉, 함수가 데이터를 조회('SELECT')할 수 있으나 데이터를 삽입('INSERT'), 갱신('UPDATE'), 삭제('DELETE')하는 작업은 수행하지 않는다는 것을 나타낸다. 이는 함수의 side effect를 관리하고, 데이터베이스의 무결성을 유지하는 데 도움이 된다. 

 

그럼 우린 이제 dept_avg_salary () 라는 함수를 만든 것이다.

 

이제, 이 함수를 통해 DEPARTMENT 테이블에서의 부서마다의 평균연봉을 가져와보자. 현재 EMPLOYEE 테이블과 DEPARTMENT 테이블은 아래와 같다.

EMPLOYEE 테이블 & DEPARTMENT 테이블

 

dept_avg_salary의 인자값에 dept_id 를 넣고 SELECT를 해보면 아래와 같다.

SELECT *,dept_avg_salary(dept_id)
FROM DEPARTMENT;

부서별 평균연봉

 

각 부서의 평균연봉이 나온 것을 확인 할 수 있다. 현재 legal 부서에는 어떠한 직원도 속해있지 않기 때문에 NULL 이 나온다.

 


 

 

다른 예제를 살펴보자. stored function 예제 3)

- 졸업 요건 중 하나인 토익 800 이상을 충족했는지를 알려주는 함수를 작성하자.

 

delimiter $$
CREATE FUNCTION toeic_pass_fail(toeic_score int)
RETURNS char(4)
NO SQL
BEGIN
    DECLARE pass_fail char(4);
    IF     toeic_score is NULL    THEN SET pass_fail = 'fail';
    ELSEIF toeic_score < 800      THEN SET pass_fail = 'fail';
    ELSE                               SET pass_fail = 'pass';
    END IF;
    RETURN pass_fail;
END
$$
delimiter ;

 

함수 생성

 

toeic_pass_fail 함수는 정수타입의 매개변수 'toeic_score'를 받는다. 이 매개변수는 TOEIC 점수를 나타낸다.

'RETURNS char(4)' 는 함수가 문자열을 반환할 것임을 명시하는데, 여기서는 4자리 문자열을 의미한다.

NO SQL 은 이 함수가 데이터베이스에 저장된 데이터를 읽거나 변경하지 않음을 나타낸다.

BEGIN END에 본문을 짜는데, 'DECLARE' 문을 사용해서 로컬 변수 'pass_fail' 과 타입을 선언한다.

'IF ... THEN SET ... ELSEIF ... THEN SET ... ELSE SET ... END IF;' 는 입력받은 'toeic_score'의 값에 따라 'pass_fail' 변수에 값을 할당하는 조건문이다.

'RETURN' 문을 사용해 BEGIN END 사이에서 정해진 값 'pass_fail'을 반환한다.

 

 

student table을 만들고 학생들을 테이블에 추가해보자.

CREATE TABLE student
(
id CHAR(9),
name VARCHAR(48) NOT NULL,
major VARCHAR(10),
phone VARCHAR(14),
toeic INTEGER,
gender CHAR(1),
status INTEGER DEFAULT 1
);

 

INSERT INTO student VALUES('20230905','김알루','CS','01030564321',795,'M',1);
INSERT INTO student VALUES('20231102','박민희','MT','01020498472',820,'F',1);
INSERT INTO student VALUES('20220101','유희석','AT','01053242317',835,'M',2);
INSERT INTO student VALUES('20221101','오나미','MC','01023447732',590,'F',2);
INSERT INTO student VALUES('20210141','최예인','BS','01030564323',905,'F',3);
INSERT INTO student VALUES('20200231','이나라','FS','01048146561',690,'F',3);
INSERT INTO student VALUES('20222201','곽시온','PE','01079986542',780,'M',2);
INSERT INTO student VALUES('20210101','조혜연','AB','01096984130',800,'F',3);
INSERT INTO student VALUES('20180101','정태식','AB','01082322615',920,'M',4);
INSERT INTO student VALUES('20171601','김정엽','CS','01014589322',890,'M',4);
INSERT INTO student VALUES('20202301','성시아','PE','01074230331',620,'F',2);
INSERT INTO student VALUES('20185211','마성민','CS','01056208443',770,'M',3);
INSERT INTO student VALUES('20170231','차진교','BS','01022319985',980,'M',4);
INSERT INTO student VALUES('20231794','고하연','MT','01023221124',610,'F',1);
INSERT INTO student VALUES('20232001','여진우','FS','01087989910',950,'M',1);
INSERT INTO student VALUES('20223141','임예린','AT','01087905235',920,'F',2);
INSERT INTO student VALUES('20230665','송현','MC','01042959455',555,'M',1);

STUDENT 테이블

 

 

 

이제 만들었던 toeic_pass_fail 함수를 사용해서 toeic 점수 조건을 충족했는지 여부를 파악해보면 아래와 같다.

SELECT *, toeic_pass_fail(toeic)
FROM STUDENT;

결과

 


이외에 stored function 이 할 수 있는 일

MySQL 에서는 stored function 은

  • 루프를 돌면서 반복적인 작업을 수행하거나
  • case 키워드를 사용해서 값에 따라 분기처리를 하거나
  • 에러를 핸들링하거나 에러를 일으키는 등의 다양한 동작을 정의할 수 있다.

 

stored 함수의 사용은 데이터베이스의 로직을 중앙화하고 표준화하는 데 유용하며, 다양한 상황에서 코드의 재사용성과 효율성을 높일 수 있다. 그러나 복잡한 함수는 데이터베이스 서버에 부하를 줄 수 있으므로, 성능과 리소스 사용을 고려하여 적절히 사용해야 하는 것도 중요하다.


stored function 삭제하기

생성했던 stored 함수를 삭제하고 싶다면, 아래와 같은 구문을 작성하면 된다.

DROP FUNCTION stored_function_name;

 

위에서 toeic_pass_fail 함수를 삭제한다고 하면, DROP FUNCTION toeic_pass_fail; 이라고 할 수 있다.

삭제하고 함수 호출하면 없다고 반환


등록된 stored function 파악하기

보통 실무에 가면, stored function 이 문서나 파일등에 잘 정리되어 있겠지만, 그게 아니라면 직접 파악해야 할 수도 있다.

그럼, 등록된 stored function이 어떤것이 있는지 파악하는 방법에 대해 한 번 알아보자.

.

..

...

가장 쉽게 알 수 있는 방법은 SHOW FUNCTIONS STATUS를 사용하고 WHERE 조건에 DB 이름을 걸어주면 된다.

SHOW FUNCTIONS STATUS WHERE DB = 'company';

 

company 라는 이름의 DB에 어떤 stored_function 이 있는지 파악해보면 아래와 같다. 

company DB가 사용하는 stored function

 

결과의 일부를 캡쳐한 것인데, 우선 현재 company 라는 DB 에는 dept_avg_salary() 와 id_generator() 함수가 있는 것을 알 수 있다.

그럼, school DB를 확인해보자.

SHOW FUNCTIONS STATUS WHERE DB = 'school';

school DB가 사용하는 stored function

 

school DB 에는 toeic_pass_fail() 함수가 있는 것을 알 수 있다.


 

우리가 stored_function을 만들 때, 뒤에 따로 DB 이름을 붙여주지 않으면, 그 stored_function 은 현재 활성화 되어 있는 DB 에 만들어진다. 

아까 예제에서는 각 DB 안에서 함수를 만들었기 때문에, 따로 DB 이름을 뒤에 붙여주지 않았어도 각 해당 DB 내에 stored_function이 잘 작성된 것을 볼 수 있다.

 

만약 현재 DB와는 다른 DB에 함수를 만들어주고 싶다면, CREATE FUNCTION 뒤에 DB 이름을 먼저 적어주고 (.)점을 붙이고 function 이름을 적어주면 된다.

CREATE FUNCTION [DBName].[FunctionName]

 

예를 들어, 아까 company DB 로 이동하지 않고 school DB 안에서 dept_avg_salary 함수를 만든다면, 아래와 같은 형식으로 함수를 생성해야 한다.

delimiter $$
CREATE FUNCTION company.dept_avg_salary(d_id int)
    RETURNS ...
    ...
...$$

 

그럼, 이제 특정 stored_function 이 어떤 함수인지 살펴보려면 어떻게 하면 되는지 알아보자.

예를 들어, 아까 company DB에서의 두 개의 함수 중에 id_generator 라는 함수를 알고 싶다고 해보자.

.

..

...

키워드는 간단하다. 우리가 DATABASES 를 볼 때, show 키워드를 쓰는 것처럼, SHOW 키워드와 함께 CREATE FUNCTION 을 써주고 그 뒤에 원하는 함수이름을 적어주면 된다.

(물론 이 때도, 해당 DB 내에 있는 stored_function 을 확인하고 싶다면, 굳이 DB이름을 적지 않고 바로 FunctionName 만 써줘도 된다.)

SHOW CREATE FUNCTION [DBName].[FunctionName]

SHOW CREATE FUNCTION [FunctionName]

 

company DB가 사용하는 stored function

SHOW CREATE FUNCTION company.id_generator;

 

SHOW CREATE FUNCTION company.id_generator 함수

 

위 사진은 SHOW CREATE FUNCTION company.id_generator 을 했을 때, 일부를 캡처한 것이다.

중간 부분을 보면, CREATE FUNCTION 부분이 있고, 이 함수가 어떻게 선언되어 있는지 볼 수 있다.

CREATE DEFINER

 

CREATE DEFINER 라는 정보도 나와있는데, 이는 이 함수를 정의한 사람에 대한 정보이다. 우리는 함수를 작성할 때, 따로 명시하지 않았기 때문에 디폴트 값인 localhost 가 들어가 있는 것을 확인할 수 있다. (=root 계정으로 localhost 가 작성했다)

 

💡위 내용은 MySQL 기준이다. 다른 RDBMS 의 stored_function 에 관한 문법이 조금씩 다를 수 있다.


stored function 은 언제 써야 할까?

 

💡로마에 가면 로마법을 따르라

 

아래 내용은 개인적인 생각이다. 실무에 가면, stored function 에 대해 어떻게 쓸건지에 대해서는 회사사람들끼리 내부적으로 고민과 회의를 거쳐 결정했을 것이므로, 그대로 내부규약을 따르는게 좋다.(생각이 달라지면 팀 안에서 회의를 또 하면 된다.)

 

💡Three - tier - architecture

 

우선, 대부분의 IT 회사는 three-tier-architecture 로 나뉘어져 있고 이걸 따른다.

Three - tier - architecture

 

 

💡그건 그렇고, 그럼 stored function 은 언제 써야 할까?

(유튜브에서는 개인적인 의견이 포함되어 있음)

 

util 함수로 쓰기에는 괜찮을 것 같다.

비즈니스 로직을 stored function 에 두는 것은 좋지 않을 것 같다.

(왜냐하면, 비즈니스 로직은 Logic Tier 에서 담당하고 있는 것인데, 비즈니스 로직의 일부를 stored function 에 두게 되면 로직의 일부가 Data Tier 에 있게 되는 셈이다.)

stored function 은 Logic Tier에 두지 않는 것이 좋을 것 같다.

 

그럼, 원래 비즈니스 로직이 Logic Tier에 있었다가, 일부는 Data Tier 에 있게 되는 것이므로, 서비스의 규모가 커지면 커질수록, 관리 비용이 점점 더 늘어날 것이다. 양쪽을 다 봐야 하는 상황이 발생한다!

 

 

💡 그럼, 우리가 이번 장에서 생성한 stored function을 각각 판별해보자.

stored function 비즈니스 로직을 가지는가?
dept_avg_salary X
id_generator
toeic_pass_fail O

 

dept_avg_salary : 단순하게 계산을 편하게 하기 위한 함수였다.

id_generator : 얘를 로직으로 보기에는 애매하다. 로직으로 보기에는 너무 간단하기도 하고, 중요해보이지도 않지만, 한편으로는 1로 시작하는 10자리 정수라고 정책을 정했기 때문에 비즈니스 로직을 가지는 것이 아닐까도 싶다.

toeic_pass_fail : 비즈니스 로직을 가진다고 생각한다. 매우 중요해보이기도 하고, 800 이라는 점수 자체가 바뀔 수도 있는 부분이기 서비스의 방향이 달라질 수 있다. → 그래서 이런 부분은 stored_function 으로 만들기보다는 logic-tier 에서(소스코드 상에서) 구현하는 것이 더 낫지 않을까 싶다.


참고자료

- 유튜브 쉬운코드

반응형