2010-06-16 21 views
8

Sólo tratando de averiguar la mejor manera de diseñar mi mesa para el siguiente escenario:tabla de base de datos genérica diseño

tengo varias áreas en mi sistema (documentos, proyectos, grupos y clientes) y cada uno de ellos puede tener comentarios registrados contra ellos.

Mi pregunta es debería tener una tabla como la siguiente:

CommentID 
DocumentID 
ProjectID 
GroupID 
ClientID 
etc 

Cuando sólo uno de los identificadores se tienen datos y el resto será nulo o debería tener una tabla CommentType separada y tengo mi mesa de comentarios como este:

CommentID 
CommentTypeID 
ResourceID (this being the id of the project/doc/client) 
etc 

Mi opinión es que la opción 2 sería más eficiente desde un punto de vista de indexación. ¿Es esto correcto?

+0

¿Se puede registrar un solo comentario en más de un elemento? Por ejemplo, un documento y un proyecto o dos documentos? –

+0

Ningún comentario puede ser solo contra un elemento – Gazeth

Respuesta

0

De las opciones que das, me inclinaría por el número 2.

3

Desde una perspectiva clave externa, el primer ejemplo es mejor porque se puede tener múltiples restricciones de clave externa en una columna, pero los datos tienen que existir en todas esas referencias También es más flexible si cambian las reglas comerciales.

+0

+1 ... Buen punto sobre el problema de las claves foráneas. –

+1

No solo es un problema aplicar las claves externas, sino que para que todos los datos vuelvan a salir, debe unirse a la misma tabla cuatro veces. También puede tener cuatro tablas de comentarios separadas que estén especializadas como uso porque, al menos, puede usar las restricciones FK. Las tablas EAV en general no se escalan bien. – HLGEM

5

Lectura sobre la normalización de la base de datos.

Los valores nulos en la forma que describe serían una gran indicación de que la base de datos no está diseñada correctamente.

Necesita dividir todas sus tablas para que los datos almacenados en ellas estén completamente normalizados, esto le ahorrará mucho tiempo más adelante en la línea garantizada, y es mucho mejor práctica para adquirir el hábito.

+4

Bastante seguro de que he deletreado correctamente la 'normalización', estoy en el Reino Unido. –

+1

Lo siento :) Estaba corrigiendo un par de errores ortográficos y mi corrector ortográfico los resaltó. Pensé que ese podría ser el caso, pero pensé que tal vez les ayudaría a los británicos a ver el error de sus formas;) –

3

para continuar desde @OMG Ponies' answer, lo que usted describe en el segundo ejemplo se llama un polimórfica Asociación, donde la clave externa ResourceID puede hacer referencia a las filas de más de una tabla. Sin embargo, en las bases de datos SQL, una restricción de clave externa solo puede hacer referencia exactamente a una tabla. La base de datos no puede aplicar la clave externa de acuerdo con el valor en CommentTypeID.

Usted puede estar interesado en probar el siguiente mensaje de desbordamiento de pila para una solución para hacer frente a este problema:

2

El primer enfoque no es muy grande, ya que es bastante desnormalizará. Cada vez que agrega un nuevo tipo de entidad, necesita actualizar la tabla. Tal vez sea mejor que esto sea un atributo del documento, es decir, almacenar el comentario en línea en la tabla de documentos.

Para el enfoque ResourceID trabajar con integridad referencial, tendrá que tener una mesa Resource, y una clave externa ResourceID en todos sus documentos, etc .. Proyecto entidades (o utilizar una tabla de asignación.) Hacer "ResourceID "Un juego de todos los oficios, que puede ser un ID de documento, ID de proyecto, etc. no es una buena solución, ya que no se puede utilizar para indexación sensible o restricción de clave externa.

Para normalizar, necesita la tabla de comentarios en una tabla por tipo de recurso.

Comment 
------- 
CommentID 
CommentText 
...etc 

DocumentComment 
--------------- 
DocumentID 
CommentID 

ProjectComment 
-------------- 
ProjectID 
CommentID 

Si sólo se permite un comentario, a continuación, se agrega una restricción única en la clave externa para la entidad (DocumentID, Proyectólo etc.) Esto asegura que sólo puede haber una fila para el elemento dado y por lo que sólo un comentario. También puede asegurarse de que los comentarios no se compartan utilizando una restricción única en CommentID.

EDITAR: Curiosamente, esto es casi paralelo a la implementación normalizada de ResourceID - reemplace "Comment" en el nombre de la tabla, con "Resource" y cambie "CommentID" a "ResourceID" y tiene la estructura necesaria para asociar un ResourceID con cada recurso. A continuación, puede usar una sola tabla "ResourceComment".

Si van a haber otras entidades asociadas con cualquier tipo de recurso (por ejemplo, detalles de auditoría, derechos de acceso, etc.), entonces el uso de las tablas de asignación de recursos es el camino a seguir, ya que le permitirá para agregar comentarios normalizados y cualquier otra entidad relacionada con los recursos.

