AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • Início
  • system&network
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • Início
  • system&network
    • Recentes
    • Highest score
    • tags
  • Ubuntu
    • Recentes
    • Highest score
    • tags
  • Unix
    • Recentes
    • tags
  • DBA
    • Recentes
    • tags
  • Computer
    • Recentes
    • tags
  • Coding
    • Recentes
    • tags
Início / dba / Perguntas / 131400
Accepted
youngrrrr
youngrrrr
Asked: 2016-03-06 16:13:27 +0800 CST2016-03-06 16:13:27 +0800 CST 2016-03-06 16:13:27 +0800 CST

Melhor design para referenciar várias tabelas de uma única coluna?

  • 772

Esquema proposto

Em primeiro lugar, aqui está um exemplo do meu esquema proposto para referência ao longo do meu post:

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

declaração do problema

Eu tenho uma tabela de roupas que possui colunas como nome, cor, preço, brandid e assim por diante para descrever os atributos de uma determinada peça de roupa.

Aqui está o meu problema: diferentes marcas de roupas requerem informações diferentes. Qual é a melhor prática para lidar com um problema como esse?

Observe que, para meus propósitos, é necessário encontrar informações específicas da marca a partir de uma entrada de roupas . Isso ocorre porque primeiro mostro as informações de uma entrada de roupas para o usuário, após o que devo usar as informações específicas da marca para comprar o item. Em resumo, deve haver uma relação direcional entre as roupas (de) e as tabelas brand_x .

Solução proposta/atual

Para lidar com isso, pensei no seguinte esquema de design:

A tabela de roupas terá uma coluna de marca que pode ter valores de id variando de 1 a x, onde um determinado id corresponde a uma tabela específica da marca. Por exemplo, o valor de id 1 corresponderá à tabela brand_1 (que pode ter uma coluna de url ), o id 2 corresponderá a brand_2 (que pode ter uma coluna de fornecedor ), etc.

Assim, para associar uma determinada entrada de roupas com suas informações específicas da marca, imagino que a lógica no nível do aplicativo seja algo como isto:

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.

Outros comentários e pensamentos

Estou tentando normalizar todo o meu banco de dados no BCNF e, embora seja isso que eu criei, o código do aplicativo resultante me deixa muito ansioso. Não há como impor relações, exceto no nível do aplicativo e, portanto, o design parece muito hackeado e, prevejo, muito propenso a erros.

Pesquisar

Certifiquei-me de olhar as entradas anteriores antes de fazer uma postagem. Aqui está um post com um problema quase idêntico que consegui encontrar. Eu fiz este post de qualquer maneira porque parece que a única resposta fornecida não tem uma solução SQL ou baseada em design (ou seja, menciona OOP, herança e interfaces).

Também sou um novato quando se trata de design de banco de dados e, portanto, gostaria de receber informações.


Parece que há respostas mais úteis no Stack Overflow:

  • Aqui
  • E aqui
  • Aaaaand aqui (o conceito-chave é: herança de tabela de classe)

Eu me referi às soluções lá e sugiro que outras pessoas que encontrem minha pergunta também o façam.

Apesar dos links fornecidos acima, ainda estou procurando respostas aqui e agradeceria qualquer solução fornecida!

Estou usando o PostgreSQL.

database-design subtypes
  • 4 4 respostas
  • 19848 Views

