데이터베이스 테이블의 열에 목록을 저장하는 방법


114

따라서, 당 관련 질문에 대한 메흐 다드의 대답 , 나는 그것을 얻을 에 "적절한"데이터베이스 테이블의 열 목록을 저장하지 않습니다. 오히려 상기 목록의 요소를 효과적으로 보유하는 다른 테이블을 만든 다음 직접 또는 접합 테이블을 통해 연결해야합니다. 그러나 내가 만들고 싶은 목록의 유형은 (연결된 질문의 과일 과 달리) 고유 한 항목으로 구성됩니다.예). 또한 내 목록의 항목은 명시 적으로 정렬되어 있습니다. 즉, 다른 테이블에 요소를 저장하면 액세스 할 때마다 항목을 정렬해야합니다. 마지막으로, 목록은 기본적으로 원자 적입니다. 목록에 액세스하고 싶을 때마다 목록의 일부가 아닌 전체 목록에 액세스하고 싶을 것입니다. 따라서 데이터베이스 쿼리를 실행하여 일부 목록.

AKX의 솔루션 (위에 링크 됨)은 목록을 직렬화하고 이진 열에 저장하는 것입니다. 그러나 이것은 직렬화와 역 직렬화에 대해 걱정해야한다는 것을 의미하기 때문에 불편한 것 같습니다.

더 나은 해결책이 있습니까? 경우가 없다 더 나은 솔루션은 왜? 이 문제는 때때로 발생해야하는 것 같습니다.

... 내가 어디에서 왔는지 알려주는 약간의 추가 정보입니다. 일반적으로 SQL과 데이터베이스를 이해하기 시작하자마자 LINQ to SQL을 사용하게되었으므로 이제는 개체가 어떻게 작동하는지 생각할 필요없이 프로그래밍 개체 모델을 처리 할 것으로 예상하기 때문에 약간 버릇이 생겼습니다. 쿼리되거나 데이터베이스에 저장됩니다.

모두 감사합니다!

남자

업데이트 : 그래서 제가받은 첫 번째 답변에서 "CSV / XML 경로로 갈 수 있지만 ...하지 마십시오!"라는 메시지가 표시됩니다. 이제 이유에 대한 설명을 찾고 있습니다. 몇 가지 좋은 참조를 알려주십시오.

또한 내가하고있는 일에 대한 더 나은 아이디어를 제공하기 위해 : 데이터베이스에 (x, y) 쌍의 목록이있는 Function 테이블이 있습니다. (이 표에는 토론에 중요하지 않은 다른 정보도 있습니다.) (x, y) 쌍 목록의 일부를 볼 필요가 없습니다. 오히려 나는 그것들을 모두 가져 와서 화면에 플롯 할 것입니다. 사용자가 노드를 드래그하여 가끔 값을 변경하거나 플롯에 더 많은 값을 추가 할 수 있습니다.

답변:


182

아니요, 단일 열에 일련의 항목을 저장하는 "더 나은"방법은 없습니다. 관계형 데이터베이스는 행 / 열 조합 당 하나의 값을 저장 하도록 특별히 설계되었습니다 . 둘 이상의 값을 저장 하려면 목록을 저장을 위해 단일 값으로 직렬화 한 다음 검색시 역 직렬화해야합니다. 당신이 말하는 것을 할 수있는 다른 방법은 없습니다 (당신이 말하는 것은 일반적으로 절대로하지 말아야 하는 나쁜 생각 이기 때문입니다 ).

그 목록을 저장하기 위해 다른 테이블을 만드는 것이 어리석은 일이라고 생각하지만 이것이 관계형 데이터베이스가하는 일입니다. 당신은 힘겨운 싸움을 벌이고 있으며 특별한 이유없이 관계형 데이터베이스 설계의 가장 기본적인 원칙 중 하나를 위반하고 있습니다. 방금 SQL을 배우고 있다고 말씀 하셨으므로이 아이디어를 피하고 숙련 된 SQL 개발자가 권장하는 방법 을 따르도록 강력히 권합니다.

위반하는 원칙을 데이터베이스 정규화의 첫 번째 단계 인 첫 번째 정규형 이라고 합니다.

