관계형 데이터베이스에서 목록을 사용해도 괜찮습니까?


94

프로젝트 개념을 다루기 위해 데이터베이스를 설계하려고 시도했으며 뜨거운 논쟁의 여지가있는 것처럼 보였습니다. 필자는 몇 가지 기사와 스택 오버플로 답변을 읽었으며 필드에 ID 목록 등을 저장하는 것은 결코 불가능하거나 거의 불가능하다는 것을 나타냅니다. 모든 데이터는 관계형이어야합니다.

그러나 내가 겪고있는 문제는 작업 할당자를 만들려고한다는 것입니다. 사람들은 작업을 만들어 여러 사람에게 할당하고 데이터베이스에 저장합니다.

물론, 이러한 작업을 "개인"에 개별적으로 저장하는 경우 한 사람에게 0에서 100 개의 작업이 할당 될 수 있기 때문에 수십 개의 더미 "TaskID"열이 있고이를 미세 관리해야합니다.

그런 다음 "작업"테이블에 작업을 저장하면 수십 개의 더미 "PersonID"열이 있고이를 이전과 같은 문제로 미세 관리해야합니다.

이와 같은 문제의 경우, 하나의 양식을 취하는 ID 목록을 저장해도 괜찮습니까? 아니면 원칙을 어 기지 않고 달성 할 수있는 다른 방법을 생각하지 않습니까?


22
나는이 태그가 "관계형 데이터베이스는"그래서 난 그냥 언급하지 대답로 떠날거야 실현하지만, 다른 유형의 데이터베이스에는 않습니다 목록을 저장하는 의미를. Cassandra는 조인이 없으므로 마음에 듭니다.
Captain Man

12
연구하고 여기에서 요구하는 좋은 일! 사실, 첫 번째 정규 형식을 위반하지 않는 '권장 사항'은 표준 패턴이있는 다른 관계형 접근 방식, 즉 "다 대다"관계를 생각해 내야하기 때문에 실제로 당신에게 잘되었습니다. 사용해야하는 관계형 데이터베이스.
JimmyB

6
"괜찮아."네 .. 다음에 나오는 것이 무엇이든지 대답은 '예'입니다. 정당한 사유가있는 한. 모범 사례를 위반하도록 강요하는 유스 케이스가 항상 있습니다. (귀하의 경우에는 절대로해서는 안됩니다)
xyious

3
현재 태그 목록을 저장하기 위해 배열 ( 구분 문자열이 아닌 a VARCHAR ARRAY)을 사용하고 있습니다. 아마도 나중에 줄에 저장되는 방식은 아니지만 프로토 타이핑 단계에서 목록이 매우 유용 할 수 있습니다. 다른 작업을 수행하십시오.
Nic Hartley

3
@ 벤 " (그들은 색인되지 않습니다하지만) "- 포스트 그레스의는, (내가 확인하지 않은하지만 아마도 XML) JSON 컬럼에 대한 몇 가지 쿼리 입니다 색인.
Nic Hartley

답변:


249

조사해야 할 핵심 단어와 핵심 개념은 데이터베이스 정규화 입니다.

할 일은 개인 또는 작업 테이블에 할당에 대한 정보를 추가하는 것이 아니라 관련 정보와 함께 해당 할당 정보가있는 새 테이블을 추가하는 것입니다.

예를 들어, 다음 테이블이 있습니다.

명:

+ −−−− + −−−−----
| 아이디 | 이름 |
+ ==== + =========== +
| 1 | 알프레드 |
| 2 | 예베 디아 |
| 3 | 야곱 |
| 4 | 에스겔 |
+ −−−− + −−−−----

작업 :

+ ------
| 아이디 | 이름 |
+ ==== + ===================== +
| 1 | 닭을 먹이 |
| 2 | 쟁기 |
| 3 | 젖 짜기 소 |
| 4 | 헛간을 제기 |
+ ------