4 respostas

  • Voted
  1. Best Answer
    McNets
    2017-02-28T12:22:04+08:002017-02-28T12:22:04+08:00

    Pessoalmente, não gosto de usar um esquema de várias tabelas para essa finalidade.

    • É difícil garantir a integridade.
    • É difícil manter.
    • É difícil filtrar os resultados.

    Eu configurei uma amostra dbfiddle .

    Meu esquema de tabela proposto:

    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)
    );
    

    Deixe-me inserir alguns dados:

    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');
    

    Se você precisar buscar atributos comuns:

    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
    

    Ou você pode facilmente obter Roupas por Marca:

    Dê-me todas as roupas de 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
    

    Mas para mim, um dos melhores desse esquema é que você pode filtrar por Atributos:

    Dê-me todas as roupas que tenham o atributo: Tamanho

    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
    

    Usando um esquema multi-tabela, qualquer uma das consultas anteriores exigirá lidar com um número ilimitado de tabelas ou com campos XML ou JSON.

    Outra opção com este esquema é que você pode definir modelos, por exemplo, você pode adicionar uma nova tabela BrandAttrTemplates. Cada vez que você adiciona um novo registro, você pode usar um gatilho ou um SP para gerar um conjunto de atributos predefinidos para esta Filial.

    Sinto muito, gostaria de estender minhas explicações, acho que é mais claro do que meu inglês.

    Atualizar

    Minha resposta atual deve funcionar, independentemente de qual RDBMS. De acordo com seus comentários, se você precisar filtrar os valores dos atributos, sugiro pequenas alterações.

    Como o MS-Sql não permite arrays, configurei uma nova amostra mantendo o mesmo esquema de tabela, mas alterando AttrValue para um tipo de campo ARRAY.

    Na verdade, usando o POSTGRES, você pode aproveitar essa matriz usando um índice GIN.

    (Deixe-me dizer que @EvanCarrol tem um bom conhecimento sobre Postgres, certamente melhor do que eu. Mas deixe-me adicionar minha parte.)

    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}');
    

    Agora, você também pode consultar usando valores de atributos individuais como:

    Dê-me uma lista de todas as calças Tamanho: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;
    

    Este é o resultado:

    clothes name | brand name | attribute | values 
    ------------- ------------ ----------  ---------------- 
    Pants          Brand1       Size        32, 33, 34
    
    • 10
  2. Evan Carroll
    2017-03-03T17:22:59+08:002017-03-03T17:22:59+08:00

    Aqui está o meu problema: diferentes marcas de roupas requerem informações diferentes. Qual é a melhor prática para lidar com um problema como esse?

    Usando JSON e PostgreSQL

    Acho que você está tornando isso mais difícil do que precisa ser e será mordido por isso mais tarde. Você não precisa do modelo de valor de atributo de entidade, a menos que realmente precise de 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
    );
    

    Não há absolutamente nada de errado com esse esquema.

    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 )
    ;
    

    Agora você pode consultá-lo usando uma junção simples

    SELECT *
    FROM brands
    JOIN clothes
      USING (brand_id);
    

    E qualquer um dos operadores JSON funciona em uma cláusula where.

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

    Como observação, não coloque os URLs no banco de dados. Eles mudam com o tempo. Basta criar uma função que os leve.

    generate_url_brand( brand_id );
    generate_url_clothes( clothes_id );
    

    como queiras. Se você estiver usando o PostgreSQL, pode até usar hashids .

    Também digno de nota, jsonbé armazenado como binário (portanto, o -'b') e também é indexável, ou SARGable ou qualquer outra coisa que os garotos legais estejam chamando atualmente:CREATE INDEX ON brands USING gin ( attributes );

    A diferença aqui está na simplicidade da consulta..

    Give me all clothes of Brand2

    SELECT * FROM clothes WHERE brand_id = 2;
    

    Give me all Clothes that has the attribute: Size

    SELECT * FROM clothes WHERE attributes ? 'size';
    

    How about a different one..

    Give me all clothes and attributes for any clothes available in large.

    SELECT * FROM clothes WHERE attributes->>'size' = 'large';
    
    • 4
  3. Joel Brown
    2017-03-07T04:29:13+08:002017-03-07T04:29:13+08:00

    What you are describing is, at least in part, a product catalog. You have several attributes which are common to all products. These belong in a well normalized table.

    Beyond that, you have a series of attributes which are brand specific (and I expect could be product specific). What does your system need to do with these specific attributes? Do you have business logic that depends on the schema of these attributes or are you just listing them in a series of "label":"value" pairs?

    Other answers are suggesting using what is essentially a CSV approach (whether this is JSON or ARRAY or otherwise) - These approaches forego regular relational schema handling by moving the schema out of metadata and into the data itself.

    There is a portable design pattern for this which fits relational databases very well. It is EAV (entity-attribute-value). I'm sure you've read in many, many places that "EAV is Evil" (and it is). However, there is one particular application where the problems with EAV are not important, and that is product attribute catalogs.

    All of the usual arguments against EAV don't apply to a product feature catalog, since product feature values are generally only regurgitated into a list or worst case into a comparison table.

    Using a JSON column type takes your ability to enforce any data constraints out of the database and forces it into your application logic. Also, using one attributes table for every brand has the following disadvantages:

    • It doesn't scale well if you eventually have hundreds of brands (or more).
    • If you change the allowable attributes on a brand you have to change a table definition instead of just adding or removing rows in a brand field control table.
    • You may still end up with sparsely populated tables if the brand has many potential features, only a small subset of which are known.

    It is not especially difficult to retrieve data about a product with brand-specific features. It is arguably easier to create a dynamic SQL using the EAV model than it would be using the table-per-category model. In table-per-category, you need reflection (or your JSON) to find out what the feature column names are. Then you can build a list of items for a where clause. In the EAV model, the WHERE X AND Y AND Z becomes INNER JOIN X INNER JOIN Y INNER JOIN Z, so the query is a little more complicated, but the logic to build the query is still totally table-driven and it will be more than scalable enough if you have the proper indexes built.

    There are a lot of reasons not to use EAV as a general approach. Those reasons don't apply to a product feature catalog so there is nothing wrong with EAV in this specific application.

    To be sure, this is a short answer for a complex and controversial topic. I have answered similar questions before and gone into more detail about the general aversion to EAV. For example:

    • If EAV is evil, what to use for dynamic values?
    • Entity-Attribute-Value Table Design

    I would say EAV is used less often lately than it used to be, for mostly good reasons. However, I think it is also not well understood.

    • 4
  4. Matthew Sontum
    2017-02-12T15:44:53+08:002017-02-12T15:44:53+08:00

    Uma solução fácil é incluir todos os atributos possíveis como colunas na mesa de roupas principal e tornar todas as colunas específicas da marca anuláveis. Esta solução quebra a normalização do banco de dados, mas é muito fácil de implementar.

    • -1