지나치게 단순화 할 위험이있는 데이터베이스 정규화는 데이터 무엇인지를 기반으로 데이터베이스를 정의하는 프로세스 이므로 이에 대해 합리적이고 일관된 쿼리를 작성하고 쉽게 유지 관리 할 수 ​​있습니다. 정규화는 데이터의 논리적 불일치 및 손상을 제한하도록 설계되었으며 여기에는 많은 수준이 있습니다. 데이터베이스 정규화 에 대한 Wikipedia 기사 는 실제로 꽤 좋습니다.

기본적으로 정규화의 첫 번째 규칙 (또는 형식)은 테이블이 관계를 나타내야 함을 나타냅니다. 이는 다음을 의미합니다.

  • 한 행을 다른 행과 구별 할 수 있어야합니다. 즉, 테이블 에 기본 키 역할 을 할 수 있는 항목이 있어야합니다 . 이는 행이 중복되지 않아야 함을 의미합니다.
  • 데이터의 모든 순서는 행의 물리적 순서가 아니라 데이터에 의해 정의되어야합니다 (SQL은 집합 개념을 기반으로합니다. 즉, 사용자가 의존해야하는 유일한 순서는 쿼리에서 명시 적으로 정의하는 순서입니다)
  • 모든 행 / 열 교차에는 하나의 값만 포함되어야합니다.

마지막 요점은 분명히 여기서 두드러진 요점입니다. SQL은 세트를 직접 저장할 수있는 "버킷"을 제공하지 않고 세트를 저장하도록 설계되었습니다. 예, 가능합니다. 아니, 세상은 끝나지 않을 것입니다. 그러나 이미 ORM을 사용하여 즉시 SQL과 함께 제공되는 모범 사례를 이해하는 데 어려움을 겪었습니다. LINQ to SQL은 그래프 계산기와 마찬가지로 환상적입니다. 그러나 같은 맥락에서 그들이 사용하는 프로세스가 실제로 어떻게 작동 하는지를 아는 대신 사용 해서는 안됩니다 .

귀하의 목록은 이제 완전히 "원자 적"일 수 있으며이 프로젝트에서는 변경되지 않을 수 있습니다. 그러나 다른 프로젝트에서 비슷한 일을하는 습관이 생기고 결국에는 (아마도 빨리) 빠르고 쉬운 열 목록을 맞추는 시나리오에 직면하게 될 것입니다. 완전히 부적절한 곳에 접근하십시오. 저장하려는 항목에 대한 올바른 테이블을 만드는 데 추가 작업이 많지 않으며 다른 SQL 개발자가 데이터베이스 디자인을 볼 때 조롱하지 않을 것입니다. 게다가 LINQ to SQL은 사용자의 관계를 확인하고 자동으로 목록에 적절한 개체 지향 인터페이스를 제공합니다 . 비표준적이고 잘못된 데이터베이스 해커를 수행 할 수 있도록 ORM이 제공하는 편의를 포기하는 이유는 무엇입니까?


17
따라서 열에 목록을 저장하는 것은 나쁜 생각이라고 굳게 믿지만 그 이유는 언급하지 않습니다. 방금 SQL로 시작했기 때문에 "이유"에 대한 약간의 설명이 실제로 매우 도움이 될 것입니다. 예를 들어, 내가 "힘든 싸움을 벌이고 있으며 관계형 데이터베이스 설계의 가장 기본적인 원칙 중 하나를 정당한 이유없이 위반하고있다"고한다면 원칙은 무엇입니까? 내가 "좋지 않다"고 언급 한 이유는 무엇입니까? (특히, 내 목록의 정렬 및 원자 특성)
JnBrymn 2010-06-18

6
기본적으로 수년간의 경험이 모범 사례로 압축됩니다. 문제의 기본 원칙은 1st Normal Form으로 알려져 있습니다.
Toby

1
감사합니다 Adam. 매우 유익합니다. 마지막 질문에 좋은 지적입니다.
JnBrymn

8
"[…] 그리고 다른 SQL 개발자가 데이터베이스 디자인을 볼 때 비웃지 않을 것입니다." First Normal Form을 존중하는 데에는 매우 좋은 이유가 있습니다 (그리고 귀하의 답변에 언급되어 있음). 동료 압력 / "이것이 여기서 일이 이루어지는 방식"은 아닙니다 .
Lynn