그런 다음 과제가 포함 된 세 번째 테이블을 만듭니다. 이 테이블은 사람과 작업 간의 관계를 모델링합니다.

+ −−−− + −−−−−−−−−−− + −−−−−−−−− +
| 아이디 | PersonId | TaskId |
+ ==== + =========== + ========= +
| 1 | 1 | 3 |
| 2 | 3 | 2 |
| 3 | 2 | 1 |
| 4 | 1 | 4 |
+ −−−− + −−−−−−−−−−− + −−−−−−−−− +

그런 다음 데이터베이스는 PersonId 및 TaskId가 해당 외래 항목의 유효한 ID 여야하도록 외래 키 제약 조건을 갖습니다. 첫 번째 행의 경우, 우리가 볼 수 PersonId is 1있도록, 알프레드 에 할당되고 TaskId 3, 착유 소 .

여기서 볼 수있는 것은 작업 또는 사람마다 원하는 수만큼 또는 많은 수의 과제를 가질 수 있다는 것입니다. 이 예에서 Ezekiel 에는 작업이 할당되지 않았고 Alfred 에 2가 할당되었습니다. 100 명의 사용자를 가진 하나의 작업이 SELECT PersonId from Assignments WHERE TaskId=<whatever>;있는 경우 다양한 행인이 지정된 100 개의 행이 생성됩니다. WHEREPersonId에서 해당 개인에게 지정된 모든 작업을 찾을 수 있습니다 .

ID를 이름 및 작업으로 바꾸는 쿼리를 반환하려면 테이블에 참여하는 방법을 배우십시오.


86
더 많은 것을 배우고 자하는 키워드는 " 다 대다 관계 "
BlueRaja-Danny Pflughoeft

34
Thierrys의 의견을 조금 더 자세히 설명하려면 : X 만 필요하기 때문에 정규화 할 필요가 없으며 ID 목록을 저장하는 것이 매우 간단 하지만 나중에 확장 될 수있는 시스템의 경우 정규화하지 않은 것을 후회하게됩니다 일찍이. 항상 정규화하십시오 . 유일한 질문은 일반적인 형태에 관한 것입니다
Jan Doggen

8
@Jan과 동의-더 나은 판단에 대해서는 팀이 디자인 지름길을 다시 가져 와서 "확장 할 필요가없는"것 대신 JSON을 저장하도록 허용했습니다. 그것은 6 개월 FML처럼 지속되었습니다. 그런 다음 업그레이드 프로그램은 JSON을 시작해야 할 구성표로 마이그레이션하기 위해 열심히 싸웠습니다. 나는 정말로 더 잘 알고 있어야했다.
궤도에서 가벼움 경주

13
@ 중복 제거기 : 이것은 정원 다양성, 자동 증가 정수 기본 키 열을 나타냅니다. 꽤 전형적인 것들.
whatsisname

8
@whatsisname Persons 또는 Tasks 테이블에서 동의합니다. 이미 대리 키가있는 다른 두 테이블 사이의 다 대다 관계를 나타내는 것이 유일한 목적인 브리지 테이블에서? 나는 정당한 이유없이 하나를 추가하지 않을 것입니다. 쿼리 나 관계에서 사용되지 않으므로 오버 헤드입니다.
jpmc26

35

여기에 두 가지 질문이 있습니다.

먼저, 목록을 저장해도 괜찮은지를 열로 직렬화했는지 묻습니다. 예, 괜찮습니다. 프로젝트가 요구하는 경우. 카탈로그 페이지의 제품 구성 요소를 예로들 수 있는데, 각 구성 요소를 개별적으로 추적하려고하지 않습니다.

불행히도 두 번째 질문은보다 관계적인 접근 방식을 선택해야하는 시나리오를 설명합니다. 3 개의 테이블이 필요합니다. 하나는 사람들을 위해, 하나는 작업을 위해, 하나는 어떤 작업이 어떤 사람들에게 할당되는지 목록을 유지합니다. 마지막 하나는 기본 키, 작업 ID 및 개인 ID에 대한 열이있는 세로 / 개인 / 작업 조합 당 한 행입니다.


