단일 열에서 여러 테이블을 참조하는 최고의 디자인?


18

제안 된 스키마

가장 먼저, 다음은 내 게시물 전체에서 참조 할 제안 된 스키마의 예입니다.

Clothes
---------- 
ClothesID (PK) INT NOT NULL
Name VARCHAR(50) NOT NULL
Color VARCHAR(50) NOT NULL
Price DECIMAL(5,2) NOT NULL
BrandID INT NOT NULL
...

Brand_1
--------
ClothesID (FK/PK) int NOT NULL
ViewingUrl VARCHAR(50) NOT NULL
SomeOtherBrand1SpecificAttr VARCHAR(50) NOT NULL

Brand_2
--------
ClothesID (FK/PK) int NOT NULL
PhotoUrl VARCHAR(50) NOT NULL
SomeOtherBrand2SpecificAttr VARCHAR(50) NOT NULL

Brand_X
--------
ClothesID (FK/PK) int NOT NULL
SomeOtherBrandXSpecificAttr VARCHAR(50) NOT NULL

문제 설명

특정 의류 품목의 속성을 설명하기 위해 이름, 색상, 가격, 브랜드 등의 열 이있는 옷걸이 테이블이 있습니다 .

여기 내 문제가있다 : 다른 브랜드 의 의류에는 다른 정보가 필요하다. 이와 같은 문제를 처리하는 가장 좋은 방법은 무엇입니까?

내 목적을 위해 옷을 입기 시작하여 브랜드 별 정보를 찾아야 합니다. 의류 입문서에서 사용자에게 먼저 정보를 표시 한 후 브랜드 별 정보를 사용하여 품목을 구매해야하기 때문입니다. 요컨대, 의상 (from)과 brand_x 테이블 사이에는 방향 관계가 있어야합니다 .

제안 / 현재 솔루션

이에 대처하기 위해 다음과 같은 디자인 계획을 생각했습니다.

표는 것이다 브랜드 1에서 X, 범위 ID 값을 가질 수있다 여기서 열 브랜드 특정 테이블의 특정 번호에 대응한다. 예를 들어, id 값 1은 brand_1 ( url 열 이있을 수 있음) 테이블에 해당하고 id 2는 brand_2 ( 공급자 열 이있을 수 있음 ) 등에 해당합니다.

따라서 특정 의류 항목을 브랜드 별 정보와 연결하려면 응용 프로그램 수준의 논리가 다음과 같이 보일 것입니다.

clothesId = <some value>
brand = query("SELECT brand FROM clothes WHERE id = clothesId")

if (brand == 1) {
    // get brand_1 attributes for given clothesId
} else if (brand == 2) {
    // get brand_2 attributes for given clothesId
} ... etc.

다른 의견 및 생각

나는 BCNF에서 전체 데이터베이스를 정규화하려고 시도하고 있는데, 이것이 내가 생각해 냈지만 결과 응용 프로그램 코드는 매우 불안해합니다. 응용 프로그램 수준을 제외하고 관계를 적용 할 방법이 없으므로 디자인이 매우 해킹되어 오류가 발생하기 쉽습니다.

연구

게시물을 작성하기 전에 이전 항목을 살펴 보았습니다. 내가 찾은 거의 똑같은 문제가있는 게시물이 있습니다. 제공된 게시물에 SQL 또는 디자인 기반 솔루션이없는 것처럼 보이기 때문에이 게시물을 작성했습니다 (즉, OOP, 상속 및 인터페이스에 대한 언급).

또한 데이터베이스 디자인과 관련하여 초보자이기도하므로 통찰력에 감사드립니다.


스택 오버플로에 더 유용한 응답이있는 것으로 보입니다.

나는 거기에있는 해결책을 언급했으며 내 질문을 찾는 다른 사람들도 그렇게 제안합니다.

위에 제공된 링크에도 불구하고, 나는 여전히 여기에 대한 답변을 찾고 있으며 제공된 솔루션을 높이 평가할 것입니다!

PostgreSQL을 사용하고 있습니다.

답변:


7

