문서 읽는 데 49분 · B2

B-2: 엔터티와 속성 — 다섯 등장인물을 종류·이름·속살까지 들여다보기

목차 42
전체 24강 중 4강 · 데이터베이스
난이도 · 입문

ℹ️Oracle SQL로 SQLD 자격증을 대비하며 실전 SQL을 쌓는 트랙이에요. 프로그래밍 경험이 없어도 시작할 수 있고, 부트캠프에서는 자바 기초 다음·스프링 부트 직전에 배치하길 권해요.

안녕하세요, 데이터베이스의 세계를 함께 걷는 홍순구 튜터입니다. 다시 만나서 반가워요.

지난 시간엔 인스타그램이라는 무대에 어떤 등장인물이 오르는지를 골라냈죠. 회원·게시물·댓글·좋아요·팔로우 — Member·Post·Comment·Like·Follow 다섯을 박스로 그리고, 속성(엔터티가 갖는 항목)과 관계(엔터티끼리 잇는 선)로 통합 ERD까지 완성했어요. 종이 위에 인스타그램의 뼈대를 처음 세운 시간이었습니다.

그런데 그 다섯 박스를 가만히 들여다보면, 성격이 제각각이라는 게 보여요. 회원은 혼자서도 존재하지만 댓글은 게시물이 있어야 생기죠. 회원은 눈에 보이는 실체 같은데, 좋아요는 "누른다"는 행동에 가깝고요. 지난 시간엔 이 차이를 슬쩍 짚고 넘어갔는데, 오늘은 그 차이에 정식 이름을 붙입니다.

텍스트
 오늘의 여정 — 다섯 등장인물을 더 깊이 들여다보기

   눈에 보이나?       — 엔터티 분류 : 유형·개념·사건
   언제 생기나?       — 엔터티 분류 : 기본·중심·행위
   혼자 서나, 기대나   — 독립 엔터티·종속 엔터티
   이름을 잘 짓는 법   — 엔터티·속성 명명 규칙
   속성에도 종류가     — 기본·설계·파생 속성
   더 쪼갤 수 있나     — 단일·복합·다중값 속성
   값의 울타리         — 도메인(자료형·길이·제약)
   인스타그램 다시      — 다섯 엔터티 명세를 속살까지 완성
        │
        
   엔터티 박스를 '종류 · 이름 · 속성 · 값의 범위'까지 꽉 채우기

종이와 펜은 지난 시간 그대로 들고 오시면 됩니다. 오늘도 SQL을 새로 짜는 시간이 아니라, 표를 만들기 전에 설계를 더 촘촘히 다듬는 시간이에요.

💡 오늘 수업의 핵심 — "엔터티를 세 관점으로 분류하고, 이름을 제대로 지으며, 속성을 종류별로 나누고, 값이 가질 수 있는 범위(도메인)까지 종이 위에서 채운다"

🎯 학습 목표

  • 엔터티를 유무형(유형·개념·사건)과 발생 시점(기본·중심·행위), 그리고 독립·종속의 세 관점으로 분류할 수 있다.
  • 좋은 엔터티·속성 이름의 조건을 알고, 속성을 기본·설계·파생 / 단일·복합·다중값으로 구분할 수 있다.
  • 속성이 가질 수 있는 값의 범위인 도메인(자료형·길이·제약)을 이해하고, SQLD 1과목 엔터티·속성 출제 포인트를 정리할 수 있다.

Step 1: "눈에 보이나? — 엔터티 분류 ① 유형·개념·사건"

지난 시간에 그린 다섯 박스를 다시 떠올려 봅시다. Member·Post·Comment·Like·Follow. 이 다섯을 한 줄로 늘어놓으면 다 똑같은 '엔터티'지만, 사실 종류가 나뉘어요. 사람을 분류할 때 "어른이냐 아이냐", "직장인이냐 학생이냐" 여러 기준으로 나눌 수 있듯이, 엔터티도 보는 기준에 따라 종류가 갈립니다.

오늘 첫 기준은 이거예요. "눈에 보이는 실체인가?"

유형·개념·사건 — 형태가 있느냐로 가른다

엔터티를 형태가 있느냐 없느냐로 나누면 세 가지가 됩니다. SQLD에서는 이걸 유무형에 따른 분류라고 불러요.

  • 유형 엔터티(tangible entity) — 눈에 보이고 만질 수 있는 물리적 실체예요. 사람, 물건, 책처럼요. 한번 정해지면 잘 안 바뀌고 안정적입니다.
  • 개념 엔터티(conceptual entity) — 형태는 없지만 우리가 머릿속에서 약속한 개념이에요. 회사의 '부서', 상품의 '등급', 글의 '분류' 같은 것들이죠. 손으로 만질 순 없지만 분명히 관리해야 하는 대상입니다.
  • 사건 엔터티(event entity) — 어떤 일이 일어나면서 생기는 거예요. 주문, 예약, 신청처럼 "무슨 일이 벌어졌다"는 기록이죠. 사건은 자주 생기고 자주 사라집니다.

식당으로 비유해 볼게요. 식당에 있는 '테이블'과 '메뉴판'은 눈에 보이는 유형이에요. '메뉴 카테고리(식사류·음료류)'는 형태 없는 개념이고요. 손님이 "주문을 했다"는 건 그 순간 일어난 사건이죠. 같은 식당 안에도 이렇게 세 종류가 섞여 있습니다.

인스타그램 다섯에 대보자

그럼 우리 다섯 엔터티는 어디에 속할까요? 하나씩 물어봅시다.

텍스트
   엔터티 분류  — 눈에 보이는가? (유무형 기준)

   [유형] 눈에 보이는 실체 — 회원·게시물
   ┌──────────┐ ┌──────────┐
   │  Member  │ │   Post   │
   └──────────┘ └──────────┘

   [개념] 형태 없는 분류·약속 — 게시물 카테고리
   ┌──────────┐
   │ Category │    아직 안 만든, '개념'을 보여주는 예
   └──────────┘

   [사건] 무슨 일이 '일어나' 남는 기록 — 좋아요·댓글·팔로우
   ┌──────────┐ ┌──────────┐ ┌──────────┐
   │   Like   │ │ Comment  │ │  Follow  │
   └──────────┘ └──────────┘ └──────────┘

회원과 게시물은 유형이에요. 회원은 가입한 사람이라는 또렷한 실체고, 게시물도 사진과 글이라는 분명한 덩어리죠. 한번 만들어지면 그 자체로 안정적으로 남습니다.

좋아요·댓글·팔로우는 사건이에요. "좋아요를 눌렀다", "댓글을 달았다", "팔로우했다" — 전부 어떤 순간에 일어난 일이고, 그 일이 벌어진 결과를 기록으로 남긴 거니까요. 그래서 좋아요는 눌렀다 취소했다 하며 자주 생기고 사라지죠.

개념 엔터티는 우리 핵심 다섯에는 딱 들어맞는 게 없어요. 굳이 든다면 게시물을 '여행·음식·일상' 같은 갈래로 나누는 카테고리가 개념 엔터티예요. 형태는 없지만 "이 게시물은 어느 분류에 속하나"를 관리하려면 필요하죠. 이건 한참 뒤 모듈에서 게시물에 분류를 붙일 때 다시 만나요.

⚠️ 함정 — 유형·개념·사건은 "물리적 형태가 있느냐"로 나누는 기준이에요. 잠시 뒤 Step 2에서 배울 "언제 생기느냐(기본·중심·행위)"와 헷갈리기 쉬워요. SQLD에서 이 둘을 섞어 함정 보기로 내니, 기준이 서로 다르다는 것부터 잡아두세요.