9
참고 한 성분 예는 표면에 맞습니다. 그러나이 경우에는 평문입니다. 프로그래밍 의미의 목록이 아닙니다 (문자열이 분명히 그렇지 않은 문자 목록임을 의미하지 않는 한). 데이터를 "ID 목록"(또는 "[..] 목록")으로 설명하는 OP는 특정 시점에서이 데이터를 개별 개체로 처리하고 있음을 나타냅니다.
Flater

10
@Flater : 그러나 그것은 목록입니다. 웹 페이지, 일반 텍스트 문서, 모바일에 항목이 올바르게 표시되도록하려면 HTML 목록, 마크 다운 목록, JSON 목록 등으로 (다양하게) 형식을 다시 지정할 수 있어야합니다. 응용 프로그램 ... 그리고 당신은 실제로 일반 텍스트로는 그렇게 할 수 없습니다.
케빈

12
@Kevin 그것이 당신의 목표라면, 재료를 테이블에 저장함으로써 훨씬 쉽고 빠르게 달성됩니다! 나중에 사람들이 말할 것입니다 ... 아, 나는 추천 대체품 을 원하거나 땅콩, 글루텐 또는 동물성 단백질이 없는 모든 요리법을 찾는 것과 같은 것을 모릅니다 ...
Dan 브론

10
@ DanBron : YAGNI. 지금 우리는 UI 로직을 더 쉽게 만들기 때문에 목록 만 사용하고 있습니다. 우리가 필요로하거나 비즈니스 로직 계층의 목록과 같은 행동을해야 할 경우, 다음 이를 별도의 테이블로 정규화해야합니다. 테이블과 조인은 반드시 비싸지는 않지만 무료는 아니며 요소 순서 ( "재료의 순서에 관심이 있습니까?") 및 추가 정규화 ( "3 개의 달걀을 돌릴 예정입니까?" '(소금', 3)? '소금, 맛'은 어떻습니까?
케빈

7
@ 케빈 : YAGNI는 여기에 꽤 잘못되었습니다. 귀하는 여러 가지 방법으로 (HTML, markdown, JSON) 목록을 변환 할 수 있어야하므로 목록 의 개별 요소가 필요 하다고 주장 합니다 . 데이터 저장 및 "목록 처리"응용 프로그램이 독립적으로 개발 된 두 응용 프로그램이 아닌 경우 (별도의 응용 프로그램 계층! = 별도의 응용 프로그램임을 유의하십시오) 데이터를 쉽게 사용할 수있는 형식으로 데이터를 저장하도록 데이터베이스 구조를 항상 만들어야합니다. -추가 파싱 / 변환 논리를 피하면서.
Flater

22

귀하가 설명하는 것은 귀하의 경우 Person와 사이에 "다 대다"관계라고합니다 Task. 일반적으로 "링크"또는 "교차 참조"테이블이라고도하는 세 번째 테이블을 사용하여 구현됩니다. 예를 들면 다음과 같습니다.

create table person (
    person_id integer primary key,
    ...
);

create table task (
    task_id integer primary key,
    ...
);

create table person_task_xref (
    person_id integer not null,
    task_id integer not null,
    primary key (person_id, task_id),
    foreign key (person_id) references person (person_id),
    foreign key (task_id) references task (task_id)
);

2
task_id작업별로 필터링 된 쿼리를 수행하는 경우 먼저 인덱스를 추가 할 수도 있습니다.
jpmc26

1
브리지 테이블이라고도합니다. 또한 각 열에 색인을 권장하지만 ID 열이 없기 때문에 추가로 플러스를 줄 수 있기를 바랍니다.
jmoreno

13

... ID 등을 필드에 저장하는 것은 결코 (또는 거의) 괜찮습니다.