개인적 으로이 목적을 위해 다중 테이블 스키마를 사용하는 것을 좋아하지 않습니다.

  • 무결성을 보장하기가 어렵습니다.
  • 유지하기가 어렵습니다.
  • 결과를 필터링하기가 어렵습니다.

dbfiddle sample을 설정했습니다 .

내 제안 된 테이블 스키마 :

CREATE TABLE #Brands
(
BrandId int NOT NULL PRIMARY KEY,
BrandName nvarchar(100) NOT NULL 
);

CREATE TABLE #Clothes
(
ClothesId int NOT NULL PRIMARY KEY,
ClothesName nvarchar(100) NOT NULL 
);

-- Lookup table for known attributes
--
CREATE TABLE #Attributes
(
AttrId int NOT NULL PRIMARY KEY,
AttrName nvarchar(100) NOT NULL 
);

-- holds common propeties, url, price, etc.
--
CREATE TABLE #BrandsClothes
(
BrandId int NOT NULL REFERENCES #Brands(BrandId),
ClothesId int NOT NULL REFERENCES #Clothes(ClothesId),
VievingUrl nvarchar(300) NOT NULL,
Price money NOT NULL,
PRIMARY KEY CLUSTERED (BrandId, ClothesId),
INDEX IX_BrandsClothes NONCLUSTERED (ClothesId, BrandId)
);

-- holds specific and unlimited attributes 
--
CREATE TABLE #BCAttributes
(
BrandId int NOT NULL REFERENCES #Brands(BrandId),
ClothesId int NOT NULL REFERENCES #Clothes(ClothesId),
AttrId int NOT NULL REFERENCES #Attributes(AttrId),
AttrValue nvarchar(300) NOT NULL,
PRIMARY KEY CLUSTERED (BrandId, ClothesId, AttrId),
INDEX IX_BCAttributes NONCLUSTERED (ClothesId, BrandId, AttrId)
);

몇 가지 데이터를 삽입하겠습니다.

INSERT INTO #Brands VALUES 
(1, 'Brand1'), (2, 'Brand2');

INSERT INTO #Clothes VALUES 
(1, 'Pants'), (2, 'T-Shirt');

INSERT INTO #Attributes VALUES
(1, 'Color'), (2, 'Size'), (3, 'Shape'), (4, 'Provider'), (0, 'Custom');

INSERT INTO #BrandsClothes VALUES
(1, 1, 'http://mysite.com?B=1&C=1', 123.99),
(1, 2, 'http://mysite.com?B=1&C=2', 110.99),
(2, 1, 'http://mysite.com?B=2&C=1', 75.99),
(2, 2, 'http://mysite.com?B=2&C=2', 85.99);

INSERT INTO #BCAttributes VALUES
(1, 1, 1, 'Blue, Red, White'),
(1, 1, 2, '32, 33, 34'),
(1, 2, 1, 'Pearl, Black widow'),
(1, 2, 2, 'M, L, XL'),
(2, 1, 4, 'Levis, G-Star, Armani'),
(2, 1, 3, 'Slim fit, Regular fit, Custom fit'),
(2, 2, 4, 'G-Star, Armani'),
(2, 2, 3, 'Slim fit, Regular fit'),
(2, 2, 0, '15% Discount');

공통 속성을 가져와야하는 경우 :

SELECT     b.BrandName, c.ClothesName, bc.VievingUrl, bc.Price
FROM       #BrandsClothes bc
INNER JOIN #Brands b
ON         b.BrandId = bc.BrandId
INNER JOIN #Clothes c
ON         c.ClothesId = bc.ClothesId
ORDER BY   bc.BrandId, bc.ClothesId;

BrandName   ClothesName   VievingUrl                  Price
---------   -----------   -------------------------   ------
Brand1      Pants         http://mysite.com?B=1&C=1   123.99
Brand1      T-Shirt       http://mysite.com?B=1&C=2   110.99
Brand2      Pants         http://mysite.com?B=2&C=1    75.99
Brand2      T-Shirt       http://mysite.com?B=2&C=2    85.99

또는 브랜드별로 쉽게 옷을 입을 수 있습니다.

Brand2의 모든 옷을 줘

SELECT     c.ClothesName, b.BrandName, a.AttrName, bca.AttrValue
FROM       #BCAttributes bca
INNER JOIN #BrandsClothes bc
ON         bc.BrandId = bca.BrandId
AND        bc.ClothesId = bca.ClothesId
INNER JOIN #Brands b
ON         b.BrandId = bc.BrandId
INNER JOIN #Clothes c
ON         c.ClothesId = bc.ClothesId
INNER JOIN #Attributes a
ON         a.AttrId = bca.AttrId
WHERE      bca.ClothesId = 2
ORDER BY   bca.ClothesId, bca.BrandId, bca.AttrId;

ClothesName   BrandName   AttrName   AttrValue
-----------   ---------   --------   ---------------------
T-Shirt       Brand1      Color      Pearl, Black widow
T-Shirt       Brand1      Size       M, L, XL
T-Shirt       Brand2      Custom     15% Discount
T-Shirt       Brand2      Shape      Slim fit, Regular fit
T-Shirt       Brand2      Provider   G-Star, Armani

그러나 나 에게이 스키마의 가장 좋은 점 중 하나는 Attibutes로 필터링 할 수 있다는 것입니다.

Size 속성을 가진 모든 옷을 줘

SELECT     c.ClothesName, b.BrandName, a.AttrName, bca.AttrValue
FROM       #BCAttributes bca
INNER JOIN #BrandsClothes bc
ON         bc.BrandId = bca.BrandId
AND        bc.ClothesId = bca.ClothesId
INNER JOIN #Brands b
ON         b.BrandId = bc.BrandId
INNER JOIN #Clothes c
ON         c.ClothesId = bc.ClothesId
INNER JOIN #Attributes a
ON         a.AttrId = bca.AttrId
WHERE      bca.AttrId = 2
ORDER BY   bca.ClothesId, bca.BrandId, bca.AttrId;

ClothesName   BrandName   AttrName   AttrValue
-----------   ---------   --------   ----------
Pants         Brand1      Size       32, 33, 34
T-Shirt       Brand1      Size       M, L, XL

이전 쿼리에 관계없이 다중 테이블 스키마를 사용하면 무제한 테이블 또는 XML 또는 JSON 필드를 처리해야합니다.

이 스키마의 또 다른 옵션은 템플릿을 정의 할 수 있다는 것입니다. 예를 들어 새 테이블 BrandAttrTemplates를 추가 할 수 있습니다. 새 레코드를 추가 할 때마다 트리거 또는 SP를 사용하여이 분기에 대해 사전 정의 된 속성 세트를 생성 할 수 있습니다.

죄송합니다. 영어보다 명확하다고 생각하여 설명을 확장하고 싶습니다.

최신 정보

내 현재 답변은 어떤 RDBMS에 관계없이 작동해야합니다. 귀하의 의견에 따르면, 속성 값을 필터링 해야하는 경우 약간의 변경을 제안합니다.

MS-Sql이 배열을 허용하지 않는 한 동일한 테이블 스키마를 유지하면서 AttrValue를 ARRAY 필드 유형으로 변경 하는 새 샘플을 설정했습니다 .

실제로 POSTGRES를 사용하면 GIN 인덱스를 사용하여이 배열을 활용할 수 있습니다.

(@EvanCarrol에 Postgres에 대한 좋은 지식이 있다고 확신합니다. 그러나 저보다 더 좋습니다.)

CREATE TABLE BCAttributes
(
BrandId int NOT NULL REFERENCES Brands(BrandId),
ClothesId int NOT NULL REFERENCES Clothes(ClothesId),
AttrId int NOT NULL REFERENCES Attrib(AttrId),
AttrValue text[],
PRIMARY KEY (BrandId, ClothesId, AttrId)
);

CREATE INDEX ix_attributes on BCAttributes(ClothesId, BrandId, AttrId);
CREATE INDEX ix_gin_attributes on BCAttributes using GIN (AttrValue);