💡 한 줄 정리

엔터티를 형태가 있느냐로 나누면 유형(눈에 보이는 실체 — 회원·게시물)·개념(형태 없는 분류 — 카테고리)·사건(일어난 일의 기록 — 좋아요·댓글·팔로우) 셋이다. SQLD 1과목 '유무형에 따른 분류'의 단골 보기다.

🙋 학생 질문 — "튜터님, 댓글은 글자가 적혀 있으니 눈에 보이는 거 아니에요? 왜 유형이 아니라 사건이에요?"

헷갈릴 만한 좋은 질문이에요. 댓글에 글자가 들어 있으니 실체처럼 느껴지죠.

핵심은 "그게 무엇으로부터 생겨났나"예요. 댓글은 누군가 게시물에 "댓글을 단 순간"에 비로소 생깁니다. 즉 댓글이라는 행위가 일어나야 존재하는 거예요. 회원처럼 가입만 하면 혼자 존재하는 게 아니라, "달았다"는 사건이 있어야 따라 생기죠. 그래서 글자가 적혀 있어도 사건 엔터티로 봅니다.

반대로 회원은 어떤 사건의 결과가 아니라, 그 자체로 먼저 존재하는 실체예요. "가입"이라는 행위가 있긴 하지만, 우리가 관리하는 건 그 행위가 아니라 '회원'이라는 안정적인 대상이죠. 이 차이가 유형과 사건을 가르는 감각이에요. 너무 칼같이 외우기보다, "이게 어떤 일이 벌어져서 생긴 기록인가?"를 물어보면 대개 답이 나옵니다.


Step 2: "언제 생기나? — 엔터티 분류 ② 기본·중심·행위"

Step 1에서는 "눈에 보이는가"로 엔터티를 나눴어요. 이번엔 완전히 다른 기준으로 다시 나눠봅니다. 바로 "언제, 무엇으로부터 생기는가" 예요. 같은 다섯 박스를 발생 순서로 줄 세워 보는 거죠.

기본·중심·행위 — 발생 순서로 가른다

엔터티가 생기는 순서를 따라가면 세 가지로 나뉩니다. SQLD에서는 이걸 발생 시점에 따른 분류라고 불러요.

  • 기본 엔터티(basic entity) — 다른 엔터티의 도움 없이 스스로 생기는 엔터티예요. 누구에게도 기대지 않고 자기 식별자만으로 존재하죠. 그래서 다른 엔터티의 부모 노릇을 합니다.
  • 중심 엔터티(main entity) — 기본 엔터티로부터 생겨나는, 업무의 중심이 되는 엔터티예요. 그리고 자기도 또 다른 엔터티를 낳습니다.
  • 행위 엔터티(active entity) — 두 개 이상의 엔터티로부터 생기는, "무슨 일을 했다"는 엔터티예요. 자주 생기고 자주 바뀌고 자주 사라집니다.

인스타그램은 어떤 순서로 생기나

회원이 가입해야 게시물을 올리고, 게시물이 있어야 댓글과 좋아요가 달리죠. 이 순서를 그대로 그리면 발생 시점 분류가 보여요.

텍스트
   발생 순서 — 누가 있어야 누가 생기나

   [기본] Member       스스로 존재 — 가입만 하면 회원
        │  회원이 있어야
        
   [중심] Post         회원으로부터 생김 · 인스타 업무의 중심
        │  게시물이 있어야
        
   [행위] Comment · Like · Follow
                       회원+게시물 등 둘 이상에서 생김 · 자주 변함

Member는 기본이에요. 가입만 하면 다른 무엇도 필요 없이 회원으로 존재하니까요. Post는 중심입니다. 회원이 있어야 비로소 게시물이 생기고, 게시물은 다시 댓글·좋아요라는 행위를 낳죠. 그래서 인스타그램 업무의 한가운데 있어요. Comment·Like·Follow는 행위예요. "댓글을 달았다", "좋아요를 눌렀다", "팔로우했다" — 전부 회원과 게시물(또는 회원과 회원)이 있어야 벌어지는 일이니까요.

⚠️ 함정 — 같은 엔터티가 여러 기준으로 동시에 분류돼요. Member는 Step 1 기준으론 '유형'이면서, Step 2 기준으론 '기본'입니다. Like는 '사건'이면서 '행위'고요. 기준이 다르니 둘 다 맞는 거예요. SQLD에서 "Member는 유형 엔터티이자 기본 엔터티다" 같은 보기가 나오면 당황하지 마세요.

💡 한 줄 정리

발생 시점으로 나누면 기본(스스로 생김 — Member)·중심(기본에서 생기고 업무의 중심 — Post)·행위(둘 이상에서 생김 — Comment·Like·Follow) 셋이다. 유무형 분류와는 기준이 다르므로, 한 엔터티가 두 기준으로 동시에 분류된다.

🙋 학생 질문 — "튜터님, Post가 왜 '중심'이에요? 게시물도 그냥 하나의 대상 아닌가요?"

좋은 질문이에요. Post도 분명 하나의 엔터티인데 왜 굳이 '중심'이라는 특별한 이름을 줄까요?

기준은 "이 엔터티를 빼면 서비스가 어떻게 되나"예요. 인스타그램에서 게시물을 빼면 댓글도 좋아요도 의미가 없어지죠. 댓글은 게시물에 달리고, 좋아요도 게시물에 눌리니까요. 즉 Post는 여러 행위 엔터티가 매달리는 한가운데에 있어요. 회원(기본)에서 생겨나면서, 동시에 댓글·좋아요(행위)를 낳는 길목인 거죠.

반대로 Member는 게시물을 한 개도 안 올려도 멀쩡한 회원이에요. 누구에게도 기대지 않고 먼저 존재하니 '기본'이고요. 이렇게 "스스로 먼저 있나(기본), 업무의 길목인가(중심), 다른 것들이 있어야 생기나(행위)"를 따지면 셋이 자연스럽게 갈립니다.


Step 3: "혼자 서나, 기대나 — 독립 엔터티·종속 엔터티"

지난 시간에 제가 약속한 게 하나 있어요. 다섯 엔터티를 보면서 "회원은 혼자 서고, 댓글은 게시물에 기대어 존재한다"는 차이를 짚고는, "둘을 뭐라고 부르는지는 B-2에서 정식으로 다룬다"고 미뤄뒀죠. 약속대로 오늘 그 정식 이름을 붙입니다.

독립 엔터티와 종속 엔터티

  • 독립 엔터티(independent entity) — 다른 엔터티에 기대지 않고, 자기 식별자만으로 스스로 존재하는 엔터티예요. Member가 그렇죠. 게시물이 0개여도 member_id 하나로 또렷이 구분되는 회원으로 존재합니다. 강한 엔터티(strong entity)라고도 불러요.
  • 종속 엔터티(dependent entity) — 다른 엔터티가 있어야 비로소 존재하는 엔터티예요. Comment가 그렇습니다. 댓글은 "어느 게시물의" 댓글인지가 없으면 의미가 없죠. 부모(게시물)의 식별자를 빌려와야 자기를 가릴 수 있어요. 약한 엔터티(weak entity)라고도 합니다.

지난 시간 7번(taeyang)·8번(narae) 회원 기억하시죠? 게시물이 0개여도 멀쩡한 회원이었어요. 그게 바로 독립 엔터티의 모습입니다. 반대로 게시물이 사라지면 거기 달린 댓글도 존재 의미가 사라지죠. 그게 종속이고요.

