본문 바로가기
DB & SQL/이것이 MySQL이다

스토어드 프로그램 (2)

by k-mozzi 2022. 6. 25.
반응형
Preface

 

이번 장에선 스토어드 함수, 커서, 트리거 등 다양한 스토어드 프로그램에 대해 공부했다.

 

내용 자체가 많진 않았지만, 직접 코드를 작성하며 이해해야 할 부분이 많아서 그런지 3~4시간이 훌쩍 갔다.

 

처음 BEFORE 트리거의 설명을 봤을 땐 어떤 상황에서 사용하는 트리거인지 도통 감이 오지 않았는데, 예시를 본 후 직접 코드를 짜니 금방 이해가 됐다.

 

또, 중첩 트리거를 공부하며 코딩은 차라리 지식이 얕을 때가 훨씬 간단하고 쉬운 것 같다는 생각을 했다.

 

이런저런 내용을 배우면 배울수록 코드를 짤 때 고려해야 할 부분이 많아지는 것 같다.

 

그래도 오늘은 오랜만에 코딩 공부를 처음 시작했을 때 처럼 시간 가는 줄 모르고 재미있게 공부했다.


 

2. 스토어드 함수

 

 

- 스토어드 함수: 사용자가 직접 만들어서 사용하는 함수

 

 

- 스토어드 함수와 스토어드 프로시저의 차이

1) 스토어드 함수는 in, out 등을 사용할 수 없고, 모두 입력 파라미터로 사용된다.

2) 스토어드 함수는 returns문으로 반환할 값의 데이터 형식을 지정하고, 본문 안에서는 return문으로 하나의 값을 반환해야 한다.

→ 스토어드 프로시저는 별도의 반환하는 구문이 없다.

3) 스토어드 함수는 select 문장 안에서 호출된다.

4) 스토어드 함수 안에서는 집합 결과를 반환하는 select를 사용할 수 없다.

→ select~into~는 집합 결과를 반환하는 것이 아니므로 사용할 수 있다.

5) 스토어드 프로시저는 여러 SQL문이나 숫자 계산 등 다양한 용도로 사용되지만, 스토어드 함수는 어떤 계산을 통해 하나의 값을 반환하는데 주로 사용된다.

 

 

※ 스토어드 함수를 사용하기 위해서는 먼저 스토어드 함수 생성 권한을 허용해줘야 한다.

set global log_bin_trust_function_creators = 1;

 

 

- 2개 숫자의 합을 계산하는 스토어드 함수

use sqldb;
drop function if exists userFunc;
delimiter $$
create function userFunc(value1 int, value2 int)
	returns int
begin
	return value1 + value2;
end $$
delimiter ;

select userFunc(100, 200);

 

 

- 출생년도를 입력하면 나이가 출력되는 함수

drop function if exists getAgeFunc;
delimiter $$
create function getAgeFunc(bYear int)
	returns int
begin
	declare age int;
    set age = year(curdate()) - bYear;
    return age;
end $$
delimiter ;

select getAgeFunc(1979) into @age1979;
select getAgeFunc(1989) into @age1989;
select concat('1989년과 1979년의 나이차 ==> ', (@age1989 - @age1979));

 

 

- 스토어드 함수의 내용을 확인하는 방법

show create function getAgeFunc;

 

 

- 스토어드 함수의 삭제는 다른 DB 개체와 마찬가지로 drop문을 사용한다.

 


 

3. 커서

 

 

- MySQL은 스토어드 프로시저 내부에 커서를 사용할 수 있다.

 

 

- 커서(cursor): 인덱스를 지정한 변수

 

 

- 커서의 처리 순서

1) 커서의 선언: declare cursor

2) 반복 조건 선언: declare continue handler

3) 커서 열기: open

4) 커서에서 데이터 가져오기: fetch

5) 데이터 처리: loop~end loop문으로 반복 구간 지정

6) 커서 닫기: close

 

 

- 커서를 활용하여 고객의 평균 키를 구하는 스토어드 프로시저

use sqldb;
drop procedure if exists cursorProc;
delimiter $$
create procedure cursorProc()
begin
	declare userHeight int;
    declare cnt int default 0;	-- 고객의 인원 수(=읽은 행의 수)
    declare totalHeight int default 0;	-- 키의 합계
    
    declare endOfRow boolean default false;	-- 행의 끝 여부 기본은 false
    
    declare userCursor cursor for	-- 커서 선언
		select height from userTbl;
        
	declare continue handler	-- 행의 끝이면 endOfRow 변수에 true를 대입
		for not found set endOfRow = true;
		
	open userCursor;	-- 커서 열기
    
    cursor_loop: loop
		fetch userCursor into userHeight;	-- 고객 키 1개를 대입
        
        if endOfRow then	-- 더이상 읽을 행이 없으면 loop를 종료
			leave cursor_loop;
		end if;
        
        set cnt = cnt + 1;
        set totalHeight = totalHeight + userHeight;
	end loop cursor_loop;
    
    -- 고객 키의 평균을 출력한다.
    select concat('고객 키의 평균 ==> ', (totalHeight/cnt));
    
    close userCursor;	-- 커서 닫기