INSERT INTO BCAttributes VALUES
(1, 1, 1, '{Blue, Red, White}'),
(1, 1, 2, '{32, 33, 34}'),
(1, 2, 1, '{Pearl, Black widow}'),
(1, 2, 2, '{M, L, XL}'),
(2, 1, 4, '{Levis, G-Star, Armani}'),
(2, 1, 3, '{Slim fit, Regular fit, Custom fit}'),
(2, 2, 4, '{G-Star, Armani}'),
(2, 2, 3, '{Slim fit, Regular fit}'),
(2, 2, 0, '{15% Discount}');

이제 다음과 같은 개별 속성 값을 사용하여 추가로 쿼리 할 수 ​​있습니다.

모든 바지의 목록을 알려주세요. 사이즈 : 33

AttribId = 2 AND ARRAY['33'] && bca.AttrValue

SELECT     c.ClothesName, b.BrandName, a.AttrName, array_to_string(bca.AttrValue, ', ')
FROM       BCAttributes bca
INNER JOIN BrandsClothes bc
ON         bc.BrandId = bca.BrandId
AND        bc.ClothesId = bca.ClothesId
INNER JOIN Brands b
ON         b.BrandId = bc.BrandId
INNER JOIN Clothes c
ON         c.ClothesId = bc.ClothesId
INNER JOIN Attrib a
ON         a.AttrId = bca.AttrId
WHERE      bca.AttrId = 2
AND        ARRAY['33'] && bca.AttrValue
ORDER BY   bca.ClothesId, bca.BrandId, bca.AttrId;

결과는 다음과 같습니다.

clothes name | brand name | attribute | values 
------------- ------------ ----------  ---------------- 
Pants          Brand1       Size        32, 33, 34

이 설명이 정말 마음에 들지만 여러 열을 단일 열에 여러 테이블로 가져 오는 다중 테이블 스키마를 사용하는 것 같습니다. 반면에 스키마에 대한 변경이 필요하지 않기 때문에이 접근법이 더 좋을 것 같습니다.하지만 다시 다른 곳 (즉, 가변 길이 열을 가짐)으로 문제를 추진하고있는 것처럼 느껴집니다. 이것은 문제가 될 수 있습니다. DB에서 크기 3의 바지를 쿼리하려면 어떻게해야합니까? 아마도 이런 종류의 문제에 대한 훌륭한 해결책이 없을 수도 있습니다. 이 개념에 대한 이름이있어 더 자세히 알아볼 수 있습니까?
youngrrrr

실제로 ... 내가 제기 한 문제에 대답하기 위해 @EvanCarroll의 솔루션에서 대답을 빌릴 수 있습니다. 즉, 단순히 CSV 형식의 TEXT / STRINGS 대신 jsonb 유형을 사용하면 가능합니다. 그러나이 개념의 이름이 있다면 다시 알려주세요!
youngrrrr

1
엔터티 속성 값 유형의 솔루션입니다. 성능과 디자인이 나쁘지 않습니다. 그러나 트레이드 오프입니다. 끝이없는 "Brand_X"테이블이없는 깨끗한 디자인을 위해 성능을 교환하십시오. 명시된 가장 일반적인 방향에서 성능 저하가 최소화되어야합니다. 다른 길로가는 것은 더 고통 스러울 것이지만 그것은 타협입니다. en.wikipedia.org/wiki/…
Jonathan Fite

4

당신이 설명하는 것은 적어도 부분적으로 제품 카탈로그입니다. 모든 제품에 공통적 인 몇 가지 속성이 있습니다. 이들은 잘 정규화 된 테이블에 속합니다.

그 외에도 브랜드마다 다른 일련의 속성이 있으며 제품마다 다를 수 있습니다. 시스템이 이러한 특정 속성과 어떤 관련이 있습니까? 이러한 특성의 스키마에 의존하는 비즈니스 로직이 있습니까? 아니면 일련의 "label": "value"쌍으로 나열합니까?

다른 답변은 CSV 방식 (이인지 본질적으로 무엇을 사용하여 제안하는 JSON또는 ARRAY또는 기타) -이 메타 데이터에서 데이터 자체에 스키마를 이동하여 처리 일반 관계형 스키마 포기 접근한다.