+0

Esto no impide el intercambio de comentarios entre diferentes tipos de entidades (si eso es un requisito). Un ID de comentario podría estar tanto en DocumentComments como en ProjectComments –

+1

@Tom: es correcto, y hasta donde yo sé, no hay una forma relacional para aplicar esto aparte de usar activadores para verificar después de la modificación, o si está disponible, una vista materializada que UNIONs todos los ID de recursos de las tablas Documento, Proyecto, etc. y aplica un constriant de singularidad. – mdma

+0

Alguien había publicado un enlace (parece que lo eliminaron) con una descripción de cómo escribir súper las tablas. No creo que sea apropiado en este caso, pero algo así permitiría la aplicación de lo que estamos hablando. –

0

La opción 2 es un buen camino a seguir. El problema que veo con eso es que estás poniendo la tecla de recursos en esa mesa. Cada uno de los identificadores de los diferentes recursos podría duplicarse. Cuando une recursos a los comentarios, es muy probable que se le ocurran comentarios que no pertenecen a ese recurso en particular. Esto se consideraría unir muchos a muchos. Creo que una mejor opción sería tener tus tablas de recursos, la tabla de comentarios y luego las tablas que hacen referencia cruzada al tipo de recurso y la tabla de comentarios.

+0

Mis consultas sobre esta mesa siempre será algo como: SELECT * DE tblComments DONDE CommentTypeID = 1 y = 1,244 ResourceID – Gazeth

0

Si llevas el mismo tipo de datos sobre todos los comentarios independientemente de sus comentarios, votaría en contra de crear múltiples tablas de comentarios. Tal vez un comentario es solo "de lo que se trata" y texto, pero si no tiene otros datos ahora, es probable que: fecha en que se ingresó el comentario, identificación de usuario de la persona que lo hizo, etc. Con múltiples tablas, usted tiene que repetir todas estas definiciones de columna para cada tabla.

Como se indicó, el uso de un solo campo de referencia significa que no podría poner una restricción de clave foránea en él. Esto es muy malo, pero no rompe nada, solo significa que tiene que hacer la validación con un disparador o en el código. Más en serio, las uniones se vuelven difíciles. Simplemente puede decir "del documento join join using (documentid)". Necesita una unión compleja basada en el valor del campo tipo.

Así que mientras que los campos de múltiples punteros son feos, tiendo a pensar que es el camino correcto a seguir. Sé que algunas personas de DB dicen que nunca debe haber un campo nulo en una tabla, que siempre debes dividirlo en otra tabla para evitar que eso suceda, pero no veo ninguna ventaja real al seguir esta regla.

Personalmente estaría abierto a seguir debatiendo sobre pros y contras.

3

La opción 2 es no una buena solución para una base de datos relacional. Se llama asociaciones polimórficas (como lo menciona @Daniel Vassallo) y rompe la definición fundamental de una relación.

Por ejemplo, supongamos que tiene un Id. De recursos de 1234 en dos filas diferentes. ¿Representan estos el mismo recurso? Depende de si el CommentTypeId es el mismo en estas dos filas. Esto viola el concepto de tipo en una relación. Ver SQL and Relational Theory por C. J. Date para más detalles.

Otra pista de que se trata de un diseño roto es que no se puede declarar una restricción de clave externa para ResourceId, ya que podría apuntar a cualquiera de varias tablas.Si intenta forzar la integridad referencial usando disparadores o algo así, se encuentra reescribiendo el desencadenador cada vez que agrega un nuevo tipo de recurso commentable.

Me gustaría resolver esto con la solución que @mdma menciona brevemente (pero luego pasa por alto):

CREATE TABLE Commentable (
    ResourceId INT NOT NULL IDENTITY, 
    ResourceType INT NOT NULL, 
    PRIMARY KEY (ResourceId, ResourceType) 
); 

CREATE TABLE Documents (
    ResourceId INT NOT NULL, 
    ResourceType INT NOT NULL CHECK (ResourceType = 1), 
    FOREIGN KEY (ResourceId, ResourceType) REFERENCES Commentable 
); 

CREATE TABLE Projects (
    ResourceId INT NOT NULL, 
    ResourceType INT NOT NULL CHECK (ResourceType = 2), 
    FOREIGN KEY (ResourceId, ResourceType) REFERENCES Commentable 
); 

Ahora cada tipo de recurso tiene su propia mesa, pero la clave principal en serie se asigna de forma única por commentable. Un valor de clave primaria dado puede ser utilizado solo por un tipo de recurso.

CREATE TABLE Comments (
    CommentId INT IDENTITY PRIMARY KEY, 
    ResourceId INT NOT NULL, 
    ResourceType INT NOT NULL, 
    FOREIGN KEY (ResourceId, ResourceType) REFERENCES Commentable 
); 