5
우리는 이미 매일 데이터베이스 열에 많은 목록을 저장합니다. "char"및 "varchar"라고합니다. 물론 Postgres에서는 텍스트라고도합니다. 1NF가 실제로 말하는 것은 어떤 필드의 정보를 더 작은 필드로 나누고 싶지 않다는 것입니다. 그렇게한다면 바보입니다. 따라서 이름을 저장하지 않고 개인 이름, 중간 이름 및 성 (현지화에 따라 다름)을 저장하고이를 하나로 연결합니다. 그렇지 않으면 텍스트 문자열을 전혀 저장하지 않습니다. 반면에 그가 원하는 것은 문자열의 문자열뿐입니다. 그리고 그렇게하는 방법이 있습니다.
Haakon Løtveit

15

SQL을 모두 잊어 버리고 "NoSQL"접근 방식을 사용할 수 있습니다. RavenDB , MongoDBCouchDB 는 가능한 솔루션으로 떠 오릅니다 . NoSQL 접근 방식을 사용하면 관계형 모델을 사용하지 않습니다. 스키마에 제한되지 않습니다.


11

많은 사람들이하는 것을 본 것은 다음과 같습니다 (최선의 접근 방식은 아닐 수 있습니다. 제가 틀렸다면 수정하십시오).

예제에서 사용하는 표는 다음과 같습니다 (표에는 특정 여자 친구에게 부여한 별명이 포함되어 있습니다. 각 여자 친구는 고유 한 ID를가집니다) :

nicknames(id,seq_no,names)

ID 아래에 많은 별명을 저장하려고한다고 가정하십시오. 이것이 우리가 seq_no필드를 포함시킨 이유 입니다.

이제 다음 값을 테이블에 채 웁니다.

(1,1,'sweetheart'), (1,2,'pumpkin'), (2,1,'cutie'), (2,2,'cherry pie')

여자 친구 ID 1에 부여한 모든 이름을 찾으려면 다음을 사용할 수 있습니다.

select names from nicknames where id = 1;

5

간단한 대답 : 목록이 항상 목록으로 사용된다는 확신이있는 경우에만 목록에서 사용되지 않을 문자 (예 : '\ 0')로 목록을 연결하십시오. 텍스트를 입력하고 저장합니다. 그런 다음 검색 할 때 '\ 0'으로 나눌 수 있습니다. 물론이 문제를 해결하는 다른 방법이 있지만 특정 데이터베이스 공급 업체에 따라 다릅니다.

예를 들어 Postgres 데이터베이스에 JSON을 저장할 수 있습니다. 목록이 텍스트이고 더 이상 번거롭지 않고 목록을 원하면 합리적인 타협입니다.

다른 사람들은 직렬화를 제안했지만 직렬화가 좋은 생각이라고는 생각하지 않습니다. 데이터베이스에 대한 깔끔한 부분 중 하나는 서로 다른 언어로 작성된 여러 프로그램이 서로 통신 할 수 있다는 것입니다. 그리고 Java의 형식을 사용하여 직렬화 된 프로그램은 Lisp 프로그램이로드하려는 경우 그다지 잘 수행되지 않습니다.

이런 종류의 일을하는 좋은 방법을 원한다면 일반적으로 배열 또는 유사한 유형을 사용할 수 있습니다. 예를 들어 Postgres는 배열을 유형으로 제공하고 원하는 경우 텍스트 배열을 저장할 수 있으며 JSON을 사용 하는 MySqlMS SQL에 대한 유사한 트릭이 있으며 IBM의 DB2 는 배열 유형도 제공합니다. 자신의 유용한 문서). 이것이 필요하지 않았다면 이것은 그렇게 일반적이지 않을 것입니다.

당신이 그 길을 가면서 잃는 것은 일련의 일들로서 목록의 개념입니다. 최소한 명목상 데이터베이스는 필드를 단일 값으로 취급합니다. 그러나 그것이 당신이 원하는 전부라면, 당신은 그것을해야합니다. 그것은 당신이 스스로 내려야하는 가치 판단입니다.


3