텍스트
   혼자 서는가, 기대어 서는가

   [독립] 스스로 식별 — 자기 식별자로 존재
   ┌──────────┐
   │  Member  │   member_id 하나로 회원이 또렷이 구분됨 · 게시물 0개여도 회원
   └──────────┘

   [종속] 기대어 식별 — 부모가 있어야 존재
   ┌──────────┐         ┌──────────┐
   │   Post   │ ──────> │ Comment  │   댓글은 '어느 게시물의' 댓글인지 없으면 의미 없음
   └──────────┘         └──────────┘
   부모 = 게시물          자식 = 댓글 — 게시물 식별자를 빌려와야 자기를 가림

여기서 중요한 감각 하나. 독립이냐 종속이냐는 결국 식별자를 스스로 갖느냐, 부모에게서 빌려오느냐의 문제예요. Member는 member_id를 스스로 갖지만, Comment는 "어느 게시물"이라는 부모 정보를 끌어와야 자기를 가릴 수 있죠. 이 "식별자를 빌려주고 빌려오는" 이야기가 다음 시간에 배울 식별 관계·비식별 관계로 곧장 이어집니다.

💡 한 줄 정리

독립 엔터티(강한 엔터티)는 자기 식별자만으로 스스로 존재한다(Member). 종속 엔터티(약한 엔터티)는 부모 엔터티가 있어야 존재한다(Comment). 둘을 가르는 핵심은 "식별자를 스스로 갖느냐, 빌려오느냐"다.

🙋 학생 질문 — "튜터님, 그럼 좋아요(Like)랑 팔로우(Follow)는 독립이에요, 종속이에요?"

날카로운 질문이에요. 좋아요는 회원과 게시물 양쪽에 다 기대거든요.

좋아요는 "누가(회원) 어느 게시물에(게시물) 좋아요를 눌렀다"는 거라, 회원도 있어야 하고 게시물도 있어야 존재해요. 회원이나 게시물이 사라지면 그 좋아요 기록도 의미가 없어지죠. 그러니 좋아요는 종속 엔터티예요. 그것도 부모가 둘(회원·게시물)인 종속이죠. 팔로우도 마찬가지로 회원 둘이 있어야 존재하니 종속이고요.

사실 Step 2에서 좋아요·팔로우를 '행위 엔터티'라고 했던 것과 자연스럽게 통해요. 두 개 이상의 엔터티로부터 생기는 행위 엔터티는 대개 그 부모들에 종속됩니다. 분류 기준은 다르지만(발생 시점 vs 독립·종속), 같은 엔터티를 다른 각도에서 본 거라 결론이 맞물리는 거예요.


Step 4: "이름을 잘 짓는 법 — 엔터티·속성 명명 규칙"

엔터티를 종류별로 나눠봤으니, 이제 아주 현실적인 이야기를 해요. 바로 이름 짓기입니다. 사소해 보이지만, 이름을 대충 지으면 설계 전체가 흔들려요.

좋은 이름이 왜 중요한가

컴퓨터 폴더를 떠올려 보세요. '새 폴더', '새 폴더(2)', '사진들', '진짜최종' 이런 이름이 가득하면 뭐가 어디 있는지 한참 헤매죠. 반대로 '2026-제주여행', '회원가입-서류'처럼 또렷하면 한눈에 찾습니다. 엔터티·속성 이름도 똑같아요. 이름만 봐도 무엇을 담는지 모두가 같은 뜻으로 읽혀야 해요. 지난 시간 모델링의 특징으로 배운 '명확화'가 여기서 다시 등장합니다.

이름 짓기 네 가지 원칙

  • 현업에서 쓰는 용어로 — 실제 그 일을 하는 사람들이 부르는 말을 써요. 회원을 'usr_tbl_a'가 아니라 Member로.
  • 의미가 분명하게 — 약어를 남발하지 않아요. mem_inf_2는 무슨 뜻인지 알 수 없지만, member는 누가 봐도 회원이죠.
  • 유일하게 — 한 이름이 한 대상만 가리켜요. 똑같은 'data'라는 이름을 여기저기 붙이면 나중에 뒤섞입니다.
  • 단수 명사로Members(복수)가 아니라 Member(단수). 엔터티는 "한 종류의 대상"을 가리키니, 한 건을 기준으로 단수로 짓는 게 관례예요.

속성 이름도 같은 원칙으로 지어요. un·col1 대신 username처럼 무엇을 담는지 분명한 이름으로요. 우리가 지난 시간부터 써온 member_id·username·nickname·bio가 전부 이 원칙을 따른 이름이었어요.

나쁜 이름 무엇이 문제인가 좋은 이름
tbl_1, data2 무엇을 담는지 알 수 없음 member, post
Members (복수) 한 건인지 여럿인지 헷갈림 member (단수)
mem_inf 약어가 과해 의미가 흐림 member
col1, un 속성이 무슨 값인지 모름 username

💡 한 줄 정리

엔터티·속성 이름은 현업 용어로, 의미가 분명하게, 유일하게, 단수 명사로 짓는다. 이름만 봐도 무엇을 담는지 누구나 같은 뜻으로 읽혀야 한다(모델링의 명확화).

🙋 학생 질문 — "튜터님, 이름을 그냥 한글로 '회원', '게시물'이라고 지으면 안 되나요? 더 알아보기 쉽잖아요."

충분히 들 수 있는 생각이에요. 사실 개념·논리 모델 단계에서는 한글로 '회원', '게시물'이라고 적기도 해요. 사람이 보고 이해하는 설계도니까요.

다만 실제 데이터베이스 표로 내려갈 때는 영문 이름을 쓰는 게 오랜 관례예요. 여러 도구와 환경에서 한글 이름이 깨지거나 말썽을 일으키는 경우가 있었고, 전 세계 개발자가 함께 보는 코드에서는 영문이 무난하거든요. 그래서 보통 개념·논리 단계에선 '회원'으로 이해하되, 물리 단계로 가면 member라는 영문 이름을 붙입니다.

우리 강의도 그 관례를 따라 Member·Post 같은 영문 이름으로 설계해요. 대신 처음 나올 때 '회원'·'게시물'이라고 한글 뜻을 꼭 같이 적어드릴게요. 이름은 영문이되 뜻은 또렷하게, 두 마리 토끼를 다 잡는 거죠.


Step 5: "속성에도 종류가 — 기본·설계·파생 속성"

엔터티에 종류가 있었듯, 그 안을 채우는 속성에도 종류가 있어요. 지난 시간 우리는 회원의 username·nickname·bio, 게시물의 caption·created_at 같은 속성을 봤죠. 그런데 이 속성들도 성격이 다 같지는 않아요. 오늘 첫 기준은 "이 값이 어디서 왔는가" 예요.

기본·설계·파생 — 값의 출처로 가른다

  • 기본 속성(basic attribute) — 업무에서 원래부터 가진, 직접 나온 속성이에요. 회원의 username·nickname·bio, 게시물의 caption이 전부 기본 속성이죠. "회원이라면 당연히 갖는 정보"예요.
  • 설계 속성(designed attribute) — 원래 업무엔 없었는데, 설계자가 관리를 위해 새로 만든 속성이에요. 대표적인 게 일련번호죠. member_id·post_id가 여기 속해요. 현실의 회원이 "나는 7번 회원이야"라고 생각하진 않잖아요? 그 번호는 우리가 구분하려고 붙인 거예요.
  • 파생 속성(derived attribute) — 다른 데이터로부터 계산해서 나온 값이에요. 게시물의 '좋아요 수'가 그렇죠. 좋아요 기록을 세면 나오는 값이니까요.