관계형 데이터베이스에 매우 적합한 휴대용 디자인 패턴이 있습니다. EAV (엔티티 속성 값)입니다. 나는 당신이 많은 곳에서 "EAV is Evil"(그리고 그것은 그렇다는)을 읽었을 것이라고 확신합니다. 그러나 EAV의 문제점이 중요하지 않은 특정 응용 프로그램이 있으며 이는 제품 속성 카탈로그입니다.

제품 기능 값은 일반적으로 목록 또는 최악의 경우 비교 테이블로만 역류되기 때문에 EAV에 대한 모든 일반적인 인수는 제품 기능 카탈로그에 적용되지 않습니다.

JSON열 유형을 사용 하면 데이터베이스에서 데이터 제약 조건을 적용하여 응용 프로그램 논리로 강제 적용 할 수 있습니다. 또한 모든 브랜드에 대해 하나의 속성 테이블을 사용하면 다음과 같은 단점이 있습니다.

  • 결국 수백 개의 브랜드 (또는 그 이상)가 있다면 확장 성이 떨어집니다.
  • 브랜드에서 허용되는 속성을 변경하는 경우 브랜드 필드 제어 테이블에서 행을 추가하거나 제거하는 대신 테이블 정의를 변경해야합니다.
  • 브랜드에 잠재적 인 기능이 많고 그 중 일부만 알려진 경우에도 데이터가 거의 채워지지 않을 수 있습니다.

브랜드 별 기능이있는 제품에 대한 데이터를 검색하는 것은 특히 어렵지 않습니다. 범주 당 테이블 모델을 사용하는 것보다 EAV 모델을 사용하여 동적 SQL을 작성하는 것이 더 쉽습니다. 범주 별 테이블 JSON에서 피처 열 이름이 무엇인지 확인 하려면 리플렉션 (또는 ) 이 필요합니다 . 그런 다음 where 절에 대한 항목 목록을 작성할 수 있습니다. EAV 모델에서이 WHERE X AND Y AND Z되고 INNER JOIN X INNER JOIN Y INNER JOIN Z쿼리가 조금 더 복잡하지만, 쿼리를 구축 할 수있는 논리가 아직 완전히 테이블 기반이며 내장 적절한 인덱스가있는 경우는 확장 성이 충분보다 더 많은 수 있도록.

일반적인 방법으로 EAV를 사용하지 않는 데에는 여러 가지 이유가 있습니다. 이러한 이유는 제품 기능 카탈로그에는 적용되지 않으므로이 특정 응용 프로그램에서 EAV에는 아무런 문제가 없습니다.

확실히, 이것은 복잡하고 논쟁의 여지가있는 주제에 대한 짧은 대답입니다. 나는 이전에 비슷한 질문에 대답했으며 EAV의 일반적인 혐오에 대해 더 자세히 설명했습니다. 예를 들면 다음과 같습니다.

나는 EAV가 예전보다 덜 자주 사용된다고 말하는데, 대부분 좋은 이유가 있습니다. 그러나 나는 그것이 또한 잘 이해되지 않는다고 생각합니다.


3

내 문제는 다음과 같습니다. 다른 브랜드의 의류에는 다른 정보가 필요합니다. 이와 같은 문제를 처리하는 가장 좋은 방법은 무엇입니까?

JSON 및 PostgreSQL 사용

나는 당신이 필요 이상으로 이것을 어렵게 만들고 있다고 생각하며 나중에 물릴 것입니다. 실제로 EAV가 필요하지 않으면 엔터티 속성 값 모델이 필요하지 않습니다 .

CREATE TABLE brands (
  brand_id     serial PRIMARY KEY,
  brand_name   text,
  attributes   jsonb
);
CREATE TABLE clothes (
  clothes_id   serial        PRIMARY KEY,
  brand_id     int           NOT NULL REFERENCES brands,
  clothes_name text          NOT NULL,
  color        text,
  price        numeric(5,2)  NOT NULL
);