단일 필드에 둘 이상의 데이터 항목을 저장할 있는 유일한 경우는 해당 필드가 단일 엔티티 로만 사용되며 더 작은 요소로 구성된 것으로 간주 되지 않는 경우 입니다. 예를 들어 BLOB 필드에 저장된 이미지가 있습니다. 그것은 많은 작은 요소 (바이트)로 구성되어 있지만 데이터베이스에는 아무런 의미가 없으며 모두 함께 사용할 수 있습니다 (최종 사용자에게는 예쁘게 보입니다).

"목록"은 정의상 더 작은 요소 (항목)로 구성되므로 여기에는 해당되지 않으며 데이터를 정규화해야합니다.

...이 작업을 "개인"에 개별적으로 저장하면 수십 개의 더미 "TaskID"열이 있어야합니다 ...

아니요 . Person과 Task 사이의 교차 테이블 (약한 엔티티)에 몇 개의 행 이 있습니다 . 데이터베이스는 실제로 많은 행을 다루는 데 능숙합니다. 그들은 실제로 많은 [반복 된] 열을 다루는 데 아주 쓰레기입니다.

whatsisname으로 주어진 좋은 분명한 예.


4
실제 시스템을 만들 때 "절대로 말하지 마십시오"는 살기에 아주 좋은 규칙입니다.
l0b0

1
많은 경우에, 목록을 정규화 된 형태로 유지 또는 검색하는 요소 별 비용은 목록의 각 항목이이를 포함하는 마스터 항목의 ID를 보유해야하기 때문에 항목을 한 방울로 유지하는 비용을 크게 초과 할 수 있습니다. 실제 데이터 외에 목록 내에서 관련 위치와 위치가 있습니다. 코드가 전체 목록을 업데이트하지 않고 일부 목록 요소를 업데이트 할 수있는 이점이있는 경우에도 모든 항목을 얼룩으로 저장하고 무언가를 다시 작성할 때마다 모든 항목을 다시 쓰는 것이 더 저렴할 수 있습니다.
supercat

4

사전 계산 된 특정 필드에서 합법적 일 수 있습니다.

쿼리 중 일부가 비싸고 데이터베이스 트리거를 사용하여 자동으로 사전 계산 된 필드를 업데이트하기로 결정한 경우 목록을 열 안에 유지하는 것이 합법적 일 수 있습니다.

예를 들어, UI에서 그리드보기를 사용하여이 목록을 표시하려면 두 번 클릭 한 후 각 행이 전체 목록으로 전체 세부 사항을 열 수 있습니다.

REGISTERED USER LIST
+------------------+----------------------------------------------------+
|Name              |Top 3 most visited tags                             |
+==================+====================================================+
|Peter             |Design, Fitness, Gifts                              |
+------------------+----------------------------------------------------+
|Lucy              |Fashion, Gifts, Lifestyle                           |
+------------------+----------------------------------------------------+

클라이언트가 새 기사를 방문 할 때 트리거되거나 예약 된 작업에 의해 두 번째 열을 업데이트합니다.

이러한 필드를 일반 텍스트로 검색 할 수 있도록 만들 수도 있습니다.

이러한 경우 목록을 유지하는 것이 합법적입니다. 최대 필드 길이를 초과하는 경우를 고려해야합니다.


또한 Microsoft Access를 사용하는 경우 제공된 다중 값 필드 가 또 다른 특별한 사용 사례입니다. 필드에서 목록을 자동으로 처리합니다.

그러나 다른 답변에 표시된 표준 정규화 된 양식으로 언제든지 돌아갈 수 있습니다.


요약 : 일반적인 형태의 데이터베이스는 데이터 모델링의 중요한 측면을 이해하는 데 필요한 이론적 모델입니다. 그러나 물론 정규화는 데이터 검색의 성능이나 기타 비용을 고려하지 않습니다. 그것은 이론적 모델의 범위를 벗어납니다. 그러나 실제 구현에는 종종 목록 또는 기타 사전 계산 (및 제어 된) 중복 저장이 필요합니다.