다른 사람들이 말한 것 외에도 지금보다 더 긴 관점에서 접근 방식을 분석하는 것이 좋습니다. 이다 현재 항목이 고유하는 경우. 이다 현재 항목을 의지하는 새 목록을 필요로하는 경우. 현재 목록이 짧아야합니다. 도메인 세부 사항은 없지만 이러한 요구 사항이 변경 될 수 있다고 생각하는 것은 그리 어렵지 않습니다. 목록을 직렬화하면 좀 더 정규화 된 디자인에 필요하지 않은 비 유연성으로 베이킹됩니다. Btw, 그것은 반드시 완전한 Many : Many 관계를 의미하지는 않습니다. 부모에 대한 외래 키와 항목에 대한 문자 열이있는 단일 자식 테이블을 가질 수 있습니다.

목록을 직렬화하는이 길을 계속 가고 싶다면 목록을 XML로 저장하는 것을 고려할 수 있습니다. SQL Server와 같은 일부 데이터베이스에는 XML 데이터 형식도 있습니다. 내가 XML을 제안하는 유일한 이유는 거의 정의상이 목록이 짧아야하기 때문입니다. 목록이 길면 일반적으로 직렬화하는 것은 끔찍한 접근 방식입니다. CSV 경로로 이동하는 경우 구분 기호가 포함 된 값을 고려해야합니다. 즉, 인용 된 식별자를 사용해야합니다. 목록이 짧다고 가정하면 CSV를 사용하든 XML을 사용하든 별 차이가 없을 것입니다.


미래의 변화를 예상하기 위해 +1-항상 데이터 모델을 확장 가능하도록 설계하십시오.
coolgeek

2

난 그냥 CSV로 저장하고, 단순한 값이라면 필요한 모든 것이어야합니다 (XML은 매우 장황하고 그것과 직렬화하는 것은 아마도 과잉 일 수 있지만 옵션이기도합니다).

다음 은 LINQ를 사용하여 CSV를 가져 오는 방법에 대한 좋은 답변 입니다.


나는 그것에 대해 그래도. 여전히 직렬화 및 역 직렬화해야 함을 의미하지만 가능할 것 같습니다. 내가 원하는 일을 할 수있는 묵인 된 방법 이 있었으면 좋겠지 만, 그렇지 않은 것 같습니다.
JnBrymn

capnproto.org은 선택의 언어에서 지원되지 않는 경우 capnproto에서 (CSV 또는 XML에 비해) 유사 빠르고, 직렬화 및 역 직렬화 할 필요가 없게하는 방법입니다 msgpack.org/index.html
VoronoiPotato

2

목록에서 쿼리해야하는 경우 테이블에 저장하십시오.

항상 목록을 원하면 열에 구분 된 목록으로 저장할 수 있습니다. 이 경우에도 특별한 이유가없는 한 조회 테이블에 저장하십시오.


1

답변에는 하나의 옵션 만 언급되어 있지 않습니다. DB 설계를 비정규화할 수 있습니다. 따라서 두 개의 테이블이 필요합니다. 한 테이블에는 적절한 목록, 행당 하나의 항목이 포함되고 다른 테이블에는 한 열에 전체 목록이 포함됩니다 (예 : 쉼표로 구분).

다음은 '전통적인'DB 디자인입니다.

List(ListID, ListName) 
Item(ItemID,ItemName) 
List_Item(ListID, ItemID, SortOrder)

다음은 비정규 화 된 테이블입니다.

Lists(ListID, ListContent)

여기서 아이디어는 트리거 또는 애플리케이션 코드를 사용하여 Lists 테이블을 유지하는 것입니다. List_Item 콘텐츠를 수정할 때마다 목록의 해당 행이 자동으로 업데이트됩니다. 대부분 목록을 읽으면 꽤 잘 작동 할 수 있습니다. 장점-한 문장으로 목록을 읽을 수 있습니다. 단점-업데이트에는 더 많은 시간과 노력이 필요합니다.


0