지난 시간 식별자로 봤던 member_id가 사실은 설계 속성이었던 거예요. 현실에 원래 있던 정보가 아니라, 회원을 또렷이 구분하려고 우리가 붙인 번호니까요.

텍스트
   파생 속성 — 다른 데이터에서 '계산'으로 나온 값

   [원본] Like 기록 (누가 어느 게시물에 좋아요를 눌렀나)
        minji    post 7
        jaehoon  post 7      이 게시물의 좋아요를 세어보면 3건
        narae    post 7
            │
             계산
   [파생] post 7 의 like_count = 3
        (저장해두면 빨리 보여주지만, 원본 기록과 어긋날 위험이 있음)

⚠️ 함정 — 파생 속성은 "계산하면 언제든 나오는 값"이라, 미리 저장해 둘지 말지가 설계 판단이에요. 좋아요 수를 게시물에 저장해두면 보여주기는 빠르지만, 누가 좋아요를 누를 때마다 그 숫자도 같이 안 고치면 원본과 어긋나죠. 이 "저장할까 계산할까"의 트레이드오프는 한참 뒤 정규화·반정규화 모듈에서 깊이 다뤄요.

💡 한 줄 정리

속성을 값의 출처로 나누면 기본(업무에서 직접 나온 것 — username·caption)·설계(관리를 위해 만든 것 — member_id 일련번호)·파생(다른 데이터에서 계산한 것 — 좋아요 수) 셋이다.

🙋 학생 질문 — "튜터님, member_id가 왜 '설계' 속성이에요? 회원의 정보 아닌가요?"

헷갈리기 딱 좋은 부분이에요. member_id도 회원 표에 들어 있으니 회원의 정보처럼 보이죠.

기준은 "이게 현실에 원래 있던 값인가, 우리가 만든 값인가"예요. 회원의 아이디(username)나 자기소개(bio)는 그 사람이 실제로 가진 정보죠. 하지만 member_id의 '7'이라는 번호는 현실엔 없던 값이에요. 우리가 회원을 겹치지 않게 구분하려고 1, 2, 3… 순서대로 붙인 거니까요. 이렇게 관리·식별을 위해 설계자가 만들어 붙인 값이 설계 속성입니다.

그래서 설계 속성은 식별자로 자주 쓰여요. 현실의 값(이름·전화번호)은 바뀌거나 겹칠 수 있지만, 우리가 만든 일련번호는 안 겹치고 안 바뀌게 관리할 수 있으니까요. 식별자 종류 이야기는 다음 시간에 더 자세히 이어집니다.


Step 6: "더 쪼갤 수 있나 — 단일·복합·다중값 속성"

속성을 또 다른 기준으로 나눠볼게요. 이번 기준은 "더 쪼갤 수 있는가, 값이 하나인가 여럿인가" 예요.

단일·복합·다중값 — 쪼갬과 값의 개수로 가른다

  • 단일 속성(single attribute) — 더 쪼갤 수 없고 값이 하나인 속성이에요. nickname·bio 같은 거죠. 그냥 값 하나로 끝나요.
  • 복합 속성(composite attribute) — 하나처럼 보이지만 여러 의미로 쪼갤 수 있는 속성이에요. 대표적으로 '주소'가 그렇죠. "서울시 강남구 역삼동"은 시·구·동 셋으로 나뉘잖아요.
  • 다중값 속성(multi-valued attribute) — 한 대상이 그 속성을 여러 개 가질 수 있는 경우예요. 한 게시물에 해시태그가 #여행 #제주 #바다 여러 개 달리는 것처럼요.
텍스트
   복합 속성 — 한 속성이 여러 의미로 쪼개질 때

   주소 = "서울시 강남구 역삼동"
        │ 쪼개면
        
     시 = 서울시 · 구 = 강남구 · 동 = 역삼동

복합 속성과 다중값 속성은 그냥 두면 나중에 골치가 아파요. 그래서 보통 쪼개거나 나눠서 정리합니다. 복합 속성은 시·구·동 같은 단일 속성들로 쪼개고, 다중값 속성은 별도로 한 줄씩 떼어내요.

텍스트
   다중값 속성 — 한 칸에 여러 값이 들어가려 할 때

   ❌ 한 칸에 욱여넣기 :  post 7 의 hashtags = "여행, 제주, 바다"
                          한 칸에 여러 값  "제주 달린 게시물 찾기"가 지옥이 됨

   ✅ 값마다 한 줄로 분리 :
        post 7  여행
        post 7  제주
        post 7  바다
        이렇게 떼어내는 게 다중값 속성의 '해소' — 정규화로 이어짐

이렇게 한 줄씩 떼어내면, "여행 해시태그가 달린 게시물 다 찾아줘" 같은 걸 깔끔하게 할 수 있어요. 한 칸에 쉼표로 욱여넣으면 글자를 일일이 뜯어봐야 하지만요. 이 "여러 값을 어떻게 나눠 담을까"가 정규화의 출발점이라, 한참 뒤 모듈에서 제대로 파고듭니다.

💡 한 줄 정리

속성을 쪼갬·값 개수로 나누면 단일(값 하나 — nickname)·복합(쪼갤 수 있음 — 주소=시+구+동)·다중값(여러 개 — 게시물의 해시태그들) 셋이다. 복합·다중값은 그대로 두지 않고 쪼개거나 나눠서 정리한다.

🙋 학생 질문 — "튜터님, 해시태그를 그냥 한 칸에 쉼표로 넣으면 편한데 왜 굳이 나눠요?"

당장은 한 칸에 "여행, 제주, 바다"라고 넣는 게 편해 보이죠. 입력도 한 번에 끝나고요. 그런데 나중에 그 데이터를 쓸 때 문제가 터져요.

예를 들어 "제주 해시태그가 달린 게시물을 다 찾아줘"를 해야 한다고 해봅시다. 한 칸에 쉼표로 뭉쳐 있으면, 모든 게시물의 그 칸을 일일이 열어서 글자 안에 '제주'가 들었는지 뜯어봐야 해요. 게다가 '제주'와 '제주도'를 헷갈리거나, 쉼표 없이 붙여 쓴 걸 잘못 읽기도 쉽죠. 데이터가 많아질수록 느리고 부정확해집니다.

반대로 "post 7 → 제주"처럼 한 줄씩 떼어두면, "제주인 줄을 다 골라줘" 한 번이면 끝이에요. 정확하고 빠르죠. 그래서 다중값은 편함을 조금 포기하더라도 나눠 담는 게 좋은 설계예요. 이걸 제대로 다듬는 방법이 바로 뒤에 배울 정규화랍니다.


Step 7: "값의 울타리 — 도메인(자료형·길이·제약)"

지난 시간 마지막에 논리 스키마 명세표를 그리면서 제가 이런 말을 했어요. "자료형이나 길이는 일부러 안 적었어요. 그건 다음 시간 도메인을 배울 때 채워 넣습니다." 약속대로, 오늘 그 빈칸을 채울 차례예요.

도메인 — 속성이 가질 수 있는 값의 범위

도메인(domain) 은 한 속성이 가질 수 있는 값의 범위예요. 아무 값이나 들어갈 수 있는 게 아니라, 정해진 울타리 안의 값만 허용하는 거죠. 도메인은 보통 세 가지로 정합니다.

  • 자료형(data type) — 숫자인가, 문자인가, 날짜인가. member_id는 숫자, username은 문자, created_at은 날짜·시각이죠.
  • 길이(length) — 최대 몇 글자까지. username은 30자까지, bio는 150자까지처럼요.
  • 제약(constraint) — 그 밖의 규칙이에요. 비어 있어도 되는지(NULL 허용), 값의 범위(좋아요 수는 0 이상), 정해진 목록 중 하나여야 하는지 등.

