2010-07-13 8 views
23

Por lo tanto, estoy tratando de diseñar una base de datos que me permita conectar un producto con múltiples categorías. Esta parte que he figurado. Pero lo que no puedo resolver es la cuestión de tener diferentes tipos de detalles del producto.diseño de base de datos para contener información de metadatos diferente

Por ejemplo, el producto podría ser un libro (en cuyo caso yo tendría metadatos que se refiere a ese libro como ISBN, autor, etc.) o podría ser una lista de empresas (que tiene diferentes metadatos) ..

¿Cómo debo abordar eso?

+7

Nota: Si una base de datos almacena información sobre libros, los atributos detallados de un libro en particular serían "datos" en lugar de "metadatos". Los metadatos serían datos sobre el mecanismo de almacenamiento en sí, como el hecho de que Book.Title es un nvarchar no nulable (255). Sin embargo, si los datos se almacenan en un libro (como un almanaque), a continuación la información sobre el libro en sí (como ISBN, etc.) sería metadatos. :-) –

Respuesta

34

Esto se llama Patrón de observación.

alt text http://www.damirsystems.com/dp_images/observation_model_3.png

Tres objetos, por ejemplo

Book 
Title = 'Gone with the Wind' 
Author = 'Margaret Mitchell' 
ISBN = '978-1416548898' 

Cat 
Name = 'Phoebe' 
Color = 'Gray' 
TailLength = 9 'inch' 

Beer Bottle 
Volume = 500 'ml' 
Color = 'Green' 

Esta es la forma en tablas pueden ser:

Entity 
EntityID Name   Description 
    1  'Book'   'To read' 
    2  'Cat'    'Fury cat' 
    3  'Beer Bottle'  'To ship beer in' 

.

PropertyType 
PropertyTypeID Name  IsTrait   Description 
    1   'Height'  'NO'  'For anything that has height' 
    2   'Width'  'NO'  'For anything that has width' 
    3   'Volume'  'NO'  'For things that can have volume' 
    4   'Title'  'YES'  'Some stuff has title' 
    5   'Author'  'YES'  'Things can be authored' 
    6   'Color'  'YES'  'Color of things' 
    7   'ISBN'  'YES'  'Books would need this' 
    8   'TailLength' 'NO'  'For stuff that has long tails' 
    9   'Name'  'YES'  'Name of things' 

.

Property 
PropertyID EntityID PropertyTypeID  
    1   1    4  -- book, title 
    2   1    5  -- book, author 
    3   1    7  -- book, isbn 
    4   2    9  -- cat, name 
    5   2    6  -- cat, color 
    6   2    8  -- cat, tail length 
    7   3    3  -- beer bottle, volume 
    8   3    6  -- beer bottle, color 

.

Measurement 
PropertyID  Unit  Value 
    6   'inch'  9   -- cat, tail length 
    7   'ml'  500   -- beer bottle, volume 

.

Trait 
PropertyID   Value 
    1   'Gone with the Wind'  -- book, title 
    2   'Margaret Mitchell'  -- book, author 
    3   '978-1416548898'   -- book, isbn 
    4   'Phoebe'     -- cat, name 
    5   'Gray'     -- cat, color 
    8   'Green'     -- beer bottle, color 

EDIT:

Jefferey planteó una cuestión válida (ver comentario), así que voy a ampliar la respuesta.

El modelo permite la creación dinámica (en marcha) de cualquier número de entidades con cualquier tipo de propiedades sin cambios de esquema. Hovewer, esta flexibilidad tiene un precio: el almacenamiento y la búsqueda son más lentos y más complejos que en un diseño de mesa habitual.

Es hora de dar un ejemplo, pero primero, para facilitar las cosas, voy a aplanar el modelo para verlo.

create view vModel as 
select 
     e.EntityId 
    , x.Name as PropertyName 
    , m.Value as MeasurementValue 
    , m.Unit 
    , t.Value as TraitValue 
from Entity   as e 
join Property   as p on p.EntityID  = p.EntityID 
join PropertyType  as x on x.PropertyTypeId = p.PropertyTypeId 
left join Measurement as m on m.PropertyId  = p.PropertyId 
left join Trait  as t on t.PropertyId  = p.PropertyId 
; 