end $$
delimiter ;

call cursorProc();

 

 

- 고객의 구매 총액에 따라 등급 값을 입력하는 스토어드 프로시저

drop procedure if exists gradeProc;
delimiter $$
create procedure gradeProc()
begin
	declare id varchar(10);	-- 사용자 아이디를 저장할 변수
    declare hap bigint;	-- 총 구매액을 저장할 변수
    declare userGrade char(5);
    
    declare endOfRow boolean default false;
    
    declare userCursor cursor for
		select U.userid, sum(price * amount)
			from buyTbl B
				right outer join userTbl U
                on B.userid = U.userid
			group by U.userid, U.username;
            
	declare continue handler
		for not found set endOfRow = true;
        
	open userCursor;
    grade_loop: loop
		fetch userCursor into id, hap;	-- 첫 행 값을 대입
        if endOfRow then
			leave grade_loop;
		end if;
        
        case
			when (hap >= 1500) then set userGrade = '최우수고객';
			when (hap >= 1000) then set userGrade = '우수고객';
			when (hap >= 1) then set userGrade = '일반고객';
			else set userGrade = '유령고객';
		end case;
        
        update userTbl set grade = userGrade where userID = id;
	end loop grade_loop;
    
    close userCursor;
end $$
delimiter ;

call gradeProc();
select * from userTbl;

 


 

4. 트리거

 

 

- 트리거(trigger): 테이블에 무슨 일이 일어나면 자동으로 실행되는 DB 개체

1) MySQL은 View에 트리거를 부착할 수 없다.

2) in, out 매개 변수를 사용할 수 없다.

3) 삭제는 drop trigger를 사용하면 된다.

4) alter trigger문을 사용할 수 없다.

 

 

- 트리거 사용을 위한 테이블

create database if not exists testDB;
use testDB;
create table if not exists testTbl (id int, txt varchar(10));
insert into testTbl values(1, '레드벨벳');
insert into testTbl values(2, '잇지');
insert into testTbl values(3, '블랙핑크');

 

 

 

- 테이블에 트리거를 부착한 후 데이터를 삽입, 수정, 삭제하는 과정

drop trigger if exists testTrg;
delimiter $$
create trigger testTrg
	after delete	-- 삭제 후에 작동하도록 지정
    on testTbl	-- 트리거를 부착할 테이블
    for each row	-- 각 행마다 적용시킴
begin
	set @msg = '가수 그룹이 삭제됨';	-- 트리거 실행 시 작동되는 코드들
end $$
delimiter ;


set @msg = '';
insert into testTbl values(4, '마마무');
select @msg;
update testTbl set txt = '블핑' where id = 3;
select @msg;
delete from testTbl where id = 4;
select @msg;
select * from testTbl;

 

 

- 트리거의 작동 순서(본체 바로 위에 옵션 지정)

1) FOLLOWS 트리거이름: 지정한 트리거 다음에 현재 트리거가 작동

2) PRECEDES 트리거이름: 지정한 트리거가 작동하기 이전에 현재 트리거가 작동

 

 

- 트리거의 종류

1) AFTER 트리거: 테이블에 insert, update, delete 등의 작업이 일어난 후에 작동하는 트리거

2) BEFORE 트리거: insert, update, delete가 발생하기 전에 작동하는 트리거

 

 

- AFTER 트리거의 사용


1. 변경되기 전의 데이터를 저장할 테이블 생성

use sqlDB;
drop table if exists buytbl;
create table backup_userTbl
( userID	char(8) not null primary key,
  username	varchar(10) not null,
  birthYear	int not null,
  addr	char(2) not null,
  mobile1	char(3),
  monile2	char(8),
  height	smallint,
  mDate	date,
  modType	char(2),	-- 변경된 타입. 수정 또는 삭제
  modDate	date,	-- 변경된 날짜
  modUser	varchar(256)	-- 변경한 사용자
);

 

 

2. 변경과 삭제가 발생했을 때 작동하는 트리거 생성

drop trigger if exists backUserTbl_UpdateTrg;
delimiter $$
create trigger backUserTbl_UpdateTrg
	after update
    on userTBL
    for each row
begin
	insert into backup_userTbl values( old.userID, old.username, old.birthYear,
		old.addr, old.mobile1, old.mobile2, old.height, old.mDate,
        '수정', curdate(), current_user() );
end $$
delimiter ;


drop trigger if exists backUserTbl_DeleteTrg;
delimiter $$
create trigger backUserTbl_DeleteTrg
	after delete
    on userTBL
    for each row
begin
	insert into backup_userTbl values( old.userID, old.username, old.birthYear,
		old.addr, old.mobile1, old.mobile2, old.height, old.mDate,
        '삭제', curdate(), current_user() );
end $$
delimiter ;

 

 

3. 데이터를 변경한 후 결과를 확인하는 과정

update userTbl set addr = '몽고' where userID = 'JKW';
delete from userTbl where height >= 177;

select * from backup_userTbl;

 