종이 서류의 칸을 떠올리면 쉬워요. '생년월일' 칸엔 날짜만, 8자리로 적게 돼 있고, '전화번호' 칸엔 숫자만 들어가죠. 칸마다 들어갈 수 있는 값의 울타리가 정해져 있잖아요. 그 울타리가 바로 도메인이에요.

속성 자료형 길이 제약 (값의 울타리)
member_id 숫자 비어 있을 수 없음 · 유일
username 문자(영문) 30자 비어 있을 수 없음 · 유일
nickname 문자(한글) 30자 비어 있을 수 없음
bio 문자 150자 비어 있어도 됨(NULL 허용)
created_at 날짜·시각 비어 있을 수 없음
like_count 숫자 0 이상

지난 시간 비워뒀던 그 자료형 칸이 이렇게 채워졌어요. Oracle에서는 문자에 VARCHAR2, 숫자에 NUMBER 같은 이름을 붙이는데, 실제 표에 이 울타리를 거는 건 C 카테고리에서 CREATE TABLE로 직접 해봅니다. 오늘은 "속성마다 값의 범위가 정해져 있다"는 개념을 잡는 게 목표예요.

★빈출 — SQLD에서 "도메인이 무엇이냐"를 묻는 문제가 단골이에요. 핵심은 도메인이 자료형 하나만 가리키는 게 아니라, 자료형 + 길이 + 제약을 합친 '값의 집합' 이라는 점이에요. "속성이 가질 수 있는 값의 범위"라는 한 줄로 기억해 두세요.

💡 한 줄 정리

도메인은 속성이 가질 수 있는 값의 범위로, 자료형(숫자·문자·날짜)·길이(최대 글자 수)·제약(NULL 허용·값 범위 등)을 합친 것이다. 지난 시간 비워둔 명세표의 자료형 칸이 바로 도메인이었다.

🙋 학생 질문 — "튜터님, bio는 왜 비어 있어도 되게 했어요? 다 채우게 하면 안 돼요?"

좋은 관찰이에요. 지난 시간에도 bio(자기소개)에 빈칸이 있는 회원들이 있었죠.

이유는 간단해요. 현실에서 자기소개를 꼭 쓰는 사람만 있는 건 아니거든요. 누구는 한 줄 적고, 누구는 그냥 비워두죠. 만약 도메인 제약을 "반드시 채워야 함"으로 걸어두면, 자기소개를 안 쓴 사람은 아예 가입을 못 하게 돼요. 그건 현실과 안 맞죠. 그래서 bio는 "비어 있어도 됨"으로 울타리를 느슨하게 둡니다.

반대로 username(아이디)은 비어 있으면 안 돼요. 아이디가 없으면 그 회원을 가릴 수가 없으니까요. 이렇게 "이 값은 꼭 있어야 하나, 없어도 되나"를 정하는 게 도메인의 제약이에요. 비어 있는 값을 데이터베이스에서 뭐라고 부르고 어떻게 다루는지는, 한참 뒤에 NULL이라는 이름으로 더 깊이 배웁니다.


Step 8: "인스타그램 다시 — 다섯 엔터티 명세를 속살까지 완성"

자, 오늘의 마지막 시간이에요. 지난 시간엔 다섯 엔터티의 박스와 관계만 그렸다면, 오늘은 그 박스 안을 종류·속성·도메인까지 꽉 채워봅니다. 흩어져 배운 조각들을 한 그림으로 모으는 순간이에요.

엔터티 박스를 꽉 채우면

Member 하나를 예로, 오늘 배운 걸 다 끼워 넣으면 이렇게 됩니다.

텍스트
   엔터티 박스가 꽉 찬 모습 — Member 예

   ┌──────────────┐
   │  Member      │
   │   member_id  │    설계 속성 · 식별자
   │   username   │    기본 속성
   │   nickname   │    기본 속성
   │   bio        │    기본 속성 (NULL 허용)
   └──────────────┘
   분류  : 유형 · 기본 · 독립 엔터티                                    Step 1~3
   도메인: username 문자 30자(유일) · nickname 문자 30자 · bio 문자 150자(NULL 허용)    Step 7

박스 안엔 속성과 그 종류가, 박스 밖엔 엔터티 분류와 도메인이 붙었죠. 지난 시간 텅 비었던 박스가 이렇게 속살까지 꽉 찼어요.

다섯 엔터티 통합 명세 (논리 모델, 진화 버전)

지난 시간 그린 논리 스키마 명세표에 오늘 배운 분류·속성 종류·도메인을 더하면 이렇게 진화합니다.

엔터티 분류 (유무형·발생·독립종속) 주요 속성 (종류) 식별자
Member 유형 · 기본 · 독립 username·nickname·bio (기본) member_id (설계)
Post 유형 · 중심 · 종속 caption·created_at (기본), like_count (파생) post_id (설계)
Comment 사건 · 행위 · 종속 content·created_at (기본) comment_id (설계)
Like 사건 · 행위 · 종속 created_at (기본) member_id + post_id (복합)
Follow 사건 · 행위 · 종속 created_at (기본) follower + following (복합)

한눈에 보면 규칙이 보여요. Member만 독립이고 나머지는 다 무언가에 종속이죠. 식별자는 대부분 설계 속성(member_id 같은 일련번호)인데, Like와 Follow만 두 속성을 묶은 모습이에요. 이 "두 속성을 묶은 식별자"가 지난 시간 미뤄둔 M:N과 이어지는 실마리예요.

종이 설계에서 진짜 표로 — 그다음 단계 예고

여기까지가 오늘 완성한 논리 모델이에요. 엔터티를 고르고(B-1), 종류·이름·속성·도메인까지 채웠으니(B-2), 이제 남은 건 이 설계도를 진짜 Oracle 표로 만드는 일이죠. 그건 C 카테고리에서 CREATE TABLE로 직접 합니다. 오늘 채운 도메인(자료형·길이·제약)이 그때 그대로 표의 규칙으로 내려앉아요.

💡 한 줄 정리

오늘 배운 엔터티 분류·속성 종류·도메인을 모두 적용하면, 지난 시간의 빈 박스가 속살까지 채워진 다섯 엔터티 명세가 된다. Member만 독립이고, Like·Follow의 식별자는 두 속성을 묶은 복합 모습인데, 이는 다음 시간 식별자·관계로 이어진다.

🙋 학생 질문 — "튜터님, 이렇게 다 채웠으면 이제 진짜 표로 만들면 되는 건가요?"

거의 다 왔어요! 오늘 채운 명세표는 사실상 표의 설계도 완성본에 가까워요. 각 엔터티가 어떤 속성을 갖고, 무엇으로 식별하고, 각 속성에 어떤 값이 들어갈 수 있는지(도메인)까지 다 정했으니까요.

다만 두 가지가 아직 남았어요. 하나는 Like·Follow처럼 두 속성을 묶은 식별자와, 엔터티끼리 식별자를 빌려주고 빌려오는 관계예요. 이건 다음 시간에 정식으로 다룹니다. 또 하나는 이 설계도를 진짜 CREATE TABLE 명령으로 옮기는 일인데, 그건 C 카테고리에서 SQL 문법을 차근차근 배우며 해요.

그러니 오늘 명세를 꼼꼼히 채워둔 게 헛수고가 아니에요. 다음 시간에 관계와 식별자만 정리하면, 곧장 진짜 표로 내려갈 준비가 끝납니다. 설계가 탄탄할수록 표 만들기는 그 설계를 받아 적는 일에 가까워져요.


마무리