Para usar el ejemplo de Jefferey del comentario

with 
q_00 as (-- all books 
    select EntityID 
    from vModel 
    where PropertyName = 'object type' 
     and TraitValue = 'book' 
), 
q_01 as (-- all US books 
    select EntityID 
    from vModel as a 
    join q_00 as b on b.EntityID = a.EntityID 
    where PropertyName = 'publisher country' 
     and TraitValue = 'US' 
), 
q_02 as (-- all US books published in 2008 
    select EntityID 
    from vModel as a 
    join q_01 as b on b.EntityID = a.EntityID 
    where PropertyName  = 'year published' 
     and MeasurementValue = 2008 
), 
q_03 as (-- all US books published in 2008 not discontinued 
    select EntityID 
    from vModel as a 
    join q_02 as b on b.EntityID = a.EntityID 
    where PropertyName = 'is discontinued' 
     and TraitValue = 'no' 
), 
q_04 as (-- all US books published in 2008 not discontinued that cost less than $50 
    select EntityID 
    from vModel as a 
    join q_03 as b on b.EntityID = a.EntityID 
    where PropertyName  = 'price' 
     and MeasurementValue < 50 
     and MeasurementUnit = 'USD' 
) 
select 
     EntityID 
    , max(case PropertyName when 'title' than TraitValue else null end) as Title 
    , max(case PropertyName when 'ISBN' than TraitValue else null end) as ISBN 
from vModel as a 
join q_04 as b on b.EntityID = a.EntityID 
group by EntityID ; 

Esto se ve muy complicado escribir, pero en una inspección más cerca se puede notar un patrón en el CTE.

Supongamos ahora que tenemos un diseño de esquema fijo estándar donde cada propiedad de objeto tiene su propia columna. La consulta sería algo como:

select EntityID, Title, ISBN 
from vModel 
WHERE ObjectType  = 'book' 
    and PublisherCountry = 'US' 
    and YearPublished = 2008 
    and IsDiscontinued = 'no' 
    and Price   < 50 
    and Currency   = 'USD' 
; 
+0

Gracias, esta es una muy buena respuesta y una ventaja. Agreguemos más a esta discusión. En este tipo de diseño de cómo sería una cuenta para la búsqueda rápida? Me imagino que eso requeriría muchas uniones? – Obaid

+4

Esta es una muy mala idea, y conducirá a problemas más adelante. Por favor no hagas esto –

+0

Por ejemplo, trate de conseguir todos los libros de las editoriales estadounidenses de libros académicos publicados en 2008 que no han sido interrumpidas y cuestan menos de $ 50. ¡Buena suerte! En una base de datos relacional correctamente diseñada que es una tarea de 2 minutos. –

2

El producto debe ser mecanografiado. p.ej. incluya type_id en la tabla de productos, que apunte a las categorías de productos que admitirá, y le permite saber en qué otras tablas consultar para los atributos relacionados correspondientes.

4

Usted podría ir con el enfoque sin esquema:

Mantenga los metadatos en una columna de texto como un objeto JSON (u otro serialización, pero es mejor JSON por motivos explicados antes).

Las ventajas de esta técnica:

  1. Menos consultas: se obtiene toda la información en una consulta, sin necesidad de que "en la dirección" consultas (para obtener meta-meta-datos) y se une.

  2. Puede añadir/eliminar los atributos que desee en cualquier momento, sin necesidad de alterar mesa (que es problemático en algunas bases de datos, por ejemplo, Mysql bloquea la tabla, y se necesita mucho tiempo con enormes tablas)

  3. Como es JSON, no necesita procesamiento adicional en su back-end. Su página web (supongo que es una aplicación web) simplemente lee el JSON como está de su servicio web y eso es todo, puede usar el objeto JSON con javascript como desee.

