Desde el punto de vista conceptual, aunque en su entorno empresarial Order and Address son ideas estrechamente asociadas, en realidad son dos tipos de entidades separadas, cada una con su propio conjunto de propiedades (o atributos) aplicables y restricciones.
Por lo tanto, como se indicó anteriormente en los comentarios, estoy de acuerdo con @Erik , y debe organizar el diseño lógico de su base de datos declarando entre otros elementos:
- una tabla discreta para guardar la información de la Dirección ;
- una tabla para retener detalles específicos del Cliente ;
- una tabla para encerrar los puntos de datos de la Orden ; y
- una tabla para contener datos sobre las asociaciones entre Cliente (s) y Dirección (es) ;
como ejemplificaré a continuación.
Diagrama IDEF1X expositivo
Una imagen vale más que mil palabras, así que creé el diagrama IDEF1X que se muestra en la Figura 1 para ilustrar algunas de las posibilidades abiertas por mi sugerencia:
Cliente , dirección y sus asociaciones
Como se demostró, describí una asociación con una relación de cardinalidad de muchos a muchos (M: N) entre los tipos de entidad Cliente a y Dirección ; Este enfoque proporcionaría flexibilidad futura porque, como saben, un Cliente puede mantener múltiples Direcciones a lo largo del tiempo, o incluso simultáneamente, y la misma Dirección puede ser compartida por múltiples Clientes .
Una dirección particular puede ser utilizada de varias maneras por clientes de uno a muchos (1: M) ; por ejemplo, se puede definir como Físico , y / o se puede configurar para Envío y / o para Facturación . Quizás, la misma instancia de Dirección puede servir para cada uno de los propósitos mencionados al mismo tiempo, o puede estar cubriendo dos usos, mientras que una ocurrencia de Dirección diferente cubre el restante.
a En algunos entornos comerciales, un Cliente puede ser una Persona o una Organización (situación que implicaría una disposición ligeramente distinta, como se detalla en esta respuesta sobre una estructura de supertipo-subtipo) pero con el objetivo de proporcionar un ejemplo simplificado, decidí no incluir esa posibilidad aquí. En caso de que necesite cubrir esa situación en su base de datos, la publicación del enlace anterior muestra el método para resolver dicho requisito.
Solicitar , dirección , CustomerAddress y abordar las funciones
Por lo general, un pedido requiere solo dos tipos de direcciones , una para envío y otra para facturación . De esta manera, la misma instancia de Dirección podría llenar ambos Roles para un Pedido individual , pero cada Rol está representado por la propiedad respectiva, es decir, ShippingAddressId o BillingAddressId .
El pedido está conectado con la Dirección a través del tipo de entidad asociativa CustomerAddress con la ayuda de dos CLAVES EXTRANJERAS de múltiples propiedades, es decir,
- ( CustomerNumber , ShippingAddressId ) y ( CustomerNumber , BillingAddressId ),
ambos apuntando a la CLAVE PRIMARIA de múltiples propiedades CustomerAddress mostrada como
- ( CustomerNumber , AddressId )
... lo que ayuda a representar una regla comercial que estipula que (a) una instancia de Pedido debe estar vinculada exclusivamente con (b) Ocurrencias de direcciones previamente asociadas con el Cliente específico que realizó ese Pedido , y nunca con (c) un cliente aleatorio que no sea Cliente - Dirección relacionada .
Historial para (1) Dirección y para (2) la asociación CustomerAddress
Si desea proporcionar la posibilidad de modificar la información de la Dirección , debe realizar un seguimiento de todos los cambios en los datos. De esta manera, describí Address como un tipo de entidad "auditable" que mantiene su propia AddressHistory .
Dado que la naturaleza de una conexión entre un Cliente y una Dirección también puede sufrir una o más modificaciones, también describí la posibilidad de manejar dicha asociación como "auditable" en virtud del tipo de entidad CustomerAddressHistory .
A este respecto, varios factores tratados en Q & A no. 1 y Q & A no. 2 , —tanto sobre habilitar las capacidades temporales en una base de datos— son realmente relevantes.
Diseño lógico ilustrativo de SQL-DDL
En consecuencia, en términos del diagrama que se muestra y explica arriba, declaró la siguiente disposición de nivel lógico (que puede adaptar para satisfacer sus necesidades con exactitud):
-- You should determine which are the most fitting
-- data types and sizes for all your table columns
-- depending on your business context characteristics.
-- Also, you should make accurate tests to define the
-- most convenient INDEX strategies based on the exact
-- data manipulation tendencies of your business domain.
-- As one would expect, you are free to utilize
-- your preferred (or required) naming conventions.
CREATE TABLE Customer (
CustomerNumber INT NOT NULL,
SpecificAttribute CHAR(30) NOT NULL,
ParticularAttribute CHAR(30) NOT NULL,
CreatedDateTime DATETIME NOT NULL,
--
CONSTRAINT Customer_PK PRIMARY KEY (CustomerNumber)
);
CREATE TABLE Address (
AddressId INT NOT NULL,
SpecificAttribute CHAR(30) NOT NULL,
ParticularAttribute CHAR(30) NOT NULL,
CreatedDateTime DATETIME NOT NULL,
--
CONSTRAINT Address_PK PRIMARY KEY (AddressId)
);
CREATE TABLE CustomerAddress (
CustomerNumber INT NOT NULL,
AddressId INT NOT NULL,
IsPhysical BIT NOT NULL,
IsShipping BIT NOT NULL,
IsBilling BIT NOT NULL,
IsActive BIT NOT NULL,
CreatedDateTime DATETIME NOT NULL,
--
CONSTRAINT CustomerAddress_PK PRIMARY KEY (CustomerNumber, AddressId),
CONSTRAINT CustomerAddressToCustomer_FK FOREIGN KEY (CustomerNumber)
REFERENCES Customer (CustomerNumber),
CONSTRAINT CustomerAddressToAddress_FK FOREIGN KEY (AddressId)
REFERENCES Address (AddressId)
);
CREATE TABLE MyOrder (
CustomerNumber INT NOT NULL,
OrderNumber INT NOT NULL,
ShippingAddressId INT NOT NULL,
BillingAddressId INT NOT NULL,
SpecificAttribute CHAR(30) NOT NULL,
ParticularAttribute CHAR(30) NOT NULL,
OrderDate DATE NOT NULL,
CreatedDateTime DATETIME NOT NULL,
--
CONSTRAINT Order_PK PRIMARY KEY (CustomerNumber, OrderNumber),
CONSTRAINT OrderToCustomer_FK FOREIGN KEY (CustomerNumber)
REFERENCES Customer (CustomerNumber),
CONSTRAINT OrderToShippingAddress_FK FOREIGN KEY (CustomerNumber, ShippingAddressId)
REFERENCES CustomerAddress (CustomerNumber, AddressId),
CONSTRAINT OrderToBillingAddress_FK FOREIGN KEY (CustomerNumber, BillingAddressId)
REFERENCES CustomerAddress (CustomerNumber, AddressId)
);
CREATE TABLE AddressHistory (
AddressId INT NOT NULL,
AuditedDateTime DATETIME NOT NULL,
SpecificAttribute CHAR(30) NOT NULL,
ParticularAttribute CHAR(30) NOT NULL,
CreatedDateTime DATETIME NOT NULL,
--
CONSTRAINT AddressHistory_PK PRIMARY KEY (AddressId, AuditedDateTime),
CONSTRAINT AddressHistoryToAddress_FK FOREIGN KEY (AddressId)
REFERENCES Address (AddressId)
);
CREATE TABLE CustomerAddressHistory (
CustomerNumber INT NOT NULL,
AddressId INT NOT NULL,
AuditedDateTime DATETIME NOT NULL,
IsPhysical BIT NOT NULL,
IsShipping BIT NOT NULL,
IsBilling BIT NOT NULL,
IsActive BIT NOT NULL,
CreatedDateTime DATETIME NOT NULL,
--
CONSTRAINT CustomerAddressHistory_PK PRIMARY KEY (CustomerNumber, AddressId, AuditedDateTime),
CONSTRAINT CustomerAddressHistoryToCustomerAddress_FK FOREIGN KEY (CustomerNumber, AddressId)
REFERENCES CustomerAddress (CustomerNumber, AddressId)
);
Si desea echar un vistazo, lo probé en este violín db <> que se ejecuta en SQL Server 2017.
Las History
mesas
El siguiente extracto de su pregunta es muy importante:
Lo que estoy buscando es cómo puedo configurar mis direcciones para que cuando las edite, el pedido no se vea afectado por el hecho de que un cliente actualiza su dirección o se reubica.
Las tablas AddressHistory
y CustomerAddressHistory
ayudan a garantizar que un pedido no se vea afectado por los cambios de dirección , ya que todas las filas "anteriores" deben conservarse en la History
tabla respectiva y pueden consultarse cuando sea necesario. Las operaciones de ACTUALIZAR y ELIMINAR en estas dos tablas deberían estar prohibidas (intentar cambiar el historial puede incluso tener implicaciones legales negativas).
El intervalo abarca los valores encerrados AddressHistory.CreatedDateTime
y AddressHistory.AuditedDateTime
representa todo el período durante el cual una determinada Address
fila "pasada" se consideró "presente", "actual" o "efectiva". Consideraciones similares se aplican a las CustomerAddressHistory
filas.
La CustomerAddress.IsActive
columna BIT (booleana) está destinada a señalar si una Address
fila es "utilizable" por una Customer
fila o no; por ejemplo, si se establece en 'falso', transmitiría el hecho de que un Cliente ya no está usando esa Dirección y, por lo tanto, no se puede usar para nuevos Pedidos .
Nota : Por otro lado, he visto algunos sistemas en los que cada vez que se efectúa un nuevo pedido, se debe ingresar la información de la dirección (algunas veces repetidamente), y las direcciones utilizadas para pedidos anteriores nunca se borran (por lo tanto, los pedidos no se ven afectados por los cambios de dirección ).
Este curso de acción puede implicar decididamente volúmenes sustanciales de redundancia, pero es una posibilidad que, dependiendo de los requisitos de información exactos de su dominio comercial, podría funcionar, por lo que también le gustaría evaluar sus pros y sus contras.
Recuperación de datos
La versión "presente", "actual" o "efectiva" de una ocurrencia de Dirección debe estar contenida como una fila en la Address
tabla, pero SELECCIONAR los "estados" anteriores de una Dirección DESDE la tabla AddressHistory
(o desde CustomerAddressHistory
) es fácil, y puede Ser un ejercicio interesante para mejorar sus habilidades de codificación SQL.
Con respecto a una de las situaciones que mencionó en los comentarios, si desea recuperar la "penúltima versión" de una Address
fila individual DESDE su AddressHistory
, debe tener en cuenta la MAX(AddressHistory.AuditedDateTime)
y la AddressHistory.AddressId
que coincida con el Address.AddressId
valor particular en cuestión.
En este sentido, al menos cuando se construye una base de datos relacional , es bastante conveniente definir primero el esquema conceptual correspondiente (basado en las reglas comerciales aplicables ) y luego declarar su posterior disposición lógica DDL. Una vez que obtenga versiones estables y confiables de estos elementos fundamentales (que, por supuesto, pueden evolucionar con el tiempo), es hora de analizar y determinar las mejores formas de manipular (a través de las operaciones INSERT, UPDATE, DELETE y SELECT o combinaciones de ellas) sobre los datos
Percepción de los usuarios finales, vistas y asistencia para los programas
Evidentemente, en el nivel externo de abstracción, la información de la dirección es percibida (por los usuarios finales) como parte de una orden , y no hay nada de malo en eso, pero eso no significa que los modeladores tengan que diseñar las partes significativas de la orden. base de datos en cuestión como esa. En este punto, si es necesario, por ejemplo, imprimir un pedido "completo" (muy factible), puede "reproducirlo" a pedido con la ayuda de unos pocos operadores JOIN y cláusulas WHERE (considerando el período de validez correspondiente) , etc.) pueden fijarse en vistas para consumo futuro, enviando el conjunto de resultados pertinente a los programas de aplicación relacionados que, a su vez, pueden mejorar su formato según sea necesario.
Por supuesto, el / los programa (s) de aplicación también serán muy útiles cuando se efectúe una Orden ; por ejemplo, una ventana de aplicación de escritorio / móvil o una página web puede:
- mostrar solo las direcciones que el cliente involucrado ha establecido como "utilizables" (a través de
CustomerAddress.IsActive
);
- enumerar todas las direcciones que el cliente ha habilitado para el servicio de facturación (a través de
CustomerAddress.IsBilling
); y
- agrupe todas las direcciones que el cliente ha definido para el servicio de envío (a través de
CustomerAddress.IsShipping
);
facilitando de esta manera todos los procesos involucrados en la GUI (es decir, el nivel externo de abstracción de un sistema computarizado).
Lectura sugerida
Solicitó (en comentarios ahora eliminados) algunos consejos sobre literatura de bases de datos de sonido; por lo tanto, en cuanto al material teórico , le recomiendo que lea todo el trabajo escrito por el Dr. EF Codd , un ganador del Premio Turing y, por supuesto, el único autor del modelo relacional de datos (quizás ahora más relevante que nunca). Esta lista incluye algunos de sus artículos y artículos tremendamente influyentes.
Dos trabajos importantes que no están incluidos en la lista mencionada anteriormente son, precisamente, su Conferencia ACM Turing Award titulada Base de datos relacional: una base práctica para la productividad , de 1981, y su libro denominado El modelo relacional para la gestión de bases de datos: Versión 2 , que se publicó en 1990.
En el frente del diseño conceptual , la Definición Integrada para el Modelado de Información (IDEF1X) es una técnica muy recomendable que fue definida como un estándar en diciembre de 1993 por el Instituto Nacional de Estándares y Tecnología (NIST) de EE. UU .
MyOrder.ShippingAddressId
yMyOrder.BillingAddressId
deben hacer referencia aCustomerAddress.AddressId
(y no aAddress.AddressId
); de esta manera, se asegura que un Pedido pueda asociarse exclusivamente con la (s) Dirección (es) previamente conectadas con el Cliente que realizó ese Pedido . El diagrama sugiere esta disposición, por lo que el DDL será más preciso. Gracias por solicitar esa aclaración.History
tabla, ¿debería ser igual para laAddress
tabla? ¿Qué sucede si el Cliente ordena algo y luego cambia solo el código postal o la ciudad en un solo campo? Deberíamos insertar la dirección existenteHistory
y luego hacer una nueva inserción en laAddress
tabla, ¿verdad?Address
fila correspondiente que estaba "presente" hasta que se realizó la modificación se INSERTE en laAddressHistory
tabla, y también que (b ) laAddress
fila en cuestión se ACTUALIZA con los nuevos valores. Sería ventajoso llevar a cabo este proceso como una sola unidad de trabajo dentro de una transacción.Esta respuesta fue compilada de los comentarios a la pregunta.
Una solución sería utilizar un FK para la tabla de direcciones en la tabla de pedidos. Eso le permitirá ver las direcciones que se usaron para el pedido y desacoplará la dirección de la dirección actual del Usuario.
Para que esto funcione, deberá insertar una nueva dirección y vincular esa nueva dirección a la tabla Usuario. Esto significa que las direcciones se escriben una vez y la edición es una ilusión para el usuario final. Puede almacenar efectivamente el historial de todas las direcciones con las que se asoció un usuario moviendo la asociación de la tabla Usuario a una tabla de asociación con una marca de tiempo. Eso le daría un historial de ediciones / direcciones y mantendría datos inmutables en la tabla de direcciones.
@MDCCL declaró:
MDCCL también dio una descripción general sobre cómo encontrar la dirección actual de un usuario aquí:
fuente