본문 바로가기

SQL Server/SQL Server Tip & Tech

m : n 모델을 단일 컬럼으로 표현하는 문제

cardinality 가 m : n 인 모델을 비정규화하여 표현해야하는 경우가 종종 있습니다.

예를 들어 설명하는 것이 이해를 도울 수 있을 것 같군요.
음.. 뭐가 좋을까요?
.
.
회원이 구독하는 메일링 리스트를 예로 들면 적당하겠군요.

정규화된 형태라면 아래와 같습니다.
그런데 개발자로부터 이런 요구를 받아 보신 적이 있으신지 궁금하네요.

"메일링 리스트의 종류가 10개가 채 안되니.. 그냥 회원 테이블에 구독하는 메일링 리스트를 컬럼 1개에 넣어 주세요."

전 비슷한 요청을 참 많이 받아봤던 것 같습니다.
예의 메일링 리스트라던가.. 관리자의 권한이라던가.. 설문이라던가.. 등등

이때 개발자가 요구하는 1개의 컬럼이란 비트 연산의 결과를 integer나 binary로 저장해 달라는 뜻이 됩니다.
뭐.. 개발자에 따라서는 xml 필드일 수도 있습니다만..

어쨌든 어플리케이션에서 직접 읽고 쓰는 로직을 구현하겠다는 거죠.


1. 비트 연산 결과를 컬럼 하나로 표현한 모델
장점 : 특정 회원이 가입한 메일링 리스트를 조회하는데는 최적일 수 있다.
단점 : 특정 메일링 리스트를 구독하는 회원을 조회하는 것에는 취약하다.



2. 정규화된 모델과 비트 연산 결과 컬럼을 함께 사용한 모델

예를들어 293번 회원이 구독하는 메일링리스트가 0번, 3번, 4번 이라면.. 테이블의 값은 아래와 같게 됩니다.
 

회원

회원 번호

구독하는 메일링 리스트

293

27

 ※ 2^0 + 2^3 + 2^4 = 27
 

구독하는 메일링 리스트

회원 번호

메일링 리스트 번호

293

0

293

3

293

4


장점 : 개발자가 원하는 형태 & DBA가 원하는 형태
단점 : "구독하는 메일링 리스트" 속성과 "구독하는 메일링 리스트" 엔티티 사이의 무결성이 깨질 수 있다. & 성능 이슈



3. 2항의 단점인 무결성 문제를 DB 수준에서 보장해 주는 모델

2항의 모델은 비트 연산 결과 값을 회원 테이블에 컬럼으로 가지고 있고, 이 값을 어플리케이션에서 관리합니다.
이에반해 3항의 모델은 "구독하는 메일링 리스트" 엔티티를 참조한 뷰를 추가하여 이 값을 DB가 계산하도록 합니다.

- 뷰 정의
CREATE VIEW [구독하는메일링비트연산값]

AS

SELECT [회원번호], SUM(POWER(2, [메일링리스트번호])) AS [구독하는메일링리스트]

FROM [구독하는메일링리스트]

GROUP BY [회원번호]



장점 : 2항의 단점이었던 무결성 문제를 해소
단점 : 뷰를 참조할 때 매번 연산이 수행되므로 작업 부하 증가

만약, 비트 연산값을 조회하는 빈도가 갱신하는 빈도보다 훨씬 많다면, 뷰를 일반 뷰가 아닌 "인덱스된 뷰"로 만드는 것이 좋습니다.
이렇게하면 회원의 메일링 리스트 구독이 변경될 때만 연산이 수행되고, 조회할 때는 "인덱스된 뷰"로 부터 빠르게 결과를 가져올 수 있으니까요.

- 인덱스된 뷰 정의

CREATE VIEW [구독하는메일링비트연산값]

WITH SCHEMABINDING

AS

SELECT [회원번호], SUM(POWER(2, [메일링리스트번호])) AS [구독하는메일링리스트], COUNT_BIG(*) AS Cnt

FROM [구독하는메일링리스트]

GROUP BY [회원번호]

GO

 

CREATE UNIQUE CLUSTERED INDEX IX_UC_MailingListBitmap ON [구독하는메일링비트연산값] ([회원번호])

GO