스키마와 명명 규약
테이블을 영역으로 나누는 기준과 이름 규칙을 정해 두는 까닭입니다. 스키마를 빼고 적은 이름이 무엇을 여는지 봅니다.
표를 담는 영역
실습 데이터베이스의 표는 Board.POSTS 처럼 점이 든
이름을 가지고 있습니다. 앞쪽이 스키마입니다. 데이터베이스 안을
다시 나누는 영역이고, 표·뷰·프로시저가 그 안에 들어갑니다.
SELECT s.name AS 스키마, COUNT(o.object_id) AS 객체수 FROM sys.schemas s LEFT JOIN sys.objects o ON o.schema_id = s.schema_id AND o.is_ms_shipped = 0 WHERE s.name NOT IN ('sys', 'INFORMATION_SCHEMA') GROUP BY s.name HAVING COUNT(o.object_id) > 0;
| 스키마 | 객체수 |
|---|---|
| Board | 27 |
| Member | 3 |
lab-setup.sql 이 게시판 쪽 다섯 개를
Board 에, 회원 하나를
Member 에 두었습니다. 나누지 않아도 돌아갑니다.
나눠 두면 얻는 것이 셋입니다.
- 이름이 겹쳐도 됩니다. 게시판 기록과 회원 기록을 둘 다
LOGS라고 부를 수 있습니다. - 권한을 영역째로 줍니다. 표마다 주지 않아도 됩니다.
- 이름이 짧아집니다.
BOARD_POSTS처럼 표 이름에 영역을 적지 않아도 어디 것인지 드러납니다.
-- 같은 이름을 두 스키마에 둘 수 있습니다. CREATE TABLE Board.LOGS (num int IDENTITY PRIMARY KEY, msg nvarchar(100)); CREATE TABLE Member.LOGS (num int IDENTITY PRIMARY KEY, msg nvarchar(100)); SELECT s.name + '.' + t.name AS 표 FROM sys.tables t JOIN sys.schemas s ON s.schema_id = t.schema_id WHERE t.name = 'LOGS';
| 표 |
|---|
| Member.LOGS |
| Board.LOGS |
스키마를 빼고 적으면 어디로 갑니까
앞의 표를 스키마 없이 불러 봅니다.
SELECT COUNT(*) FROM POSTS;
스키마를 적지 않으면 엔진이 내 기본 스키마에서 찾고, 없으면
dbo 에서 찾습니다. 지금 접속한 계정의
기본 스키마는 dbo 이고 거기에는
POSTS 가 없으므로 208 입니다.
오류로 끝나면 다행입니다. 기본 스키마가 다른 두 사람이 같은 문장을 실행하면 서로 다른 표가 열립니다. 사용자를 둘 만들어 확인합니다.
CREATE USER board_user WITHOUT LOGIN WITH DEFAULT_SCHEMA = Board; CREATE USER member_user WITHOUT LOGIN WITH DEFAULT_SCHEMA = Member; GRANT SELECT, INSERT ON SCHEMA::Board TO board_user; GRANT SELECT, INSERT ON SCHEMA::Member TO member_user; GO -- 두 사람이 글자 하나 다르지 않은 같은 문장을 실행합니다. EXECUTE AS USER = 'board_user'; INSERT INTO LOGS (msg) VALUES (N'board_user 가 넣음'); REVERT; GO EXECUTE AS USER = 'member_user'; INSERT INTO LOGS (msg) VALUES (N'member_user 가 넣음'); REVERT; GO SELECT 'Board.LOGS' AS 표, msg FROM Board.LOGS UNION ALL SELECT 'Member.LOGS', msg FROM Member.LOGS;
| 표 | msg |
|---|---|
| Board.LOGS | board_user 가 넣음 |
| Member.LOGS | member_user 가 넣음 |
그래서 이름은 늘 두 부분으로 적습니다.
Board.POSTS 이지 POSTS
가 아닙니다. 3.7 에서 WITH SCHEMABINDING 뷰가 두
부분 이름을 요구한 것도 이 까닭이고, 4부의 저장 프로시저에서도 같습니다.
네 부분 이름도 있습니다.
서버.데이터베이스.스키마.객체 입니다. 다만 문장에
데이터베이스 이름을 박아 두면 개발·운영에서 이름이 다를 때 그대로 옮길 수
없습니다. 같은 데이터베이스 안이라면 두 부분으로 적는 것이 옮기기 좋습니다.
영역째로 줄 수 있습니다
앞의 예제에서 GRANT SELECT ON SCHEMA::Board 라고
적었습니다. 표를 하나씩 적지 않았습니다.
이 권한은 나중에 만드는 표에도 적용됩니다.
CREATE USER reader WITHOUT LOGIN WITH DEFAULT_SCHEMA = Board; GRANT SELECT ON SCHEMA::Board TO reader; GO -- 권한을 준 뒤에 표를 만듭니다. CREATE TABLE Board.T_LATER (num int IDENTITY PRIMARY KEY, msg nvarchar(50)); INSERT INTO Board.T_LATER (msg) VALUES (N'나중에 만든 표'); GO EXECUTE AS USER = 'reader'; SELECT msg AS 읽힘 FROM Board.T_LATER; -- 권한을 따로 주지 않았습니다 SELECT COUNT(*) FROM Member.USERS; -- 다른 스키마입니다 REVERT;
| 읽힘 |
|---|
| 나중에 만든 표 |
이것이 스키마로 나누는 가장 실질적인 이유입니다. 표를 더할
때마다 권한 스크립트를 고치지 않아도 됩니다. 반대로 말하면
스키마를 잘못 잡으면 권한도 함께 어긋납니다. 회원 정보를
Board 에 두었다면 게시판을 읽을 수 있는 모두가
회원 정보도 읽게 됩니다.
그래서 나누는 기준은 업무 영역이 아니라 권한 경계로 잡습니다. "누구에게 통째로 열어 줄 수 있는가" 가 같은 것끼리 묶으십시오. 권한 자체는 6.1 에서 다룹니다.
나중에 옮길 수 있습니다
처음부터 완벽하게 나눌 필요는 없습니다.
ALTER SCHEMA … TRANSFER 로 옮깁니다.
CREATE TABLE Board.T_MOVE ( num int IDENTITY PRIMARY KEY, post_num int NOT NULL, CONSTRAINT FK_T_MOVE_POSTS FOREIGN KEY (post_num) REFERENCES Board.POSTS(num)); CREATE NONCLUSTERED INDEX IX_T_MOVE_post ON Board.T_MOVE (post_num); GO ALTER SCHEMA Member TRANSFER Board.T_MOVE; GO SELECT s.name + '.' + t.name AS 표, (SELECT COUNT(*) FROM sys.foreign_keys f WHERE f.parent_object_id = t.object_id) AS 외래키, (SELECT COUNT(*) FROM sys.indexes i WHERE i.object_id = t.object_id AND i.name IS NOT NULL) AS 인덱스 FROM sys.tables t JOIN sys.schemas s ON s.schema_id = t.schema_id WHERE t.name = 'T_MOVE';
| 표 | 외래키 | 인덱스 |
|---|---|---|
| Member.T_MOVE | 1 | 2 |
따라오지 않는 것이 둘 있습니다. 하나는 그 표에 직접 준 권한입니다. 옮기면 지워집니다.
GRANT SELECT ON OBJECT::Board.T_PERM TO p_user; GO ALTER SCHEMA Member TRANSFER Board.T_PERM; GO SELECT COUNT(*) AS 남은권한수 FROM sys.database_permissions WHERE major_id = OBJECT_ID('Member.T_PERM');
| 잰 때 | 권한 수 |
|---|---|
| 옮기기 전 | 1 |
| 옮긴 뒤 | 0 |
다른 하나는 이름입니다. 제약과 인덱스의 이름은 그대로 남습니다.
위에서도 FK_T_MOVE_POSTS 라는 이름이 남았습니다.
이름에 스키마를 적어 두었다면 옮긴 뒤 어긋납니다.
스키마를 지우려면 안이 비어 있어야 합니다. 표가 남아 있으면 메시지 3729 — Cannot drop schema 'Board' because it is being referenced by object 'BOARDS' 로 막힙니다. 먼저 옮기거나 지우십시오.
-- 여기까지 만든 것을 지웁니다. 다음 절의 세기가 어긋나지 않게 합니다. DROP TABLE Board.LOGS, Member.LOGS, Board.T_LATER; DROP USER board_user; DROP USER member_user; DROP USER reader;
이름 규칙을 정해 두는 까닭
이름을 어떻게 지을지는 정답이 없습니다. 그런데
정해 두지 않으면 값을 치릅니다. 3.4 에서 제약에 이름을 주지
않았더니 CK__T_NM__cnt__7FB5F314 처럼 서버마다
다른 이름이 붙어 배포 스크립트가 막혔습니다. 규칙은 그런 자리를 없애려고
둡니다.
실습 데이터베이스가 무엇을 따르고 있는지 세어 봅니다.
SELECT LEFT(o.name, CHARINDEX('_', o.name + '_') - 1) AS 접두사, o.type_desc AS 종류, COUNT(*) AS 개수 FROM sys.objects o WHERE o.is_ms_shipped = 0 AND o.type IN ('PK', 'UQ', 'F', 'C', 'D') GROUP BY LEFT(o.name, CHARINDEX('_', o.name + '_') - 1), o.type_desc ORDER BY o.type_desc;
| 접두사 | 종류 | 개수 | 보기 |
|---|---|---|---|
| CK | CHECK_CONSTRAINT | 2 | CK_FILES_size |
| DF | DEFAULT_CONSTRAINT | 5 | DF_POSTS_hit_count |
| FK | FOREIGN_KEY_CONSTRAINT | 8 | FK_POSTS_USERS |
| PK | PRIMARY_KEY_CONSTRAINT | 6 | PK_POSTS |
| UX | UNIQUE_CONSTRAINT | 3 | UX_USERS_user_id |
이름만 보고 무엇인지 알 수 있고, 이름을 알면 어디에 붙어 있는지도
압니다. FK_POSTS_USERS 는
POSTS 에서 USERS 를
가리키는 외래 키입니다. 오류 메시지에 이 이름이 나오면 어느 표를 볼지 바로
정해집니다.
실습 데이터베이스가 따르는 규칙
| 대상 | 규칙 | 보기 |
|---|---|---|
| 스키마 | 파스칼 | Board · Member |
| 표 | 대문자 · 복수 | POSTS · COMMENTS · FILES |
| 열 | 소문자 · 밑줄 | board_num · hit_count · reg_date |
| 기본 키 | PK_표 | PK_POSTS |
| 외래 키 | FK_자식_부모 | FK_COMMENTS_POSTS |
| 인덱스 | IX_표_용도 | IX_POSTS_list |
| 고유 | UX_표_열 | UX_USERS_user_id |
| 뷰 | V_이름 | V_POST_LIST |
여기에 정답은 없습니다. 표를 단수로 두는 곳도 많고, 열을 파스칼로 적는 곳도
많습니다. 중요한 것은 하나를 정하고 지키는 것입니다. 같은
데이터베이스 안에서 POSTS 와
Comment 가 섞이면, 이름을 적을 때마다 무엇이었는지
확인해야 합니다.
규칙에 형식은 넣지 마십시오.
str_title · int_num
같은 이름은 형식을 바꾸는 순간 거짓말이 됩니다. 형식은
sys.columns 가 이미 정확히 알고 있습니다.
대소문자와 예약어
이름의 대소문자는 무시됩니다
정확히 말하면 데이터베이스 대조를 따릅니다. 실습
데이터베이스는 SQL_Latin1_General_CP1_CI_AS 이고
CI 가 대소문자를 구분하지 않는다는 뜻입니다.
SELECT COUNT(*) AS 글수 FROM board.posts; -- 전부 소문자로 적어도 됩니다 -- 값을 비교할 때도 마찬가지입니다. SELECT user_id, nickname FROM Member.USERS WHERE user_id = 'HONG';
| user_id | nickname |
|---|---|
| hong | 홍길동 |
이름을 아무렇게나 적어도 되는 것이 아니라, 틀리게 적어도 오류가 나지
않는다는 뜻입니다. 규칙을 정해도 강제되지 않으므로 사람이 지켜야
합니다. 그리고 대조가 CS 인 서버로 옮기면 그때
비로소 전부 깨집니다.
예약어를 이름으로 사용하지 마십시오
CREATE TABLE Board.USER (num int); -- USER 는 예약어입니다 GO CREATE TABLE Board.[USER] (num int); -- 대괄호로 감싸면 만들어집니다 GO DROP TABLE Board.[USER];
실습 데이터베이스가 회원 표를 USERS 로 둔 것은
이 까닭입니다. 복수로 적는 규칙이 예약어를 피하는 데도 도움이
됩니다 — USER ·
KEY · ORDER 는
예약어지만 USERS ·
KEYS · ORDERS 는
아닙니다.
직접 해보기
회원끼리 주고받는 쪽지를 담을 표를 만들어 보세요. 보낸 사람과 받은 사람, 내용, 읽은 시각, 보낸 시각이 필요합니다. 어느 스키마에 둘지 정하고, 제약과 인덱스 이름을 실습 데이터베이스의 규칙에 맞추십시오.
Board 에 있던 표를
Member 로 옮겼더니 읽던 사람이 229 로 막힙니다.
무슨 일이 일어난 것이고, 무엇을 해야 합니까.
이 단원이 만든 것은 예제 안에서 그때그때 지웠습니다. 남아 있는 것이 없는지
확인하려면 아래를 실행하십시오. 실습 데이터베이스의 여섯 표만 나와야 합니다.
SELECT s.name + '.' + t.name FROM sys.tables t JOIN sys.schemas s ON s.schema_id = t.schema_id;
- 스키마는 데이터베이스 안을 나누는 영역입니다. 같은 이름의 표를 여러 스키마에 둘 수 있습니다.
- 이름은 늘
Board.POSTS처럼 두 부분으로 적으십시오. 스키마를 빼면 실행하는 사람의 기본 스키마에서 찾습니다. 없으면 208 이고, 있으면 사람마다 다른 표가 열립니다. GRANT … ON SCHEMA::는 나중에 만드는 표에도 적용됩니다. 스키마를 나누는 기준은 업무 영역이 아니라 권한 경계입니다.ALTER SCHEMA … TRANSFER로 옮기면 외래 키와 인덱스는 따라오지만 그 표에 직접 준 권한은 지워집니다. 제약 이름도 그대로 남습니다.- 이름 규칙은 정답이 없고 하나를 정해 지키는 것이 중요합니다. 실습 데이터베이스는
PK_·FK_자식_부모·IX_표_용도·UX_·CK_·DF_를 예외 없이 따릅니다. - 이름에 형식을 넣지 마십시오. 형식을 바꾸면 이름이 거짓말이 됩니다.
- 대조가
CI라 이름의 대소문자는 무시됩니다. 틀리게 적어도 오류가 나지 않을 뿐이고,CS서버로 옮기면 그때 깨집니다. USER·KEY·ORDER는 예약어입니다. 복수로 적으면 피해 갑니다.