오늘 수고 많으셨어요! 지난 시간에 골라둔 다섯 박스가, 이제는 "이건 유형·기본·독립 엔터티고, 속성은 기본·설계·파생으로 나뉘며, 각 속성엔 이런 도메인이 있다"까지 또렷이 설명할 수 있는 설계도가 됐죠. 엔터티를 발견하는 사람에서, 그 속살까지 빚어내는 사람으로 한 걸음 더 넘어왔습니다.

오늘 배운 핵심 세 가지

  • 💡 하나 — 엔터티는 세 관점으로 분류한다. 유무형(유형·개념·사건), 발생 시점(기본·중심·행위), 독립·종속. 한 엔터티가 여러 기준으로 동시에 분류된다(Member = 유형·기본·독립).
  • 💡 — 속성에도 종류가 있다. 값의 출처로는 기본·설계·파생, 쪼갬·값 개수로는 단일·복합·다중값. 복합·다중값은 그대로 두지 않고 쪼개거나 나눈다.
  • 💡 — 도메인은 속성이 가질 수 있는 값의 범위(자료형·길이·제약)다. 좋은 이름(현업 용어·단수 명사·의미 명확)과 도메인을 채우면 엔터티 명세가 완성된다.

다음 시간 예고

오늘 엔터티 박스를 종류·이름·속성·도메인까지 꽉 채웠어요. 그런데 채우다 보니 두 가지를 자꾸 "다음 시간"으로 미뤘죠.

하나는 Like·Follow의 식별자가 두 속성을 묶은 복합 모습이었던 것. 또 하나는 독립·종속이 "식별자를 스스로 갖느냐, 부모에게서 빌려오느냐"로 갈린다고 한 것. 다음 시간엔 이 둘을 정면으로 다룹니다. 식별자에 어떤 종류가 있는지(본질·인조, 단일·복합, 주식별자·보조식별자), 주식별자가 갖춰야 할 네 가지 조건(유일·최소·불변·존재), 그리고 엔터티끼리 식별자를 빌려주고 빌려오는 식별 관계·비식별 관계까지요. 지난 시간 미뤄둔 M:N의 좋아요·팔로우를 표로 푸는 이야기도 여기서 이어져요. ★빈출 모듈이니 단단히 준비하고 오세요.


과제

오늘 과제도 SQL을 짜는 게 아니라, 종이 위에서 분류하고 판단해 보는 거예요. 정답을 맞히는 것보다 "왜 그렇게 봤는지"가 더 중요합니다.

[기초] 새 엔터티를 세 관점으로 분류해보기

지난 시간 [기초] 과제에서 만든 새 엔터티(예: 스토리, 알림)를 하나 떠올려 보세요. 그 엔터티를 오늘 배운 세 관점으로 분류해 보세요. (1) 유형·개념·사건 중 무엇인가, (2) 기본·중심·행위 중 무엇인가, (3) 독립인가 종속인가. 각각 왜 그렇게 봤는지 이유를 한 줄씩 적어보세요. 힌트: "이게 무엇으로부터 생기는가"를 먼저 물어보면 (2)와 (3)이 같이 풀려요.

[응용] 다중값·복합 속성을 찾아 분해하기

회원 프로필에 새 정보를 추가한다고 해봅시다. (1) 회원이 '관심사'를 여러 개 고를 수 있게 한다면(예: 여행, 음식, 운동), 이건 어떤 종류의 속성일까요? (2) 회원의 '거주지'를 시·구·동으로 적는다면 어떤 종류일까요? 각각 무슨 속성인지 이름을 붙이고, 그대로 한 칸에 담으면 안 되는 이유와 어떻게 나눠 담을지를 적어보세요.

[심화] 파생 속성을 저장할지 말지 판단하기

게시물의 '좋아요 수'는 좋아요 기록을 세면 나오는 파생 속성이에요. 이걸 (A) 게시물에 숫자로 저장해 두는 방법과, (B) 매번 좋아요 기록을 세서 계산하는 방법이 있어요. 인기 게시물이 1초에 수천 번 조회되는 상황을 가정하고, 두 방법의 장점과 위험을 각각 적어보세요. 그리고 여러분이라면 어떤 경우에 (A)를 고를지 자기 말로 정리해 보세요.


생각해볼 주제

1. 같은 엔터티가 여러 기준으로 동시에 분류되는 게 왜 헷갈리지 않을까

오늘 Member를 두고 "유형이면서 기본이면서 독립"이라고 했어요. 한 대상에 분류 이름이 세 개나 붙은 거죠. 분류 기준이 여러 개라는 게 오히려 설계에 어떻게 도움이 될지 생각해 보세요. 만약 기준을 하나로만 강제한다면 무엇을 놓치게 될까요?

2. 다중값·복합 속성을 그대로 두면 정말 안 될까

"해시태그를 한 칸에 쉼표로 넣기"처럼, 다중값을 그대로 두는 게 입력은 분명 편해요. 그런데도 설계에서는 굳이 나눠 담죠. 편함(입력)과 정합성·검색(사용) 사이의 트레이드오프를 떠올려 보세요. 어떤 상황이라면 "그냥 한 칸에 두는 게 낫다"고 볼 여지도 있을까요?

3. 파생 속성은 도메인의 '제약'으로 무엇을 지켜야 할까

'좋아요 수'는 도메인 제약으로 "0 이상"을 걸어뒀어요. 그런데 좋아요를 누르고 취소하는 일이 빠르게 반복되면, 저장해 둔 그 숫자가 실제 기록과 어긋날 수 있죠. 파생 속성을 저장할 때, 값이 틀어지지 않게 지키려면 무엇을 함께 신경 써야 할지 상상해 보세요. (힌트: 좋아요를 누를 때와 취소할 때, 그 숫자는 누가 언제 고쳐야 할까요?)

✅ 예시 답안정답 보기

과제와 생각해볼 주제의 예시답안입니다. 오늘 과제도 SQL 을 짜는 게 아니라 종이 위에서 분류하고 판단하는 데 초점이 있고, 생각해볼 주제는 자격증·실무 양쪽 관점을 함께 담았어요. 정답을 맞히는 것보다 "왜 그렇게 봤는지"를 설명할 수 있는지가 핵심이라, 아래 예시는 여러 모범 답 중 하나일 뿐입니다.


🎯 [과제 1 예시답안] 새 엔터티를 세 관점으로 분류해보기

채점 포인트

채점 포인트 확인
(1) 유형·개념·사건 중 무엇인지 골랐는가
(2) 기본·중심·행위 중 무엇인지 골랐는가
(3) 독립인지 종속인지 골랐는가
세 판단의 이유를 각각 한 줄씩 적었는가

풀이 예시

지난 시간 만든 새 엔터티 두 가지(Story·Notification)로 풀어볼게요. 같은 인스타 기능이라도 분류가 다르게 나온다는 걸 비교해 보세요.

먼저 Story(24시간 뒤 사라지는 사진·영상)예요.

텍스트
   Story 를 세 관점으로

   ① 유무형 : 유형  — 사진·영상이라는 또렷한 실체
   ② 발생   : 중심  — 회원으로부터 생기고, 반응(답장)을 낳는 콘텐츠
   ③ 독립종속: 종속  — 올린 회원이 없으면 스토리도 존재할 수 없음

다음 Notification(좋아요·댓글이 생기면 뜨는 알림)이에요. 같은 회원 기능인데 분류가 확 달라집니다.

텍스트
   Notification 을 세 관점으로

   ① 유무형 : 사건  — '알림이 떴다'는 일어난 일의 기록
   ② 발생   : 행위  — 좋아요·댓글·팔로우라는 둘 이상에서 생김
   ③ 독립종속: 종속  — 알림 받을 회원과 원인(좋아요 등)이 있어야 존재