열에 저장하고 쿼리 할 수 ​​있도록하려면 많은 데이터베이스에서 XML을 지원합니다. 쿼리하지 않는 경우 쉼표로 분리 된 값으로 저장하고 분리해야 할 때 함수로 구문 분석 할 수 있습니다. 관계형 데이터베이스를 사용하려는 경우 정규화의 큰 부분은 이와 같은 데이터 분리입니다. 모든 데이터가 관계형 데이터베이스에 적합하다고 말하는 것은 아닙니다. 많은 데이터가 모델에 맞지 않는 경우 항상 다른 유형의 데이터베이스를 조사 할 수 있습니다.


0

어떤 경우에는 데이터베이스에 항목의 가짜 "목록"을 만들 수 있다고 생각합니다. 예를 들어 상품에는 세부 정보를 표시 할 사진이 몇 장 있고 쉼표로 분할 된 사진의 모든 ID를 연결하여 문자열을 저장할 수 있습니다. 그런 다음 필요할 때 문자열을 구문 분석하면됩니다. 저는 지금 웹 사이트에서 작업하고 있으며 이런 방식으로 사용할 계획입니다.


0

답이 많기 때문에 드디어 택하기로 결정한 길을 택하기를 매우 꺼렸다. 그들이 SQL과 그 원칙에 대한 이해를 더하는 동안 나는 무법자가되기로 결정했습니다. 나는 또한 보편적 인 진실이 거의 없다는 것을 이해하는 것보다 규칙을 어기는 사람에게 좌절감을 표출하는 것이 더 중요하기 때문에 내 연구 결과를 게시하는 것을 주저했습니다.

광범위하게 테스트했으며 특정 경우에는 배열 유형 (일반적으로 PostgreSQL에서 제공)을 사용하거나 다른 테이블을 쿼리하는 것보다 훨씬 효율적이었습니다.

내 대답은 다음과 같습니다. 목록의 각 항목의 고정 길이를 사용하여 PostgreSQL의 단일 필드에 목록을 성공적으로 구현했습니다. 각 항목이 ARGB 16 진수 값으로 색상이라고 가정하면 8 문자를 의미합니다. 따라서 각 항목의 길이를 곱하여 최대 10 개 항목의 배열을 만들 수 있습니다.

ALTER product ADD color varchar(80)

목록 항목 길이가 다른 경우 항상 패딩을 \ 0으로 채울 수 있습니다.

NB : 정수 목록이 저장 공간을 덜 소비하므로 16 진수에 대한 최선의 방법은 아니지만 이는 각 항목에 할당 된 고정 길이를 사용하여 배열 개념을 설명하기위한 것입니다.

이유 : 1 / 매우 편리함 : 하위 문자열 i * n, (i +1) * n에서 항목 i를 검색합니다. 2 / 크로스 테이블 쿼리의 오버 헤드가 없습니다. 3 / 서버 측에서 더 효율적이고 비용을 절감합니다. 목록은 클라이언트가 분할해야하는 미니 Blob과 같습니다.

규칙을 따르는 사람들을 존중하지만 많은 설명이 매우 이론적이며 일부 특정 경우, 특히 저 지연 솔루션으로 최적의 비용을 목표로 할 때 일부 사소한 조정이 환영받는 것 이상이라는 사실을 종종 인정하지 않습니다.

"하나님은 그것이 SQL의 거룩한 신성한 원칙을 위반하는 것을 금 하신다": 규칙을 낭독하기 전에 좀 더 개방적이고 실용적인 접근 방식을 채택하는 것이 항상 갈 길이다. 그렇지 않으면 Skynet에 의해 말소되기 전에 로봇 공학세 가지 법칙을 낭독하는 솔직한 광신자처럼 끝날 수 있습니다.

이 솔루션이 혁신이라고 생각하지도 않고 가독성과 데이터베이스 유연성 측면에서 이상적이라고 생각하지는 않지만 지연 시간에 있어서는 확실히 우위를 점할 수 있습니다.


그러나 이것은 매우 특정한 경우입니다. 고정 된 길이의 항목 수입니다. 그럼에도 불구하고 "적어도 x 색상이있는 모든 제품"과 같은 간단한 검색을 표준 SQL보다 어렵게 만듭니다.
Gert Arnold

내가 여러 번 언급했듯이 나는 그것을 색상에 사용하지 않습니다. 내가 사용하는 필드는 색인화되거나 조건으로 사용되지 않아야하지만 중요한 것입니다
Antonin GAVREL