- delete 트리거는 delete문에만 작동하고, truncate table문에는 작동하지 않는다.

 

 

- 테이블에 새로운 데이터가 입력되지 못하도록 설정하고, 누군가 수정이나 삭제를 시도할 경우 경고 메시지를 출력하는 트리거

drop trigger if exists userTbl_InsertTrg;
delimiter $$
create trigger userTbl_InsertTrg
	after insert
    on userTbl
    for each row
begin
	signal sqlstate '45000'
		set message_text = '데이터의 입력을 시도했습니다. 귀하의 정보가 서버에 기록되었습니다.';
end $$
delimiter ;

signal sqlstate '45000'문은 사용자가 오류를 강제로 발생시키는 함수이다.

→ 사용자가 정의한 오류 메시지를 출력하고, 사용자가 시도한 insert는 롤백이 되어 테이블에 적용되지 않는다.

 

 

- 트리거가 생성하는 임시 테이블: 트리거에서 insert, update, delete 작업이 수행되면 임시로 사용되는 테이블

1) NEW 테이블: insert와 update 작업 시에 변경할 새로운 데이터를 잠시 저장하는 테이블

2) OLD 테이블: delete와 update 작업이 수행되면, 삭제 또는 변경되기 전의 예전 값을 저장하는 테이블

 

→ 트리거의 작동 시에 새로 입력/변경되는 새로운 데이터를 참조하려면 NEW 테이블을, 변경되기 전인 예전 데이터를 참조하려면 OLD 테이블을 참조하면 된다.

 

 

- BEFORE 트리거의 사용 예시: 입력될 데이터 값을 미리 확인해서 문제가 있을 경우 다른 값으로 변경

 

 

- 잘못 입력한 출생년도 값을 다른 값으로 정하는 BEFORE 트리거

use sqldb;
drop trigger if exists userTbl_BeforeInsertTrg;
delimiter $$
create trigger userTbl_BeforeInsertTrg
	before insert
    on userTBL
    for each row
begin
	if new.birthYear < 1900 then
		set new.birthYear = 0;
	elseif new.birthYear > year(curdate()) then
		set new.birthYear = year(curdate());
	end if;
end $$
delimiter ;


insert into userTbl values
	('AAA', '에이', 1877, '서울', '011', '1112222', 181, '2022-12-25');
select * from usertbl;

 

 

- DB에 생성된 트리거를 확인하는 방법

show triggers from 데이터베이스_이름;

 

 

- 다중 트리거: 하나의 테이블에 동일한 트리거가 여러 개 부착되어 있는 것

→ after insert 트리거가 한 테이블에 2개 이상 부착되어 있는 경우

 

 

- 중첩 트리거: 트리거가 또 다른 트리거를 작동시키는 것

→ 시스템 성능에 좋지 않은 영향을 미칠 수 있다. (마지막 작업이 실패하면 앞의 작업도 롤백됨)

 

 

- 중첩 트리거의 작동


1. 연습용 DB 생성한 후 테이블 생성

drop database if exists triggerDB;
create database if not exists triggerDB;

use triggerDB;
create table orderTbl
	(orderNo int auto_increment primary key,
     userID varchar(5),
     prodName varchar(5),
     orderamount int);
create table prodTbl
	(prodName varchar(5),
     account	int);	-- 남은 수량
create table deliverTbl
	(deliverNo int auto_increment primary key,
     prodName varchar(5),
     account int);	-- 배송할 물건 개수
     
insert into prodtbl values('사과', 100);
insert into prodtbl values('배', 100);
insert into prodtbl values('귤', 100);

 

 

2. 중첩 트리거 생성

-- 물품 테이블에서 개수를 감소시키는 트리거
drop trigger if exists orderTrg;
delimiter $$
create trigger orderTrg
	after insert
    on orderTbl
    for each row
begin
	update prodTbl set account = account- new.orderamount
		where prodName = new.prodName;
end $$
delimiter ;

-- 배송 테이블에 새 배송 건을 입력하는 트리거
drop trigger if exists prodTrg;
delimiter $$
create trigger prodTrg
	after update
    on prodTbl
    for each row
begin
	declare orderAmount int;
    -- 주문 개수 = (변경 전의 개수 - 변경 후의 개수)
    set orderAmount = old.account - new.account;
    insert into deliverTbl(prodName, account)
		values(new.prodName, orderAmount);
end $$
delimiter ;

 

 

3. 값을 입력한 후 중첩 트리거가 잘 작동했는지 확인하는 과정

insert into orderTbl values(null, 'JOHN', '배', 4);

select * from orderTbl;
select * from prodTbl;
select * from deliverTbl;

 

728x90
반응형

'DB & SQL > 이것이 MySQL이다' 카테고리의 다른 글

MySQL과 공간 데이터  (0) 2022.06.27
전체 텍스트 검색과 파티션  (0) 2022.06.26
스토어드 프로그램 (1)  (0) 2022.06.23
DB 및 테이블의 정보를 출력하는 명령어  (0) 2022.05.29
인덱스 (2)  (0) 2022.05.29

댓글