판단의 비결은 (2)를 먼저 물어보는 거예요. "이게 무엇으로부터 생기나"를 따지면, 스스로 생기면 기본, 한 부모에서 생기면 중심, 둘 이상에서 생기면 행위로 갈리죠. 그리고 이 답이 (3) 독립·종속과 거의 같이 풀려요. 스스로 생기면 독립, 무언가로부터 생기면 종속이니까요.

💡 튜터의 한마디

이 과제의 핵심은 "한 엔터티에 분류 이름이 여러 개 붙는다"를 몸으로 느끼는 거예요. Story 는 유형이면서 중심이면서 종속이죠. 셋 다 맞습니다. 기준이 다르니까요. 본인이 고른 엔터티에 세 관점의 답이 다 붙었고, 특히 (2) 발생 시점과 (3) 독립·종속이 서로 어긋나지 않는지만 확인하면 됩니다(스스로 생기는데 종속이라거나, 둘 이상에서 생기는데 독립이라면 다시 봐야 해요).


🎯 [과제 2 예시답안] 다중값·복합 속성을 찾아 분해하기

채점 포인트

채점 포인트 확인
(1) 관심사 여러 개를 다중값 속성으로 봤는가
(2) 거주지(시·구·동)를 복합 속성으로 봤는가
그대로 한 칸에 담으면 안 되는 이유를 적었는가
어떻게 나눠 담을지(분해 방법)를 적었는가

풀이 예시

두 속성은 종류가 서로 달라요. 하나는 값이 여럿이고, 하나는 한 값을 쪼갤 수 있는 거죠.

(1) 관심사(여행·음식·운동) → 다중값 속성

한 회원이 관심사를 여러 개 고를 수 있으니 다중값이에요. 한 칸에 "여행, 음식, 운동"이라고 욱여넣으면, 나중에 "운동에 관심 있는 회원 다 찾기"가 글자를 일일이 뜯어봐야 하는 일이 됩니다. 그래서 값마다 한 줄로 떼어내요.

텍스트
   ❌ 한 칸에 :  member 1 의 interests = "여행, 음식, 운동"

   ✅ 한 줄씩 :
        member 1  여행
        member 1  음식
        member 1  운동

(2) 거주지(서울시 강남구 역삼동) → 복합 속성

거주지는 값이 하나처럼 보이지만 시·구·동 세 의미로 쪼갤 수 있죠. 한 칸에 통째로 두면 "강남구에 사는 회원만 찾기"가 어려워져요. 그래서 의미 단위로 쪼갭니다.

텍스트
   거주지 = "서울시 강남구 역삼동"
        │ 쪼개면
        
     city = 서울시 · district = 강남구 · dong = 역삼동

💡 튜터의 한마디

둘을 가르는 질문은 이거예요. "값이 여러 개냐(다중값), 한 값을 쪼갤 수 있냐(복합)?" 관심사는 값 자체가 여럿이라 줄을 나누고, 거주지는 한 값을 의미로 쪼개 칸을 나눕니다. 둘 다 "한 칸에 두지 않는다"는 방향은 같지만, 푸는 모양이 달라요. 이걸 제대로 다듬는 규칙이 곧 배울 정규화이니, 오늘은 "한 칸에 욱여넣지 않는다"는 감각까지만 잡으면 충분합니다.


🎯 [과제 3 예시답안] 파생 속성을 저장할지 말지 판단하기

채점 포인트

채점 포인트 확인
(A) 저장 방식의 장점·위험을 적었는가
(B) 계산 방식의 장점·위험을 적었는가
조회 폭주 상황을 고려해 자기 판단을 정리했는가

풀이 예시

'좋아요 수'는 좋아요 기록을 세면 나오는 파생 속성이에요. 두 방식을 표로 견줘봅시다.

방식 장점 위험
(A) 게시물에 숫자로 저장 조회가 빠름 — 저장된 숫자만 읽으면 끝 좋아요가 늘고 줄 때마다 이 숫자도 같이 안 고치면 실제 기록과 어긋남
(B) 매번 좋아요를 세서 계산 항상 정확 — 원본을 직접 세니 틀릴 일 없음 인기 게시물이 1초에 수천 번 조회되면, 매번 전부 세느라 느려짐

인기 게시물이 1초에 수천 번 조회되는 상황이라면, 매번 세는 (B)는 부담이 너무 커요. 그럴 땐 (A)처럼 저장해 두고 빠르게 보여주는 쪽이 현실적이죠. 대신 좋아요를 누르거나 취소할 때 저장된 숫자도 반드시 함께 고쳐서, 실제 기록과 어긋나지 않게 지켜야 합니다.

저라면 이렇게 갈라요. 조회가 폭주하고 약간의 오차는 잠깐 허용되는 좋아요 수 같은 건 (A) 저장. 반대로 정확성이 결정적이고 변동이 드문 값이라면 굳이 저장해 동기화 부담을 지기보다 (B) 계산을 택하겠습니다.

💡 튜터의 한마디

이 판단에는 정답이 하나로 정해져 있지 않아요. 핵심은 "빠름(저장)과 정확함(계산) 사이의 트레이드오프"를 본인 말로 설명할 수 있느냐예요. 저장을 택하면 속도를 얻는 대신 동기화 책임이 따라오고, 계산을 택하면 정확함을 얻는 대신 비용이 따라오죠. 이 "중복을 일부러 허용해 속도를 얻는" 설계가 바로 뒤에서 배울 반정규화의 핵심 아이디어예요.


🤔 [생각해볼 주제 1] 같은 엔터티가 여러 기준으로 동시에 분류되는 게 왜 헷갈리지 않을까

문제 상황 요약

오늘 Member 를 두고 "유형이면서 기본이면서 독립"이라고 했어요. 한 대상에 분류 이름이 세 개나 붙은 거죠. 처음엔 "그럼 대체 뭐가 맞는 거야?" 싶어 헷갈릴 수 있어요. 분류 기준이 여러 개라는 게 오히려 설계에 어떻게 도움이 될까요?

튜터의 가이드 및 해설

헷갈리지 않는 이유는 간단해요. 세 분류가 서로 다른 질문에 답하기 때문이에요.

  • 유무형은 "형태가 있나?"에 답해요.
  • 발생 시점은 "언제, 무엇으로부터 생기나?"에 답하죠.
  • 독립·종속은 "혼자 설 수 있나?"에 답하고요.

질문이 다르니 답이 여러 개 나오는 게 당연하고, 서로 충돌하지도 않아요. 한 사람을 두고 "남자다", "어른이다", "직장인이다"라고 동시에 말해도 안 헷갈리는 것과 똑같죠. 기준이 다른 분류니까요.

오히려 기준이 여러 개라서 설계에 도움이 돼요. 같은 엔터티를 여러 각도에서 비추는 렌즈가 여러 개 생기는 셈이거든요. "이건 사건 엔터티니까 자주 생기고 사라지겠구나", "행위 엔터티니까 부모가 둘 이상이겠구나" 하고, 분류 하나하나가 그 엔터티를 어떻게 다뤄야 할지 힌트를 줍니다.

만약 기준을 하나로만 강제하면 다른 측면을 놓쳐요. 발생 시점만 본다면 "이게 형태가 있는 안정적 대상인지" 같은 걸 못 챙기죠. 여러 렌즈로 같이 봐야 엔터티의 성격이 입체적으로 드러납니다.

🎯 SQLD는 이렇게 나온다