Ahora los comentarios son recursos commentables, con integridad referencial impuesta. Un comentario dado puede hacer referencia solo a un tipo de recurso. No hay posibilidad de anomalías o identificadores de recursos conflictivos.

Cubro más sobre asociaciones polimórficas en mi presentación Practical Object-Oriented Models in SQL y mi libro SQL Antipatterns.

+1

Kudos para SQL antipatterns, Bill :) Eso es una gran pieza de trabajo. Obtuve el libro electrónico la semana pasada, y actualmente estoy leyendo un capítulo por día. Mi marcador está en el capítulo de asociaciones polimórficas por cierto :) ... ¡Felicitaciones! –

1

No iría con ninguna de esas soluciones. En función de algunos de los detalles de sus necesidades usted podría ir con una mesa de super-tipo:

CREATE TABLE Commentable_Items (
    commentable_item_id INT NOT NULL, 
    CONSTRAINT PK_Commentable_Items PRIMARY KEY CLUSTERED (commentable_item_id)) 
GO 
CREATE TABLE Projects (
    commentable_item_id INT NOT NULL, 
    ... (other project columns) 
    CONSTRAINT PK_Projects PRIMARY KEY CLUSTERED (commentable_item_id)) 
GO 
CREATE TABLE Documents (
    commentable_item_id INT NOT NULL, 
    ... (other document columns) 
    CONSTRAINT PK_Documents PRIMARY KEY CLUSTERED (commentable_item_id)) 
GO 

Si el cada elemento sólo puede tener uno de los comentarios y las observaciones no son compartidas (es decir, un comentario sólo puede pertenecer a una entidad) entonces podría simplemente poner los comentarios en la tabla Commentable_Items. De lo contrario, podría vincular los comentarios de esa tabla con una clave externa.

No me gusta mucho este enfoque en su caso específico, porque "tener comentarios" no es suficiente para juntar elementos así en mi mente.

Probablemente iría con tablas de Comentarios separadas (suponiendo que pueda tener varios comentarios por artículo; de lo contrario, simplemente colóquelos en sus tablas base). Si un comentario puede ser compartida entre varios tipos de entidad (es decir, un documento y un proyecto pueden compartir el mismo comentario) tienen entonces una mesa Comentarios central y varias tablas de relación entidad-comentario:

CREATE TABLE Comments (
    comment_id INT   NOT NULL, 
    comment_text NVARCHAR(MAX) NOT NULL, 
    CONSTRAINT PK_Comments PRIMARY KEY CLUSTERED (comment_id)) 
GO 
CREATE TABLE Document_Comments (
    document_id INT NOT NULL, 
    comment_id  INT NOT NULL, 
    CONSTRAINT PK_Document_Comments PRIMARY KEY CLUSTERED (document_id, comment_id)) 
GO 
CREATE TABLE Project_Comments (
    project_id  INT NOT NULL, 
    comment_id  INT NOT NULL, 
    CONSTRAINT PK_Project_Comments PRIMARY KEY CLUSTERED (project_id, comment_id)) 
GO 

Si desea limitar comentarios a un único documento (por ejemplo), entonces podría agregar un índice único (o cambiar la clave principal) en el comment_id dentro de esa tabla de vinculación.

Son todas estas "pequeñas" decisiones las que afectarán a los PK y FK específicos. Me gusta este enfoque porque cada tabla es clara en lo que es. En bases de datos que generalmente es mejor que tener tablas/soluciones "genéricas".

0

Pawnshop Aplicación:

tengo tablas separadas para préstamos, compra, inventario & transacciones de ventas. Cada mesas filas se unen a sus respectivas filas de clientes por:

customer.pk [serial] = loan.fk [integer]; 
        = purchase.fk [integer]; 
        = inventory.fk [integer]; 
        = sale.fk [integer]; 

he consolidado las cuatro tablas en una tabla llamada "transacción", donde una columna:

transacción.Char trx_type (1) {L = Préstamo, P = Compra, I = Inventario, S = Venta}

Escenario: mercancía

un cliente inicialmente peones, hace un par de pago de intereses, y luego decide que quiere venda la mercancía a la casa de empeño, que luego coloca la mercadería en Inventario y eventualmente la vende a otro cliente.

que diseñó una tabla de transacciones genérico donde, por ejemplo:

transaction.main_amount DECIMAL (7,2)

en una operación de préstamo mantiene la cantidad de peones, en una compra vale el precio de compra, en inventario y venta tiene precio de venta.

Este es claramente un diseño desnormalizado, pero ha hecho que la programación sea mucho más fácil y un rendimiento mejorado. Ahora se puede realizar cualquier tipo de transacción desde una pantalla, sin la necesidad de cambiar a tablas diferentes.

Cuestiones relacionadas