테이블 내에 일반적으로 자주 관리되는 컬럼만이 아니라 덜 일반적인 많은 속성을 관리해야한다면 컬럼은 얼마나 많아야 충분할 것인가?
무단횡단 안티패턴과 같다. 테이블의 한 속성에 여러 개의 값을 가진다.
예제에서는 태그를 이용하여 서브시스템을 분류할 것이다.
태그는 상호 배타적일 필요가 없기 때문에 여러 태그를 다는 것도 가능하다.
원칙은 안다. 하나의 속성에 하나의 값!?
그런데 일반적인 속성에서 쉽게 하던 일이 복잡해진다.
CREATE TABLE Bugs (
bug_id SERIAL PRIMARY KEY,
description VARCHAR(1000),
tag1 VARCHAR(20),
tag2 VARCHAR(20),
tag3 VARCHAR(20)
);
어느 컬럼안에 원하는 태그문자열이 있는지 알 수 없기 때문에 모두 뒤져야 한다.
SELECT * FROM Bugs
WHERE tag1 = 'performance'
OR tag2 = 'performance'
OR tag3 = 'performance';
그리고 여러 개의 태그를 가진 자료를 찾아야 할 수도 있다.
SELECT * FROM Bugs
WHERE (tag1 = 'performance' OR tag2 = 'performance' OR tag3 = 'performance')
AND (tag1 = 'printing' OR tag2 = 'printing' OR tag3 = 'printing');
연속된 OR를 IN으로 변환하여 다음처럼 바꿀 수는 있다.
SELECT * FROM Bugs
WHERE 'performance' IN (tag1, tag2, tag3)
AND 'printing' IN (tag1, tag2, tag3);
3개 컬럼 중에서 조회하는 것도 성능문제가 발생할 수 있는데 변경/삭제 또한 걸린다.
--컬럼의 값을 초기화 할 때
UPDATE Bugs
SET tag1 = NULLIF(tag1, 'performance'),
tag2 = NULLIF(tag2, 'performance'),
tag3 = NULLIF(tag3, 'performance')
WHERE bug_id = 3456;
--컬럼을 추가할 때
UPDATE Bugs
SET tag1 = CASE
WHEN 'performance' IN (tag2, tag3) THEN tag1
ELSE COALESCE(tag1, 'performance') END,
tag2 = CASE
WHEN 'performance' IN (tag1, tag3) THEN tag2
ELSE COALESCE(tag2, 'performance') END,
tag3 = CASE
WHEN 'performance' IN (tag1, tag2) THEN tag3
ELSE COALESCE(tag3, 'performance') END
WHERE bug_id = 3456;
여러 컬럼에 동일한 값 입력을 막고 싶지만 예방하기 어렵다.??
값의 수가 증가하게 되면 컬럼이 모자라게 된다. 무단횡단과 같은 안티패턴의 부작용이 나타난다.
테이블의 Description을 변경할 수 있다고 해도 다음과 같은 부분을 고려해야 한다.
사용자 인터페이스에서 여러 개의 값을 할당 할 수 있지만 최대 개수가 제한되어 있는 속성이 있다면 다중 컬럼속성 안티패턴이 사용되어 있다는 것이다.
속성의 개수가 고정되고 선택의 위치나 순서가 중요할 수 있다. 역할/위치 등 수가 고정적이고 의미와 사용처가 명확히 나누어질 경우
무단횡단 안티패턴에서 확인한 바와 같이 가장 좋은 해법은 다중 값 속성을 위한 하나의 컬럼을 가지는 종속테이블을 만드는 것으로 여러 개의 컬럼 대신 여러 개의 행으로 만드는 것이다.
CREATE TABLE Tags (
bug_id BIGINT UNSIGNED NOT NULL
tag VARCHAR(20),
PRIMARY KEY (bug_id, tag),
FOREIGN KEY (bug_id) REFERENCES Bugs(bug_id)
);
INSERT INTO Tags (bug_id, tag)
VALUES (1234, 'crash'), (3456, 'printing'), (3456, 'performance');
하나의 내용에 연관된 모든 태그가 한 컬럼안에 있으므로 조회/수정/삭제가 용이해진다.
INSERT INTO Tags (bug_id, tag) VALUES (1234, 'save');
DELETE FROM Tags WHERE bug_id = 1234 AND tag = 'crash';
PK로 중복허용하지 않으며 태그가 무한정 증가하더라도 필요한 만큼의 태크를 적용할 수 있다.