Problemas:

  1. desperdicia espacio Potencialmente, si tiene 100 libros con el mismo autor, una mesa de autor con todos los libros que tienen sólo el espacio author_id es más económico prudente.

  2. Necesidad de implementar índices. dado que sus metadatos son un objeto JSON, no tiene índices de inmediato. Pero es bastante fácil implementar un índice específico para los metadatos específicos que necesita. Por ejemplo, si desea indexar por autor, de modo que cree una tabla author_idx con author_id y item_id, cuando alguien busque un autor, puede buscar esta tabla y los elementos en sí.

Dependiendo de la escala, esto podría ser una exageración. en combinaciones de menor escala funcionaría bien.

13

que no iba a responder, pero en este momento la respuesta aceptada tiene una muy mala idea. Una base de datos relacional nunca debe usarse para almacenar pares simples de atributo-valor. Eso causará muchos problemas en el futuro.

La mejor manera de lidiar con esto es crear una tabla separada para cada tipo.

Product 
------- 
ProductId 
Description 
Price 
(other attributes common to all products) 

Book 
---- 
ProductId (foreign key to Product.ProductId) 
ISBN 
Author 
(other attributes related to books) 

Electronics 
----------- 
ProductId (foreign key to Product.ProductId) 
BatteriesRequired 
etc. 

Cada fila de cada tabla debe representar una proposición sobre el mundo real, y la estructura de las tablas y sus limitaciones deberían reflejar las realidades que están siendo representados. Cuanto más se acerque a este ideal, más limpios serán los datos y más fácil será informar y ampliar el sistema de otras maneras. También funcionará más eficientemente.

+0

De hecho, también me gusta el subtipo de tipo súper - ¿el problema es qué sucede cuando el número de tablas de subtipos se divide en miles? ¿Cómo manejar casos cuando se agregan nuevos tipos al vuelo? ¿Qué se recomendaría en tal caso? Almacenamiento XML nativo o ... –

+0

Estoy muy interesado en ver qué opinas sobre la pregunta de Damir anterior. – bukzor

+1

@bukzor, @Damir Sudarevic - De hecho, yo debería haber respondido a esa pregunta, y lamento que no lo han hecho. La respuesta es que la base de datos debe diseñarse para reflejar la situación real que se conoce. Si se van a agregar nuevos "tipos", solo se pueden modelar relacionalmente las cosas que son consistentes en esos tipos. Puede requerirse algún tipo de sistema de atributo/valor, pero solo para aquellas cosas que son "suaves". Básicamente, si algo puede ser modificado por un usuario del sistema, en lugar de un programador, entonces debe almacenarse como datos en lugar de como una estructura. –

2

En este tipo de problemas, usted tiene tres opciones:

  1. crear una tabla con columnas "genéricas".Por ejemplo, si vende tanto libros como tostadoras, es probable que sus tostadoras no tengan un ISBN y un título, pero todavía tienen algún tipo de identificador y descripción del producto. Así que dar a los campos de nombres genéricos como "product_id" y "Descripción", y para los libros del product_id es un ISBN, por tostadoras su número de parte del fabricante, etc.

Esto funciona cuando las entidades del mundo real son todos procesándose de la misma manera, al menos en su mayor parte, y así debe tener, sino los "mismos" datos, al menos datos análogos. Esto se rompe cuando hay diferencias funcionales reales. Como si para los tostadores estuviéramos calculando vatios = voltios * amperios, es probable que no haya un cálculo correspondiente para los libros. Cuando comienza a crear un campo pages_volts que contiene el recuento de páginas para libros y el voltaje para tostadores, las cosas se han salido de control.

  1. Utilice un esquema de propiedad/valor como lo sugiere Damir. Vea mi comentario sobre su publicación para conocer los pros y contras de allí.

  2. Lo que normalmente te sugeriría es un esquema de tipo/subtipo. Cree una tabla para "producto" que contenga un código de tipo y los campos genéricos. Luego, para cada uno de los tipos verdaderos (libros, tostadores, gatos, lo que sea), cree una tabla separada que esté conectada a la tabla de productos. Luego, cuando necesite hacer un procesamiento específico del libro, procese la tabla del libro. Cuando necesite hacer un procesamiento genérico, procese la tabla de productos.

Cuestiones relacionadas