relate perguntas

  • Os índices filtrados podem ajudar a melhorar as consultas baseadas em uma hora inserida ou isso deve ser evitado?

  • Qual é a diferença entre os tipos de dados MySQL VARCHAR e TEXT?

  • É melhor armazenar os valores calculados ou recalculá-los a pedido? [duplicado]

  • Armazenar vs calcular valores agregados

  • Quais são algumas maneiras de implementar um relacionamento muitos-para-muitos em um data warehouse?

Sidebar

Stats

  • Perguntas 205573
  • respostas 270741
  • best respostas 135370
  • utilizador 68524
  • Highest score
  • respostas
  • Marko Smith

    conectar ao servidor PostgreSQL: FATAL: nenhuma entrada pg_hba.conf para o host

    • 12 respostas
  • Marko Smith

    Como fazer a saída do sqlplus aparecer em uma linha?

    • 3 respostas
  • Marko Smith

    Selecione qual tem data máxima ou data mais recente

    • 3 respostas
  • Marko Smith

    Como faço para listar todos os esquemas no PostgreSQL?

    • 4 respostas
  • Marko Smith

    Listar todas as colunas de uma tabela especificada

    • 5 respostas
  • Marko Smith

    Como usar o sqlplus para se conectar a um banco de dados Oracle localizado em outro host sem modificar meu próprio tnsnames.ora

    • 4 respostas
  • Marko Smith

    Como você mysqldump tabela (s) específica (s)?

    • 4 respostas
  • Marko Smith

    Listar os privilégios do banco de dados usando o psql

    • 10 respostas
  • Marko Smith

    Como inserir valores em uma tabela de uma consulta de seleção no PostgreSQL?

    • 4 respostas
  • Marko Smith

    Como faço para listar todos os bancos de dados e tabelas usando o psql?

    • 7 respostas
  • Martin Hope
    Jin conectar ao servidor PostgreSQL: FATAL: nenhuma entrada pg_hba.conf para o host 2014-12-02 02:54:58 +0800 CST
  • Martin Hope
    Stéphane Como faço para listar todos os esquemas no PostgreSQL? 2013-04-16 11:19:16 +0800 CST
  • Martin Hope
    Mike Walsh Por que o log de transações continua crescendo ou fica sem espaço? 2012-12-05 18:11:22 +0800 CST
  • Martin Hope
    Stephane Rolland Listar todas as colunas de uma tabela especificada 2012-08-14 04:44:44 +0800 CST
  • Martin Hope
    haxney O MySQL pode realizar consultas razoavelmente em bilhões de linhas? 2012-07-03 11:36:13 +0800 CST
  • Martin Hope
    qazwsx Como posso monitorar o andamento de uma importação de um arquivo .sql grande? 2012-05-03 08:54:41 +0800 CST
  • Martin Hope
    markdorison Como você mysqldump tabela (s) específica (s)? 2011-12-17 12:39:37 +0800 CST
  • Martin Hope
    Jonas Como posso cronometrar consultas SQL usando psql? 2011-06-04 02:22:54 +0800 CST
  • Martin Hope
    Jonas Como inserir valores em uma tabela de uma consulta de seleção no PostgreSQL? 2011-05-28 00:33:05 +0800 CST
  • Martin Hope
    Jonas Como faço para listar todos os bancos de dados e tabelas usando o psql? 2011-02-18 00:45:49 +0800 CST

Hot tag

sql-server mysql postgresql sql-server-2014 sql-server-2016 oracle sql-server-2008 database-design query-performance sql-server-2017

Explore

  • Início
  • Perguntas
    • Recentes
    • Highest score
  • tag
  • help

Footer

AskOverflow.Dev

About Us

  • About Us
  • Contact Us

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve