5장~7장
05. 테이블과 뷰
테이블 만들기
테이블: 표 형태의 2차원 구조
행: row, record
열: column, field
테이블 설계: 열 이름, 데이터 형식, 널 허용 여부(NN), 기타(PK, FK, 자동 증가 옵션(AI), unsigned 등)
외래키로 설정한 칼럼을 작성하기 위해서는 매핑된 다른 테이블의 기본키 칼럼을 작성해야 한다.
일대다 관계에서 '다'는 '일'이 있어야 데이터 입력 가능
-- DROP DATABASE IF EXISTS sample_db;
CREATE DATABASE sample_db;
USE sample_db;
-- DROP TABLE IF EXISTS member;
CREATE TABLE member
( mem_id CHAR(8) NOT NULL PRIMARY KEY,
mem_name VARCHAR(10) NOT NULL,
addr CHAR(2) NOT NULL,
phone1 CHAR(3) NULL,
phone2 CHAR(8) NULL,
height TINYINT UNSIGNED NULL,
debut_date DATE NULL
);
CREATE TABLE buy
( num INT AUTO_INCREMENT NOT NULL PRIMARY KEY,
mem_id CHAR(8),
prod_name CHAR(6) NOT NULL,
price INT UNSIGNED NOT NULL,
amount SMALLINT UNSIGNED NOT NULL,
FOREIGN KEY(mem_id) REFERENCES member(mem_id)
);
`FOREIGN KEY(외래키_칼럼) REFERENCES 연관_테이블(기본키_칼럼)`
테이블 제약조건
기본 키, 외래 키, 고유 키, 체크, 기본값, NOT NULL 등
제약조건은 데이터의 무결성을 보장한다.
기본 키 제약조건
- 수많은 행 데이터 중에 데이터를 구분할 수 있는 식별자.
- 기본 키의 조건: 다른 행과 중복 값X, NOT NULL
- 테이블마다 기본 키는 1개만 가질 수 있다. (단 하나의 칼럼만 지정)
- 기본 키는 클러스터형 인덱스를 자동 생성한다.
기본 키 지정 방법
테이블 생성시
1. 칼럼에 PRIMARY KEY 옵션 지정
2. PRIMARY KEY(칼럼명) 명시
3. 위 방법을 사용하지 않고 테이블 생성 후
ALTER TABLE ``` ADD CONSTRAINT PRIMARY KEY(```)
외래 키 제약조건
- 두 테이블을 연결하는 역할
- 외래 키가 설정된 열은 다른 테이블의 기본 키와 연결된다.
- 기본 키 테이블: 기준 테이블(일), 외래 키 테이블: 참조 테이블(다)
- 외래 키와 기본 키의 이름은 똑같이 쓰는 것을 권장
외래 키 지정 방법
테이블 생성시
1. FOREIGN KEY(외래키_칼럼) REFERENCES 연관_테이블(기본키_칼럼)
2. 위 방법을 사용하지 않고 테이블 생성 후
ALTER TABLE ``` ADD CONSTRAINT FOREIGN KEY(```) REFERENCES ```(```)
ON UPDATE CASCADE, ON DELETE CASCADE
외래 키와 연결된 기본 키는 데이터의 무결성을 보장하기 위해 수정 및 삭제가 불가하다.
ON UPDATE CASCADE: 기본 키 데이터가 수정되면 외래 키도 똑같이 수정되는 기능
ON DELETE CASCADE: 기본 키 데이터가 삭제되면 외래 키도 똑같이 삭제되는 기능
ALTER TABLE ```
ADD CONSTRAINT FOREIGN KEY(```) REFERENCES ```(```)
ON UPDATE CASCADE
ON DELETE CASCADE;
*물론 테이블을 생성할 때도 설정 가능
고유 키 제약조건 (UNIQUE)
- 중복 불가, NULL 허용
- 기본 키는 아니지만 중복을 허용하지 않는 값에 설정
체크 제약조건 (CHECK)
- 입력 데이터를 제한, 점검
- CHECK( 조건 )
CREATE TABLE member(
...,
height TINYINT UNSIGNED NULL CHECK(height >= 100),
phone1 CHAR(3) NULL CHECK(phone1 IN ('02', '031', '032', '054', '055', '061'))
);
ALTER TABLE member
ADD CONSTRAINT
CHECK(phone1 IN ('02', '031', '032', '054', '055', '061'));
기본값(DEFAULT)
- 값을 지정하지 않았을 때 자동으로 입력될 값
- 값을 지정하지 않았다는 것은 명시적 NULL과는 다르다. 이경우 기본값이 있으면 기본값이 입력되고, 기본값이 없으면 NULL이 입력된다.
-- 기본값 지정
CREATE TABLE member(
...,
height TINYINT UNSIGNED NULL DEFAULT 160,
phone1 CHAR(3) NULL
);
ALTER TABLE member
ALTER COLUMN phone1 SET DEFALUT '02';
-- 기본값 입력
INSERT INTO member VALUES (..., default, default);
/* QUERY 1 */
INSERT INTO member (height) VALUES (NULL)
/* QUERY 2 */
INSERT INTO member (height) VALUES ()
QUERY 1 : NULL 입력
QUERY 2 : DEFAULT 가 있다면, DEFAULT 입력 (없다면 NULL 값이 넘어감)
* NOT NULL, DEFAULT *
- 값 지정 안함: DEFAULT 활성화
- NULL 직접 입력: NOT NULL 활성화 -> 오류
가상의 테이블: 뷰
- 사용자 입장에서는 테이블과 거의 동일한 개체처럼 사용할 수 있다.
- 뷰는 테이블처럼 데이터를 가지고 있지 않다.
- 뷰의 실체는 SELECT 문으로 만들어져 있기 때문에 뷰에 접근하는 순간 SELECT 문이 실행되고 그 결과가 화면에 출력된다. (마치 테이블을 출력하기 위해 우리가 직접 SELECT 문을 작성하는 것처럼)
- 단순 뷰: 하나의 테이블과 연관된 뷰
- 복합 뷰: 여러 테이블과 연관된 뷰
뷰 생성
CREATE VIEW 뷰_이름
AS
SELECT 문;
뷰 사용
SELECT 문에 테이블 대신 뷰 이름을 넣고 실행
=> 뷰의 SELECT 문 실행
뷰를 사용하는 이유
보안
클라이언트가 요청한 데이터(칼럼)만 포함하여 불필요한 정보를 노출시키지 않을 수 있다.
실제 테이블에 접근하지 않기 때문에 생기는 이점이다.
SQL 간결화
자주 사용하는 쿼리를 뷰로 생성해두고 필요할 때 사용할 수 있다.
뷰의 실제 작동
뷰의 실제 생성, 수정, 삭제
- 뷰를 생성하면서 뷰에서 사용될 열 이름을 테이블과 다르게 지정할 수 있다 -> 별칭 사용
CREATE VIEW v_viewtest
AS
SELECT B.mem_id 'Member ID',
M.mem_name AS 'Member Name',
B.prod_name "Product Name",
CONCAT(M.phone1, M.phone2) AS "Office Phone"
FROM buy B
INNER JOIN member M
ON B.mem_id = M.mem_id;
SELECT DISTINCT `Member ID`, `Member Name` FROM v_viewtest; -- 백틱(`) 사용
단, 뷰를 조회할 때 열 이름(별칭)에 공백이 있으면 백틱(`)으로 묶어야 한다.
- DESCRIBE 로 뷰의 정보를 확인할 때 실제 테이블의 PK 여부는 확인할 수 없다.
- 뷰를 통한 데이터의 수정, 삭제가 가능하다.
- 뷰를 통한 데이터 입력은 NOT NULL 조건에 따라 될 수도 안 될 수도 있다. 즉, 권장하지 않는다.
- 뷰가 참조하고 있는 테이블이 삭제되면 뷰도 사용할 수 없다. ( CHECK TABLE view_name; 으로 조회되지 않는 이유 확인 가능 )
06. 인덱스
인덱스의 개념
데이터를 빠르게 찾을 수 있도록 도와주는 도구
반드시 필요한 도구는 아니지만 데이터 양이 많을 경우 인덱스는 선택 아닌 필수
장점
- SELECT 문으로 검색하는 속도가 매우 빨라진다.
- 그 결과 컴퓨터의 부담이 줄어들어 결국 전체 시스템의 성능이 향상된다.
단점
- 인덱스도 공간을 차지해서 데이터베이스 안에 추가 공간이 필요하다. (대략 테이블 크기의 10% 정도)
- 처음 인덱스를 생성하는 데 시간이 오래 소요될 수 있다.
- SELECT가 아닌 데이터 변경 작업(INSERT, UPDATE, DELETE)이 자주 일어나면 오히려 성능이 저하될 수 있다.
종류
- 클러스터형 인덱스(Clustered Index) : PRIMARY KEY > UNIQUE NOT NULL. 테이블당 1개만 지정 가능. 기본 키로 설정한 칼럼에 대해 클러스터형 인덱스가 자동 생성 -> 기본키 기준으로 데이터 자동 정렬
- 보조 인덱스(Secondary Index) : UNIQUE, UNIQUE NULL. 여러 개 지정 가능. 데이터와 인덱스를 다른 위치에 저장
*인덱스 정보 확인: SHOW INDEX FROM 테이블
key_name 가 primary 라면 클러스터형 인덱스. 칼럼명 이라면 보조 인덱스
인덱스의 내부 작동
클러스터형 인덱스와 보조 인덱스는 모두 내부적으로 균형 트리(Balenced Tree, B-Tree) 구조로 구성된다.
root node - internal node - leaf node
In database, node = page
각 페이지에 데이터가 여러 건 들어간다.
만약 데이터를 균형 트리에 적재하지 않는다면, 리프 페이지만 있으므로 모든 데이터를 처음부터 끝까지 검색하는 Full Table Scan 방법 밖에 없다.
*Full Table Scan: SELECT 과정 중에 데이터를 처음부터 끝까지 검색하는 방법.
균형 트리 는 무조건 루트 페이지부터 검색한다. 데이터가 정렬된 상태를 유지하고 있으므로 탐색을 더 빠르게 할 수 있다.
그러나 정렬된 상태를 유지하기 위해 데이터 갱신 시 균형을 재정립하는 작업이 필요하다. 이것을 페이지 분할이라고 하는데 성능 저하의 원인이 된다.
클러스터형 인덱스
- 루트 페이지: 데이터 탐색의 시작. 리프 페이지의 참조를 저장한다. 정렬O
- 리프 페이지=데이터 페이지: 실제 데이터의 참조값. 정렬O
보조 인덱스
- 루트 페이지: 데이터 페이지에 바로 연결되지 않고 리프 페이지에 매핑되는 참조 값을 가진다. 정렬O
- 리프 페이지: 데이터의 참조를 저장한다. (데이터 페이지 위치 + 페이지 내에서 위치) 정렬O
- 데이터 페이지: 실제 데이터의 참조값. 어떤 처리도 하지 않는다. 정렬X
인덱스 실제 사용
인덱스 생성 (간소화)
CREATE [UNIQUE] INDEX 인덱스명
ON 테이블명 (칼럼명) [ASC | DESC] (기본값: ASC)
ANALYZE TABLE 테이블명; -> 인덱스 생성 후 인덱스를 적용시키는 과정
UNIQUE INDEX: 지정한 칼럼의 데이터가 중복을 허용하지 않는다. 현재도 중복이 없고, 앞으로도 중복될 일이 없을 데이터에만 적용할 것
인덱스 제거
DROP INDEX 인덱스명 ON 테이블명
기본 키, 고유 키로 자동 생성된 인덱스는 DROP INDEX로 제거하지 못한다.
해당 제약조건을 제거하면 인덱스 또한 자동으로 제거된다.
- 외래 키 제거: ALTER TABLE 테이블명 DROP FOREIGN KEY 외래키;
- 기본 키 제거: ALTER TABLE 테이블명 DROP PRIMARY KEY;
*INDEX in Table Status*
SHOW TABLE STATUS LIKE '테이블명';
Non_unique: 중복 허용 여부. (UNIQUE) 1이면 중복 허용, 0이면 비허용
Data_length: 한 페이지의 크기
Index_length: 보조 인덱스 크기
인덱스 사용
- Full Table Scan 외에는 모두 인덱스를 사용한 탐색 방법이다.
- SELECT 절의 조회 데이터는 인덱스와 무관하다. (칼럼 데이터를 전부 가져오는 것일 뿐 필터링의 개념이 아님)
- WHERE 절에서 인덱스를 생성한 칼럼에 대한 조건절만 인덱스를 사용한다. 그러나, 무조건 인덱스를 사용하여 탐색하는 것은 아니다. 인덱스를 사용할 때 더 비효율적일 경우 MySQL 서버는 인덱스를 사용하지 않는다.
- 인덱스를 생성한 칼럼에 연산, 함수 등 가공이 들어가면 인덱스를 사용하지 않는다.
WHERE num*2 >= 14 -> 인덱스 사용X (Full Table Scan)
WHERE num >= 14/2 -> 인덱스 사용
07. 스토어드 프로시저(stored procedure)
스토어드 프로시저 사용 방법
SQL + 프로그래밍 -> 스토어드 프로시저
C, 자바, 파이썬 등 프로그래밍과는 조금 차이가 있지만 MySQL 내부에서 사용할 때 적절한 프로그래밍 기능 제공
쿼리문의 집합을 일괄 처리하기 위한 용도로 사용
(4장의 SQL 프로그래밍 참고)
스토어드 프로시저 생성
DELIMITER $$
CREATE PROCEDURE 스토어드_프로시저_이름(IN 또는 OUT 매개변수)
BEGIN
이 부분에 SQL 프로그래밍 코드 작성
END $$
DELIMITER ;
$$ 의 $은 1개만 사용해도 됨. 명확성을 위해 2개 사용
##, %%, &&, // 등으로 바꾸어 써도 됨
스토어드 프로시저 호출
CALL 스토어드_프로시저_이름();
스토어드 프로시저 삭제
DROP PROCEDURE 스토어드_프로시저_이름;
매개변수 사용
입력 매개변수
CREATE PROCEDURE user_prod( IN 변수명 데이터타입 ) ...
CALL user_prod( 전달값 );
출력 매개변수
CREATE PROCEDURE user_prod( OUT 변수명 데이터타입 ) ...
CALL user_prod( @변수명 ); -> 참조변수 전달
SELECT a INTO b FROM : 조회 결과 a를 변수 b에 대입한다.
참고: 프로시저 생성 시점에 프로시저에서 데이터를 저장하는 테이블이 존재하지 않더라도 오류가 발생하지 않는다.
실제로 프로시저가 실행되는 시점에 테이블이 존재하면 된다.
변수 선언 및 초기화
스토어드 프로시저 안에서 변수 선언은 `DECLARE` 을 사용한다.
DECLARE 변수명 데이터타입;
SET 변수명 = 값;
변수 선언과 초기화 동시에
SET @변수명 = 값;
동적 쿼리 사용 예제
DROP PROCEDURE IF EXISTS dynamic_prod;
DELIMITER $$
CREATE PROCEDURE dynamic_prod(IN tableName VARCHAR(20))
BEGIN
SET @sqlQuery = CONCAT('SELECT * FROM ', tableName);
PREPARE myQuery FROM @sqlQuery;
EXECUTE myQuery;
DEALLOCATE PREPARE myQuery;
END $$
DELIMITER ;
call dynamic_prod('member');
스토어드 함수
스토어드 함수는 스토어드 프로시저와 비슷하지만, 사용 방법이나 용도가 조금 다르다.
스토어드 함수 생성
DELIMITER $$
CREATE FUNCTION 스토어드_함수_이름(매개변수)
RETURN 반환형식
BEGIN
이 부분에 SQL 프로그래밍 코드 작성
RETURN 반환값;
END $$
DELIMITER ;
스토어드 프로시저와 다르게 입력 매개변수만 있다. (변수명 데이터타입) 으로 정의
반환형식+반환값이 출력 매개변수를 대신한다.
스토어드 함수 호출
SELECT 스토어드_함수_이름();
스토어드 함수는 SELECT 쿼리에서 많이 사용한다.
커서(Cursor)
커서는 스토어드 프로시저에서 모든 행을 한 번씩 처리할 때 사용한다. (Loop)
일종의 포인터?
커서의 작동 순서
커서 선언 -> 반복 조건 선언 -> 커서 열기 -> [데이터 가져오기 -> 데이터 처리하기] -> 커서 닫기
*[반복]
커서 사용 예제
1. 사용할 변수 준비
DECLARE memNumber INT; -- 회원의 인원수
DECLARE cnt INT DEFAULT 0; -- 읽은 행의 수
DECLARE totalNumber INT DEFAULT 0; -- 전체 인원의 합계
DECLARE endOfRow BOOLEAN DEFAULT FALSE; -- 행의 끝 여부
2. 커서 선언
DECLARE memberCursor CURSOR FOR
SELECT mem_number FROM member;
커서는 결국 SELECT 문이다. SELECT 쿼리를 FOR 이하에 선언한다.
3. 반복 조건 선언
DECLARE CONTINUE HANDLER
FOR NOT FOUND SET endOfRow = TRUE;
DECLARE CONTINUE HANDLER : 반복 조건을 준비하는 예약어
FOR NOT FOUND : 더이상 행이 없을 때의 수행 지정
4. 커서 열기
OPEN memberCursor;
5. 행 반복하기
한 행씩 접근해서 SELECT 문을 실행
cursor_loop: LOOP
-- 반복하는 부분
FETCH memberCursor INTO memNumber; -- 다음 행 이동
IF endOfRow THEN
LEAVE cursor_loop;
END IF;
SET cnt = cnt + 1;
SET totalNumber = totalNumber + memNumber;
END LOOP cursor_loop;
- cursor_loop: LOOP의 이름
- FETCH memberCursor INTO memNumber: FETCH 는 한 행씩 읽어오는 명령으로, 커서가 가르키는 데이터 (mem_number 행) 를 변수 memNumber 에 저장한다.
- 반복문을 빠져나갈 조건: 반복 조건 선언에서 마지막 행에 도달하면 endOfRow 를 TRUE 로 변경하도록 했다. LEAVE 를 통해 endOfRow 이 참이면 루프를 빠져나가게 하자.
6. 커서 닫기
CLOSE memberCursor;
트리거
테이블에 INSERT, UPDATE, DELETE 작업이 발생하면 자동으로 실행되는 코드
트리거는 사용자가 추가 작업을 잊어버리는 실수를 방지한다. 데이터 변경 전 백업하는 작업 등 -> 데이터의 무결성 보장
트리거의 기본 작동
DML(Data Manipulation Language) 이벤트가 발생할 때 작동. 임의로 호출하여 작동하는 것은 불가능하다.
테이블에 부착(attach)되는 프로그램 코드라고 생각하자
참고: 여기서 설명하는 트리거는 AFTER 트리거이다. 일반적으로 AFTER 트리거를 많이 사용하며 BEFORE 트리거와는 작동방식이 많이 다르다.
트리거 생성
DELIMITER $$
CREATE TRIGGER 트리거_이름
AFTER INSERT|UPDATE|DELETE -- 어떤 DML 후에 작동시킬지 지정
ON 테이블명 -- 트리거 부착할 테이블
FOR EACH ROW -- 각 행마다 적용시킴
BEGIN
트리거 실행시 작동되는 코드
END $$
DELIMITER ;
트리거 활용 - 데이터 백업
테이블 하나에 트리거는 여러 개 부착할 수 있다. ex) Update 트리거, Delete 트리거
CREATE TABLE singer (SELECT mem_id, mem_name, mem_number, addr FROM member);
DROP TABLE IF EXISTS backup_singer;
CREATE TABLE backup_singer
( mem_id CHAR(8) NOT NULL,
mem_name VARCHAR(10) NOT NULL,
mem_number INT NOT NULL,
addr CHAR(2) NOT NULL,
modType CHAR(2), -- 변경된 타입. '수정' or '삭제'
modDate DATE, -- 변경된 날짜
modUser VARCHAR(30) -- 변경한 사용자
);
DROP TRIGGER IF EXISTS singer_updateTrg;
DELIMITER $$
CREATE TRIGGER singer_updateTrg
AFTER UPDATE
ON singer
FOR EACH ROW
BEGIN
INSERT INTO backup_singer VALUE (OLD.mem_id, OLD.mem_name, OLD.mem_number,
OLD.addr, '수정', curdate(), current_user());
END $$
DELIMITER ;
DROP TRIGGER IF EXISTS singer_deleteTrg;
DELIMITER $$
CREATE TRIGGER singer_deleteTrg
AFTER DELETE
ON singer
FOR EACH ROW
BEGIN
INSERT INTO backup_singer VALUE (OLD.mem_id, OLD.mem_name, OLD.mem_number,
OLD.addr, '삭제', curdate(), current_user());
END $$
DELIMITER ;
UPDATE singer SET addr = '영국' WHERE mem_id = 'BLK';
DELETE FROM singer WHERE mem_number >= 7;
SELECT * FROM singer;
SELECT * FROM backup_singer;
- singer: 트리거 부착 테이블
- backup_singer: singer의 백업 테이블 (변경 타입, 날짜, 사용자 칼럼 추가)
- singer_updateTrg, singer_deleteTrg: singer에 부착된 트리거
- singer에 UPDATE, DELETE 가 실행되면, 트리거가 backup_singer에 데이터를 INSERT 한다.