위의 관점에서, 실제 구현에서, 우리는 완벽한 정규 형식에 의존하고 20 초 동안 실행하는 쿼리를 선호하거나 0.08 초가 걸리는 사전 계산 된 값에 의존하는 동등한 쿼리를 선호합니까? 소프트웨어 제품이 속도 저하로 고발되는 것을 좋아하는 사람은 없습니다.


1
사전 계산 된 항목이 없어도 합법적 일 수 있습니다. 나는 데이터가 올바르게 저장되는 몇 번을했지만 성능상의 이유로 캐시 된 결과를 기본 레코드에 채우는 것이 유용합니다.
Loren Pechtel

@LorenPechtel – 예, 사전 계산 된 용어를 사용할 때 필요한 곳에 저장된 캐시 값의 경우도 포함됩니다. 복잡한 종속성이있는 시스템에서는 성능을 정상적으로 유지하는 방법입니다. 그리고 적절한 노하우로 프로그래밍 된 경우 이러한 값은 신뢰할 수 있고 항상 동기화됩니다. 대답을 간단하고 안전하게 유지하기 위해 캐싱 사례를 답변에 추가하고 싶지 않았습니다. 어쨌든 downvoted했습니다. :)
miroxlav

@LorenPechtel 사실, 그것은 여전히 ​​나쁜 이유입니다 ... 캐시 데이터는 중간 캐시 저장소에 보관해야하며 캐시가 여전히 유효하지만 쿼리는 절대 메인 DB에 도달해서는 안됩니다.
Tezra

1
@Tezra 아니요, 때로는 보조 레코드의 데이터가 주 레코드에 사본을 넣는 것이 합리적 이도록 종종 필요하다고 말합니다. (내가 한 예로 직원 테이블에는 마지막 시간과 마지막 시간이 포함됩니다. 이들은 표시 목적으로 만 사용되며 실제 계산은 시계 / 시간 기록 레코드가있는 테이블에서 이루어집니다.)
로렌 Pechtel

0

주어진 두 개의 테이블; 우리는 그들을 각각 ID (PersonID, TaskID)를 가진 Person과 Task라고 부를 것입니다 ... 기본 아이디어는 그것들을 묶을 수있는 세 번째 테이블을 만드는 것입니다. 이 테이블을 PersonToTask라고합니다. 최소한 자신의 ID와 다른 두 개의 ID를 가져야합니다. 따라서 누군가를 작업에 할당 할 때; 더 이상 Person 테이블을 업데이트 할 필요가 없으며 PersonToTaskTable에 새 행을 삽입하면됩니다. 유지 관리가 더 쉬워졌습니다. TaskID를 기반으로 작업을 삭제하면 Person 테이블과 관련 구문 분석이 더 이상 삭제되지 않습니다.

CREATE TABLE dbo.PersonToTask (
    pttID INT IDENTITY(1,1) NOT NULL,
    PersonID INT NULL,
    TaskID   INT NULL
)

CREATE PROCEDURE dbo.Task_Assigned (@PersonID INT, @TaskID INT)
AS
BEGIN
    INSERT PersonToTask (PersonID, TaskID)
    VALUES (@PersonID, @TaskID)
END

CREATE PROCEDURE dbo.Task_Deleted (@TaskID INT)
AS
BEGIN
    DELETE PersonToTask  WHERE TaskID = @TaskID
    DELETE Task          WHERE TaskID = @TaskID
END

간단한 보고서 나 누가 작업에 배정 되었습니까?

CREATE PROCEDURE dbo.Task_CurrentAssigned (@TaskID INT)
AS
BEGIN
    SELECT PersonName
    FROM   dbo.Person
    WHERE  PersonID IN (SELECT PersonID FROM dbo.PersonToTask WHERE TaskID = @TaskID)
END