나는 이것이 매우 구체적이라는 것을 나타내려고 노력하고 있습니다. 작은 추가 요구 사항이 몰래 들어가면 표준 솔루션보다 빨리 어색해집니다. 하나의 db 필드에 목록을 저장하려는 유혹을받는 대다수의 사람들은이를 수행하지 않는 것이 좋습니다.
Gert Arnold

0

많은 SQL 데이터베이스를 사용하면 테이블이 하위 테이블을 구성 요소로 포함 할 수 있습니다. 일반적인 방법은 열 중 하나의 도메인을 테이블로 허용하는 것입니다. 이는 CSV와 같은 일부 규칙을 사용하여 DBMS에 알려지지 않은 방식으로 하위 구조를 인코딩하는 것에 추가됩니다.

Ed Codd가 1969-1970 년에 관계형 모델을 개발할 때 그는 특별히 정규형을 정의했습니다. 이러한 종류의 테이블 중첩을 허용하지 않는 을 . 보통 형식은 나중에 First Normal Form이라고 불 렸습니다. 그런 다음 그는 모든 데이터베이스에 대해 동일한 정보를 표현하는 첫 번째 정규 형식의 데이터베이스가 있음을 보여주었습니다.

왜 이것으로 귀찮게? 첫 번째 정규 형식의 데이터베이스는 모든 데이터에 대한 키 액세스를 허용합니다. 테이블 이름, 해당 테이블에 대한 키 값 및 열 이름을 제공하는 경우 데이터베이스에는 하나의 데이터 항목을 포함하는 최대 하나의 셀이 포함됩니다.

셀에 목록, 테이블 또는 기타 컬렉션이 포함되도록 허용하면 이제 키 개념을 완전히 재 작업하지 않고는 하위 항목에 대한 키 액세스를 제공 할 수 없습니다.

모든 데이터에 대한 키 액세스는 관계형 모델의 기본입니다. 이 개념이 없으면 모델은 관계형이 아닙니다. 관계형 모델이 좋은 아이디어 인 이유와 그 좋은 아이디어의 한계가 무엇인지에 관해서는 관계형 모델에 대한 50 년의 축적 된 경험을 살펴보아야합니다.


-1

목록처럼 보이는 텍스트로 저장하고 데이터를 실제 목록으로 반환 할 수있는 함수를 만들 수 있습니다. 예:

데이터 베이스:

 _____________________
|  word  | letters    |
|   me   | '[m, e]'   |
|  you   |'[y, o, u]' |  note that the letters column is of type 'TEXT'
|  for   |'[f, o, r]' |
|___in___|_'[i, n]'___|

그리고 목록 컴파일러 함수 (파이썬으로 작성되었지만 대부분의 다른 프로그래밍 언어로 쉽게 번역 할 수 있어야 함). TEXT는 SQL 테이블에서로드 된 텍스트를 나타냅니다. 목록을 포함하는 문자열에서 문자열 목록을 반환합니다. 문자열 대신 int를 반환하려면 모드를 'int'와 동일하게 만드십시오. 'string', 'bool'또는 'float'와 마찬가지로.

def string_to_list(string, mode):
    items = []
    item = ""
    itemExpected = True
    for char in string[1:]:
        if itemExpected and char not in [']', ',', '[']:
            item += char
        elif char in [',', '[', ']']:
            itemExpected = True
            items.append(item)
            item = ""
    newItems = []
    if mode == "int":
        for i in items:
            newItems.append(int(i))

    elif mode == "float":
        for i in items:
            newItems.append(float(i))

    elif mode == "boolean":
        for i in items:
            if i in ["true", "True"]:
                newItems.append(True)
            elif i in ["false", "False"]:
                newItems.append(False)
            else:
                newItems.append(None)
    elif mode == "string":
        return items
    else:
        raise Exception("the 'mode'/second parameter of string_to_list() must be one of: 'int', 'string', 'bool', or 'float'")
    return newItems

또한 필요한 경우를 대비하여 목록-문자열 함수가 있습니다.

def list_to_string(lst):
    string = "["
    for i in lst:
        string += str(i) + ","
    if string[-1] == ',':
        string = string[:-1] + "]"
    else:
        string += "]"
    return string
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.