이 스키마에는 아무런 문제가 없습니다.

INSERT INTO brands (brand_name, attributes)
VALUES
  ( 'Gucci', $${"luxury": true, "products": ["purses", "tawdry bougie thing"]}$$ ),
  ( 'Hugo Boss', $${"origin": "Germany", "known_for": "Designing uniforms"}$$ ),
  ( 'Louis Vuitton', $${"origin": "France", "known_for": "Designer Purses"}$$ ),
  ( 'Coco Chanel', $${"known_for": "Spying", "smells_like": "Banana", "luxury": true}$$ )
;

INSERT INTO clothes (brand_id, clothes_name, color, price) VALUES
  ( 1, 'Purse', 'orange', 100 ),
  ( 2, 'Underwear', 'Gray', 10 ),
  ( 2, 'Boxers', 'Gray', 10 ),
  ( 3, 'Purse with Roman Numbers', 'Brown', 10 ),
  ( 4, 'Spray', 'Clear', 100 )
;

이제 간단한 조인을 사용하여 쿼리 할 수 ​​있습니다

SELECT *
FROM brands
JOIN clothes
  USING (brand_id);

그리고 JSON 연산자 는 where 절에서 작동합니다.

SELECT *
FROM brands
JOIN clothes
  USING (brand_id)
WHERE attributes->>'known_for' ILIKE '%Design%';

참고로 데이터베이스에 URL을 넣지 마십시오. 그들은 시간이 지남에 따라 변합니다. 간단히 함수를 작성하십시오.

generate_url_brand( brand_id );
generate_url_clothes( clothes_id );

또는 무엇이든. PostgreSQL을 사용하는 경우 hashids 사용할 수도 있습니다.

또한 특별한 참고 사항 jsonb은 바이너리로 저장되므로 (-'b ') 색인 가능 또는 SARGable 또는 요즘 멋진 아이들이 요즘 부르는 것입니다.CREATE INDEX ON brands USING gin ( attributes );

여기서의 차이점은 쿼리의 단순성에 있습니다.

Brand2의 모든 옷을 줘

SELECT * FROM clothes WHERE brand_id = 2;

Size 속성을 가진 모든 옷을 줘

SELECT * FROM clothes WHERE attributes ? 'size';

다른 건 어때?

입을 수있는 옷의 모든 옷과 속성을 알려주세요.

SELECT * FROM clothes WHERE attributes->>'size' = 'large';

따라서 내가 올바르게 이해한다면, 브랜드와 속성 사이에 관계가 있는지 (즉, 유효한지 여부) 맥넷의 솔루션이 선호되지만 쿼리는 더 비싸거나 느릴 것입니다. 반면에이 관계가 중요하지 않거나 "임시"가 아닌 경우 솔루션을 선호 할 수 있습니다. "PostgreSQL에서는 절대 사용하지 않겠다"고 말했을 때의 의미로 조금 더 설명해 주시겠습니까? 그 의견에 대한 설명은 없었습니다. 모든 질문에 대해 죄송합니다! 정말 답장을 보내 주셔서 감사합니다 :)
youngrrrr

1
분명한 관계가 있습니다. 유일한 문제는 관계를 관리하는 데 필요한 금액입니다. properties , attributes 등 의 모호한 용어를 사용하는 경우 일반적으로 거의 임시적이거나 구조화되지 않은 것을 말합니다. 이를 위해 JSONB가 더 간단하기 때문에 더 좋습니다. 이 게시물 유익한 정보를 찾을 수 있습니다. coussej.github.io/2016/01/14/…
Evan Carroll

-1

하나의 쉬운 해결책은 가능한 모든 속성을 기본 옷 테이블에 열로 포함시키고 모든 브랜드 특정 열을 널 입력 가능하게 만드는 것입니다. 이 솔루션은 데이터베이스 표준화를 중단하지만 구현하기가 매우 쉽습니다.


나는 당신이 무엇을 말하는지에 대한 아이디어를 가지고 있지만 더 자세한 내용과 예제를 포함시키는 것이 도움이 될 수 있습니다.
youngrrrr
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.