물론 더 많은 일을 할 수 있습니다. TaskAssigned 및 TaskCompleted에 DateTime 필드를 추가 한 경우 TimeReport를 수행 할 수 있습니다. 그것은 모두 당신에게 달려 있습니다


0

사람이 읽을 수있는 기본 키를 가지고 있고 테이블 구조의 수직적 특성을 다루지 않고 작업 목록을 원한다면 작동 할 수 있습니다. 즉, 첫 번째 테이블을 훨씬 쉽게 읽을 수 있습니다.

------------------------  
Employee Name | Task 
Jack          |  1,2,5
Jill          |  4,6,7
------------------------

------------------------  
Employee Name | Task 
Jack          |  1
Jack          |  2
Jack          |  5
Jill          |  4
Jill          |  6
Jill          |  7
------------------------

문제는 다음과 같습니다. 작업 목록을 요청시 저장하거나 생성해야하는지 여부는 목록이 필요한 빈도, 데이터 행 수의 정확성, 데이터 사용 방법 등과 같은 요구 사항에 따라 크게 달라집니다. .. 그 후 사용자 경험에 대한 트레이드 오프를 분석하고 요구 사항을 충족시켜야합니다.

예를 들어 2 개의 행을 호출하는 데 걸리는 시간과 2 개의 행을 생성하는 쿼리를 실행하는 시간을 비교합니다. 시간이 오래 걸리고 사용자에게 최신 목록이 필요하지 않은 경우 (* 매일 1 회 미만의 변경이 예상 됨) 저장 될 수 있습니다.

또는 사용자에게 할당 된 작업 기록 기록이 필요한 경우 목록이 저장된 경우에도 의미가 있습니다. 그래서 그것은 실제로 당신이하는 일에 달려 있으며 절대 말하지 마십시오.


당신이 말했듯이, 그것은 모두 데이터를 검색하는 방법에 달려 있습니다. 사용자 이름으로이 테이블을 / only / 조회 한 경우 "목록"필드가 적합합니다. 그러나 이러한 테이블을 쿼리하여 작업 # 1234567에서 누가 작업하고 있는지 확인하고 여전히 성능을 유지할 수 있습니까? 거의 모든 종류의 "find-X-anywhere-in-the-the-field"문자열 함수는 / Table Scan /에 대한 쿼리를 발생시켜 크롤링 속도를 저하시킵니다. 제대로 정규화되고 올바르게 색인화 된 데이터 만 있으면 발생하지 않습니다.
Phill W.

0

다른 테이블을 가져 와서 90도 돌리고 다른 테이블로 구두를 깎습니다.

itemProdcode1, itemQuantity1, itemPrice1 ... itemProdcode37, itemQuantity37, itemPrice37이있는 주문 테이블을 갖는 것과 같습니다. 프로그래밍 방식으로 다루기가 어색한 것 외에도 내일 누군가가 38 가지를 주문하고 싶어 할 것입니다.

'목록'이 실제로 목록이 아닌 경우 (예 : 전체 목록으로 표시되고 각 개별 광고 항목이 명확하고 독립적 인 항목을 참조하지 않는 경우)에만 사용합니다. 이 경우 충분히 큰 일부 데이터 유형으로 모든 것을 채우십시오.

따라서 주문은 목록이고, BOM은 목록 (또는 "옆으로"를 구현하는 데 악몽이 될 수있는 목록의 목록)입니다. 그러나 메모 / 설명과시는 그렇지 않습니다.


0

그것이 "좋지 않다"면, 모든 Wordpress 사이트가 wp_usermeta에 하나의 행에 wp_capabilities, 하나의 행에 dismissed_wp_pointers 목록 및 다른 행을 갖는 목록을 가지고있는 것은 상당히 나쁘다 ...

이 같은 경우 사실 그것은 수도 당신은 거의 항상 원하는 것 같은 속도에 대한 더 나은 목록을 . 그러나 Wordpress는 모범 사례의 완벽한 예라고 알려져 있지 않습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.