엔터티 분류를 기준별로 구분하는 문제가 단골이에요. "유무형에 따른 분류 = 유형·개념·사건", "발생 시점에 따른 분류 = 기본·중심·행위"를 짝지어 묻습니다. 함정은 두 기준의 항목을 섞어 내는 보기예요. 예를 들어 "유형·중심·사건" 처럼 서로 다른 기준의 이름을 한 줄에 뒤섞어 놓고 고르게 하죠. 어느 기준의 분류인지부터 가려내는 게 포인트예요.

💡 실무에선

실무에서 "이건 무슨 엔터티야?"를 두고 분류 이름을 깐깐하게 따지는 일은 드물어요. 다만 이 분류 감각은 설계 사고의 도구로 쓰입니다. "행위 엔터티는 자주 변하니 이력 관리를 고려하자", "기본 엔터티는 다른 것들의 부모이니 식별자를 신중히 정하자" 처럼요. 이름을 외우는 것보다, 그 분류가 시사하는 성격을 읽어내는 게 진짜 쓸모예요.


🤔 [생각해볼 주제 2] 다중값·복합 속성을 그대로 두면 정말 안 될까

문제 상황 요약

"해시태그를 한 칸에 쉼표로 넣기"처럼, 다중값을 그대로 두는 게 입력은 분명 편해요. 한 번에 적으면 끝이니까요. 그런데도 설계에서는 굳이 나눠 담죠. 편함(입력)과 정합성·검색(사용) 사이의 트레이드오프를 떠올려 봅시다. 어떤 상황이라면 "그냥 한 칸에 두는 게 낫다"고 볼 여지도 있을까요?

튜터의 가이드 및 해설

먼저 나눠 담는 쪽이 얻는 게 뭔지 분명히 해요. 핵심은 사용할 때예요.

  • 검색이 정확하고 빨라져요. "제주 해시태그 달린 게시물"을 한 번에 골라낼 수 있죠.
  • 수정이 깔끔해요. 태그 하나를 바꾸거나 지울 때, 그 줄만 손대면 됩니다.
  • 집계가 쉬워요. "가장 많이 쓰인 해시태그"를 세는 것도 줄 단위라 간단하죠.

반대로 한 칸에 두면 입력은 한 번에 끝나 편하지만, 위 세 가지가 전부 어려워져요. 글자를 일일이 뜯어봐야 하고, '제주'와 '제주도'를 헷갈리고, 한 태그만 지우기도 번거롭죠. 즉 입력의 편함을 얻는 대신 사용의 불편을 떠안는 거예요. 데이터는 보통 한 번 넣고 여러 번 쓰니, 사용 쪽을 챙기는 게 대개 이득입니다.

그래도 "한 칸에 두는 게 낫다"고 볼 여지가 아예 없진 않아요. 그 값을 검색하거나 수정할 일이 전혀 없고, 그냥 통째로 보여주기만 한다면요. 예를 들어 어쩌다 한 번 들여다보는 기록성 메모라면, 굳이 나누는 수고가 아까울 수 있죠. 판단의 기준은 "이 값을 쪼개서 검색·수정·집계할 일이 있는가"예요. 있다면 나누고, 정말 없다면 둘 수도 있는 거죠.

🎯 SQLD는 이렇게 나온다

이 주제는 곧 배울 제1정규형(1NF) 의 씨앗이에요. "모든 속성은 더 쪼갤 수 없는 하나의 값(원자값)을 가져야 한다"가 1NF 의 정의인데, 다중값·복합 속성은 이걸 어기는 대표 사례죠. "한 칸에 여러 값을 담아도 된다"는 식의 보기가 오답 함정으로 나옵니다. 1NF 의 단계별 분해는 정규화 모듈에서 정식으로 다뤄요.

💡 실무에선

기본은 "나눈다"예요. 검색·집계할 값이라면 거의 항상 줄을 나누거나 별도로 떼어냅니다. 다만 검색할 일이 전혀 없는 부가 정보(설정값 묶음, 로그성 데이터 등)는 한 칸에 통째로 담아두기도 해요. 모던 데이터베이스가 이런 값을 다루는 방법은 강의 맨 뒤 모던 동향에서 살짝 맛봅니다. 그래도 출발점은 "쪼갤 값인가 아닌가"를 먼저 묻는 거예요.


🤔 [생각해볼 주제 3] 파생 속성은 도메인의 '제약'으로 무엇을 지켜야 할까

문제 상황 요약

'좋아요 수'는 도메인 제약으로 "0 이상"을 걸어뒀어요. 그런데 좋아요를 누르고 취소하는 일이 빠르게 반복되면, 저장해 둔 그 숫자가 실제 기록과 어긋날 수 있죠. 파생 속성을 저장할 때, 값이 틀어지지 않게 지키려면 무엇을 함께 신경 써야 할까요?

튜터의 가이드 및 해설

파생 속성을 저장하는 순간, 원본(좋아요 기록)과 파생값(좋아요 수)이라는 두 곳에 같은 사실이 적히는 셈이에요. 이 둘이 어긋나지 않게 지키는 게 핵심이죠.

가장 중요한 건 "원본을 고칠 때 파생값도 반드시 함께 고친다"예요. 좋아요를 누르면 좋아요 기록을 한 줄 추가하면서 좋아요 수도 1 올리고, 취소하면 기록을 지우면서 수도 1 내려요. 둘 중 하나만 하고 다른 하나를 빠뜨리면, 그 순간 숫자가 실제와 어긋납니다.

텍스트
   좋아요를 누를 때 — 두 가지를 반드시 함께

    Like 기록 추가 :  minji  post 7
    post 7 의 like_count : 2  3   (는 같이, 또는 둘 다 안 됨)

   ⚠ 만 하고  를 빠뜨리면  기록은 3건인데 숫자는 2 (어긋남)

그리고 "누가·언제 고치나"도 정해둬야 해요. 좋아요를 누르는 그 동작 안에서, 기록 추가와 숫자 갱신이 한 묶음으로 일어나야 합니다. 여러 사람이 동시에 같은 게시물에 좋아요를 누를 때도 숫자가 꼬이지 않게요. 이렇게 "여러 작업을 전부 되거나 전부 안 되게 묶는" 장치를 다음 모델링 모듈에서 트랜잭션이라는 이름으로 만나요.

정리하면, 파생 속성의 제약은 "0 이상" 같은 값의 범위만이 아니에요. "원본이 바뀌면 파생값도 같은 시점에 함께 바뀐다"는 약속까지 지켜야 비로소 그 숫자를 믿을 수 있습니다.

🎯 SQLD는 이렇게 나온다

파생 속성의 정의("다른 속성·데이터로부터 계산되어 나온 속성")와, 이를 저장하는 게 곧 반정규화(중복을 일부러 허용)라는 점을 묻습니다. "파생 속성은 계산식이 있어 정합성 관리가 필요하다"가 출제 포인트예요. 함정은 "파생 속성은 항상 저장해야 한다" 또는 "파생 속성은 절대 저장하면 안 된다"는 식의 단정적 보기죠. 정답은 "상황에 따른 트레이드오프"입니다.

💡 실무에선

좋아요 수·조회 수·팔로워 수처럼 조회가 잦은 파생값은 대개 저장해 두고 빠르게 보여줘요. 대신 원본이 바뀌는 동작과 한 묶음으로 숫자를 갱신하거나, 트래픽이 아주 크면 별도의 빠른 저장소(캐시)에 따로 세어두기도 합니다. 어느 쪽이든 "원본과 파생값을 어떻게 어긋나지 않게 지킬까"가 늘 따라오는 숙제예요.

전체 목록 데이터베이스