Almacenamiento de una mejor práctica de dirección de facturación en la tabla de pedidos

10

¿Alguien puede ayudarme a entender la respuesta de este usuario para una tabla CustomerLocation ? Realmente quiero un buen método para almacenar direcciones en la tabla de pedidos.

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.

Tal como está, mi esquema es similar a:

 Person           |EntityID|
 EntityAddress    |EntityID|AddressID|
 Address          |AddressID|AddressType|AddressLine1|AddressLine2|
 Order            |OrderID|BillingAddressID|
Comunidad
fuente

Respuestas:

16

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:

Figura 1 - Diagrama IDEF1X del Expositivo de Clientes, Pedidos y Direcciones

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 Historymesas

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 AddressHistoryy CustomerAddressHistoryayudan 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 Historytabla 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.CreatedDateTimey AddressHistory.AuditedDateTimerepresenta todo el período durante el cual una determinada Addressfila "pasada" se consideró "presente", "actual" o "efectiva". Consideraciones similares se aplican a las CustomerAddressHistoryfilas.

La CustomerAddress.IsActivecolumna BIT (booleana) está destinada a señalar si una Addressfila es "utilizable" por una Customerfila 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 Addresstabla, 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 Addressfila individual DESDE su AddressHistory, debe tener en cuenta la MAX(AddressHistory.AuditedDateTime)y la AddressHistory.AddressIdque coincida con el Address.AddressIdvalor 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 .

MDCCL
fuente
1
Lo sentimos, sé que la publicación es anterior, pero ¿por qué hace referencia (por ejemplo: REFERENCES Address (AddressId)) en MyOrder? ¿Por qué no CustomerAddress?
Shadrix
1
Sin preocupaciones, y buena captura: De hecho, ambos MyOrder.ShippingAddressIdy MyOrder.BillingAddressIddeben hacer referencia a CustomerAddress.AddressId(y no a Address.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.
MDCCL
2
@ Shadrix Acabo de editar la publicación, en caso de que quieras echar un vistazo.
MDCCL
@MDCCL Cuando dijo NO ACTUALIZAR y ELIMINAR en la Historytabla, ¿debería ser igual para la Addresstabla? ¿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 existente Historyy luego hacer una nueva inserción en la Addresstabla, ¿verdad?
Mike Ross el
1
OTOH, si un Cliente desea cambiar una o más piezas de información sobre una Dirección dada , uno debe asegurarse de que (a) la Addressfila correspondiente que estaba "presente" hasta que se realizó la modificación se INSERTE en la AddressHistorytabla, y también que (b ) la Addressfila 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.
MDCCL
3

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ó:

[debe] organizar la estructura de su base de datos con una tabla para retener los datos relacionados con el pedido y otra tabla para mantener la información de la dirección. Y sí, definitivamente puede tener una tabla que represente una relación de muchos a muchos entre estos dos tipos de entidades. Si un usuario puede cambiar sus atributos de dirección (es), entonces debe realizar un seguimiento de dichas modificaciones, por lo tanto, debe habilitar las correspondientes AddressHistory. Esta publicación está relacionada con el último aspecto.

MDCCL también dio una descripción general sobre cómo encontrar la dirección actual de un usuario aquí:

Para obtener la última versión de una tabla de historial que tiene, debe tener en cuenta la MAX(AuditedDateTime)de la tabla correspondiente AddressId. El primer paso es modelar / diseñar sus mejores arreglos conceptuales y lógicos posibles, el segundo paso es encontrar las formas adecuadas de INSERTAR, ACTUALIZAR, ELIMINAR y SELECCIONAR sus datos.

Erik
fuente