수업 내용 정리
(2주차 12일) 인덱스, 프로시저, 트리거
헨헨7
2024. 5. 24. 17:04
DB modeling
> 인덱스(Index)
- 테이블의 열 단위에 생성되는 데이터베이스의 개체로 데이터를 조회할 때 빠르게 접근하도록 도와주는 역할
- 클러스터형 인덱스(Clusterd Index): 테이블당 한 개만 생성 가능, 데이터가 정렬되어 저장 -> 검색 속도 빠름, 데이터 저장 느림
* 기본 키 제약조건으로 지정되는 열에 자동으로 생성됨 - 보조 인덱스(Secondary Index): 테이블당 여러 개 생성 가능, 정렬 필요 X -> 검색 속도 느림, 데이터 저장 빠름
* UNIQUE 제약 조건으로 지정되는 열에 자동으로 생성됨
- 클러스터형 인덱스(Clusterd Index): 테이블당 한 개만 생성 가능, 데이터가 정렬되어 저장 -> 검색 속도 빠름, 데이터 저장 느림
- B-트리(B-tree)
- 이진 트리를 확장해 하나의 노드가 가질 수 있는 자식 노드의 최대 숫자가 2보다 큰 트리 구조
- 노드에 저장된 KEY들은 항상 정렬된 상태로 저장됨
/*
* 인덱스(Index)
*/
SELECT * FROM employees;
EXPLAIN SELECT * FROM employees;
SELECT * FROM employees;
EXPLAIN SELECT * FROM employees WHERE emp_no = 499886;
CREATE INDEX idx_employees_first_name ON employees(first_name);
CREATE INDEX idx_employees_first_last_name ON employees(first_name, last_name);
SELECT * FROM employees;
EXPLAIN SELECT * FROM employees WHERE first_name = 'moon' AND last_name = 'Yetto';
ANALYZE TABLE employees;
-- #테이블에 지정된 인덱스 확인
SHOW INDEX FROM employees;
-- 인덱스 삭제
DROP INDEX idx_employees_first_name ON employees;
DROP INDEX idx_employees_first_last_name ON employees;
-- 인덱스 여부에 따라 쿼리 실행 시간에 유의미한 차이
CREATE TABLE emp_copy ( SELECT * FROM employees );
SELECT * FROM employees WHERE emp_no = 100000;
SELECT * FROM emp_copy WHERE emp_no = 100000;
EXPLAIN SELECT * FROM employees WHERE emp_no = 100000;
EXPLAIN SELECT * FROM emp_copy WHERE emp_no = 100000;
EXPLAIN SELECT * FROM employees WHERE emp_no < 11000;
EXPLAIN SELECT * FROM emp_copy WHERE emp_no < 11000;
-- #인덱스를 사용하지 않는 경우: 데이터 범위의 약 절반 이상 검색하는 경우
SELECT * FROM employees;
SELECT * FROM emp_copy;
EXPLAIN SELECT * FROM employees;
EXPLAIN SELECT * FROM emp_copy;
EXPLAIN SELECT * FROM employees WHERE emp_no < 400000;
EXPLAIN SELECT * FROM emp_copy WHERE emp_no < 400000;
-- #인덱스로 지정된 열 자체에 연산이 적용되는 경우 인덱스로 검색하지 않음
EXPLAIN SELECT * FROM employees WHERE emp_no * 1 = 100000;
EXPLAIN SELECT * FROM emp_copy WHERE emp_no * 1 = 100000;
-- 인덱스로 지정된 열의 검색값에 연산이 적용되는 경우 정상적으로 인덱스를 사용함
EXPLAIN SELECT * FROM employees WHERE emp_no = 100000 * 1;
EXPLAIN SELECT * FROM emp_copy WHERE emp_no = 100000 * 1;
-- #중복되는 열이 다량 있는 경우, 인덱스로 검색하지 않음
ALTER TABLE employees ADD INDEX idx_employees_gender (gender);
ANALYZE TABLE employees;
SHOW INDEX FROM employees;
SELECT * FROM employees WHERE gender = 'M';
SELECT * FROM emp_copy WHERE gender = 'M';
EXPLAIN SELECT * FROM employees WHERE gender = 'M';
EXPLAIN SELECT * FROM emp_copy WHERE gender = 'M';
ALTER TABLE employees DROP INDEX idx_employees_gender;
> 스토어드 프로시저(Stored Procedure)
- MariaDB에서 제공되는 프로그래밍 기능으로, 쿼리문을 다시 입력할 필요 없이 호출해서 실행 결과를 얻을 수 있음
/*
* 스토어드 프로시저(Stored Procedure)
*/
-- #프로시저 생성
DELIMITER $$
CREATE PROCEDURE userProc()
BEGIN
SELECT * FROM usertbl;
END$$
DELIMITER ;
CALL userProc();
-- #매개변수 사용
DELIMITER $$
CREATE OR REPLACE PROCEDURE userProc (
IN userName VARCHAR(10)
)
BEGIN
SELECT * FROM usertbl WHERE NAME = userName;
END$$
DELIMITER ;
CALL userProc('조관우');
-- e.g. 지역, 전화번호 앞자리 3자를 입력받아 usertbl 데이터를 반환
DELIMITER $$
CREATE OR REPLACE PROCEDURE userProc (
IN address VARCHAR(10), mobileFront VARCHAR(10)
)
BEGIN
SELECT * FROM usertbl WHERE address = addr and mobile1 = mobileFront;
END$$
DELIMITER ;
CALL userProc('경기', '018');
-- e.g. 사용자의 아이디를 입력받아 이름을 userName 변수에 반환
DELIMITER $$
CREATE OR REPLACE PROCEDURE userProc (
IN id CHAR(8),
OUT userName CHAR(20)
)
BEGIN
SELECT NAME INTO userName
FROM usertbl WHERE userid = id;
END$$
DELIMITER ;
CALL userProc('JYP', @NAME);
SELECT @NAME;
-- #제어문
-- IF
DELIMITER $$
CREATE OR REPLACE PROCEDURE empProc (
IN id CHAR(3)
)
BEGIN
DECLARE YEAR INT;
SELECT YEAR(hire_Date) INTO year
FROM employee WHERE emp_id = id;
IF YEAR >= 2010 THEN
SELECT '2010년도에 입사하셨네요!';
ELSEIF YEAR > 2000 THEN
SELECT '2000년도에 입사하셨네요!';
ELSE
SELECT '1900년도에 입사하셨네요!';
END IF;
END$$
DELIMITER ;
CALL empProc('215');
-- CASE
DELIMITER $$
CREATE OR REPLACE PROCEDURE gradeProc (
IN score INT
)
BEGIN
DECLARE grade CHAR(1);
CASE
WHEN score >= 90 THEN
SET grade = 'A';
WHEN score >= 80 THEN
SET grade = 'B';
WHEN score >= 70 THEN
SET grade = 'C';
WHEN score >= 60 THEN
SET grade = 'D';
ELSE
SET grade = 'F';
END CASE;
SELECT score AS '점수', grade AS '등급';
END$$
DELIMITER ;
CALL gradeProc(80);
DELIMITER $$
CREATE OR REPLACE PROCEDURE gradeProc (
IN score INT
)
BEGIN
DECLARE grade CHAR(1);
IF score >= 90 THEN
SET grade = 'A';
ELSEIF score >= 80 THEN
SET grade = 'B';
ELSEIF score >= 70 THEN
SET grade = 'C';
ELSEIF score >= 60 THEN
SET grade = 'D';
ELSE
SET grade = 'F';
END IF;
SELECT score AS '점수', grade AS '등급';
END$$
DELIMITER ;
CALL gradeProc(65);
-- #반복문
-- WHILE
DELIMITER $$
CREATE OR REPLACE PROCEDURE sumProc ()
BEGIN
DECLARE i INT;
DECLARE SUM INT;
SET i = 1;
SET SUM = 0;
WHILE (i <= 10) DO
SET SUM = SUM + i;
SET i = i + 1;
END WHILE;
SELECT SUM;
END$$
DELIMITER ;
CALL sumProc();
-- e.g. 구구단 출력
DELIMITER $$
CREATE OR REPLACE PROCEDURE multiProc(
IN num INT
)
BEGIN
DECLARE i INT;
DECLARE result VARCHAR(100);
SET i = 1;
SET result = '';
WHILE(i < 10) DO
SET result = CONCAT(result, num, ' * ', i, ' = ', num * 1, '\n');
SET i = i + 1;
END WHILE;
SELECT result;
END$$
DELIMITER ;
CALL multiProc(2);
-- 오류처리 테스트
DELIMITER $$
CREATE OR REPLACE PROCEDURE errorProc()
BEGIN
DECLARE CONTINUE handler FOR 1146 SELECT '테이블이 존재하지 않습니다.'
AS '메시지'; SELECT * FROM noTable;
END$$
DELIMITER ;
CALL errorProc();
DROP PROCEDURE empProc;
> 트리거(Trigger)
- 테이블에 삽입, 수정, 삭제 등의 작업이 발생할 때 자동으로 작동되는 개체
- AFTER 트리거
- BEFORE 트리거
/*
* 트리거
*/
-- 트리거 e.g.
-- 상품 정보 테이블 생성
CREATE TABLE tb_product (
pcode INT AUTO_INCREMENT PRIMARY KEY,
pname VARCHAR(100),
brand VARCHAR(100),
price INT,
stock INT DEFAULT 0
);
INSERT INTO tb_product (pname, brand, price)
VALUES ('아이폰12미니', '애플', 950000);
INSERT INTO tb_product (pname, brand, price)
VALUES ('갤럭시23', '삼성', 1000000);
-- 2. 상품 입/출고 상세 이력 테이블 생성
CREATE TABLE tb_prodetail (
dcode INT AUTO_INCREMENT PRIMARY KEY,
pcode INT REFERENCES tb_product,
STATUS VARCHAR(2) CHECK(STATUS IN ('입고', '출고')),
amount INT,
ddate DATE DEFAULT CURDATE()
);
-- 1번 상품이 2024-05-01 날짜로 10개 입고
INSERT INTO tb_prodetail(pcode, STATUS, amount, ddate)
VALUES (1, '입고', 10, '2024-05-01');
-- 1번 상품의 재고 수량도 변경되어야 한다.
UPDATE tb_product SET stock = stock + 10
WHERE pcode = 1;
-- 1번 상품이 2024-05-02 날짜로 5개 출고
INSERT INTO tb_prodetail(pcode, STATUS, amount, ddate)
VALUES (1, '출고', 5, '2024-05-02');
UPDATE tb_product SET stock = stock - 5
WHERE pcode = 1;
-- 2번 상품이 2024-05-06 날짜로 20개 입고
INSERT INTO tb_prodetail(pcode, STATUS, amount, ddate)
VALUES (2, '입고', 20, '2024-05-06');
-- 2번 상품의 재고 수량도 변경되어야 한다.
UPDATE tb_product SET stock = stock + 20
WHERE pcode = 2;
-- #tb_prodetail 테이블에 데이터 삽입 시 tb_product 테이블에 재고 수량이
-- 자동으로 업데이트 되도록 트리거를 생성
DELIMITER $$
CREATE OR REPLACE TRIGGER trg_pro_stock
AFTER INSERT ON tb_prodetail
FOR EACH ROW
BEGIN
-- 상품이 입고된 경우(재고 증가)
IF NEW.status = '입고' THEN
UPDATE tb_product
SET stock = stock + NEW.amount
WHERE pcode = NEW.pcode;
END IF;
-- 상품이 출고된 경우(재고 감소)
IF NEW.status = '출고' THEN
UPDATE tb_product
SET stock = stock - NEW.amount
WHERE pcode = NEW.pcode;
END IF;
END$$
DELIMITER ;
-- 2번 상품이 2024-05-07 날짜로 20개 입고
INSERT INTO tb_prodetail(pcode, STATUS, amount, ddate)
VALUES (2, '입고', 20, '2024-05-07');
SELECT * FROM tb_prodetail;
SELECT * FROM tb_product;
-- 2번 상품이 2024-05-07 날짜로 30개 출고
INSERT INTO tb_prodetail(pcode, STATUS, amount, ddate)
VALUES (2, '출고', 30, '2024-05-07');
SELECT * FROM tb_prodetail;
SELECT * FROM tb_product;
-- 1번 상품이 오늘 날짜로 100개 입고
INSERT INTO tb_prodetail(pcode, STATUS, amount, ddate)
VALUES (1, '입고', 100, DEFAULT);
SELECT * FROM tb_prodetail;
SELECT * FROM tb_product;
-- 트리거 삭제
DROP TRIGGER trg_pro_stock;
DROP TABLE tb_prodetail, tb_product;