Armazenamento das melhores práticas de endereço de cobrança na tabela Pedidos

10

Alguém pode me ajudar a entender a resposta deste usuário para uma tabela CustomerLocation . Eu realmente quero um bom método para armazenar endereços na tabela de pedidos.

O que estou procurando é como posso configurar meus endereços para que, quando eu os edite, o pedido não seja efetuado pelo fato de um cliente atualizar seu endereço ou se mudar.

Tal como está, meu esquema é semelhante a:

 Person           |EntityID|
 EntityAddress    |EntityID|AddressID|
 Address          |AddressID|AddressType|AddressLine1|AddressLine2|
 Order            |OrderID|BillingAddressID|
Comunidade
fonte

Respostas:

16

Conceitualmente falando, embora em seu ambiente de negócios, Ordem e Endereço sejam idéias estreitamente associadas, elas são, na verdade, dois tipos de entidade separados, cada um com seu próprio conjunto de propriedades (ou atributos) e restrições aplicáveis.

Portanto, como declarado anteriormente nos comentários, concordo com o @Erik , e você deve organizar o layout lógico do seu banco de dados declarando entre outros elementos:

  • uma tabela discreta para manter as informações do endereço ;
  • uma tabela para reter detalhes específicos do cliente ;
  • uma tabela para incluir pontos de dados do pedido ; e
  • uma tabela para conter fatos sobre as associações entre Cliente (s) e Endereço (s) ;

como vou exemplificar abaixo.

Diagrama do IDEF1X expositivo

Uma imagem vale mais que mil palavras, por isso criei o diagrama IDEF1X mostrado na Figura 1 para ilustrar algumas das possibilidades abertas pela minha sugestão:

Figura 1 - Diagrama do IDEF1X de clientes, pedidos e endereços

Cliente , endereço e suas associações

Como demonstrado, eu retratada uma associação com um de muitos para muitos (M: N) cardinalidade proporção entre os tipos de entidade de cliente um e de endereços ; essa abordagem forneceria flexibilidade futura porque, como você sabe, um cliente pode manter vários endereços ao longo do tempo, ou mesmo simultaneamente, e o mesmo endereço pode ser compartilhado por vários clientes .

Um endereço específico pode ser usado de várias maneiras por clientes um para muitos (1: M) ; por exemplo, pode ser definido como físico e / ou definido para remessa e / ou cobrança . Talvez, a mesma instância de Endereço possa atender a cada um dos propósitos mencionados ao mesmo tempo, ou pode estar cobrindo dois usos, enquanto uma ocorrência de Endereço diferente cobre o restante.

a Em alguns ambientes de negócios, um Cliente pode ser uma Pessoa ou uma Organização (situação que implicaria um arranjo ligeiramente distinto, conforme detalhado nesta resposta sobre uma estrutura de supertipo-subtipo), mas com o objetivo de fornecer um exemplo simplificado, decidi para não incluir essa possibilidade aqui. Caso você precise cobrir essa situação em seu banco de dados, a postagem do link anterior mostra o método para resolver o requisito.

Ordem , endereço , CustomerAddress e Roles endereço

Geralmente, um pedido requer apenas dois tipos de endereços , um para remessa e outro para cobrança . Dessa maneira, a mesma instância de Endereço pode preencher as duas Funções de um Pedido individual , mas cada Função é representada pela propriedade respectiva, ou seja, ShippingAddressId ou BillingAddressId .

O pedido é conectado ao Endereço por meio do tipo de entidade associativa CustomerAddress com o auxílio de duas CHAVES ESTRANGEIRAS de várias propriedades, ou seja,

  • ( CustomerNumber , ShippingAddressId ) e ( CustomerNumber , BillingAddressId ),

ambos apontando para a CHAVE PRIMÁRIA de várias propriedades CustomerAddress mostrada como

  • ( CustomerNumber , AddressId )

... o que ajuda a representar uma regra comercial que estipula que (a) uma instância do Pedido deve ser vinculada exclusivamente a (b) Ocorrências de endereços anteriormente associadas ao Cliente específico que fez esse Pedido e nunca com (c) um não Cliente aleatório - Endereço relacionado .

Histórico para (1) Endereço e (2) para a associação CustomerAddress

Se você deseja fornecer a possibilidade de modificar as informações do Endereço , deve acompanhar todas as alterações de dados. Dessa maneira, descrevi Address como um tipo de entidade "auditável" que mantém seu próprio AddressHistory .

Como a natureza de uma conexão entre um Cliente e um Endereço também pode sofrer uma ou mais modificações, também descrevi a possibilidade de lidar com essa associação como uma associação “auditável” em virtude do tipo de entidade CustomerAddressHistory .

A esse respeito, vários fatores abordados nas perguntas e respostas não. 1 e perguntas e respostas não. 2 , tanto sobre ativar recursos temporais em um banco de dados, são realmente relevantes.

Layout lógico SQL-DDL ilustrativo

Consequentemente, em termos do diagrama exibido e explicado acima, declarei a seguinte organização de nível lógico (que você pode adaptar para atender às suas necessidades com exatidão):

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

Se você quiser dar uma olhada, testei-o neste db <> violino que é executado no SQL Server 2017.

As Historymesas

O seguinte trecho da sua pergunta é muito importante:

O que estou procurando é como posso configurar meus endereços para que, quando os editar, o pedido não seja afetado pelo fato de um cliente atualizar seu endereço ou se mudar.

As tabelas AddressHistorye CustomerAddressHistoryajudam a garantir que um pedido não seja afetado pelas alterações de endereço , pois todas as linhas "anteriores" devem ser mantidas na respectiva Historytabela e podem ser consultadas quando necessário. As operações UPDATE e DELETE nessas duas tabelas devem ser proibidas (tentar alterar o histórico pode até ter implicações legais negativas).

O Intervalo englobou os valores incluídos AddressHistory.CreatedDateTimee AddressHistory.AuditedDateTimerepresenta todo o Período durante o qual uma determinada Addresslinha "passada" foi considerada "presente", "atual" ou "efetiva". Considerações semelhantes se aplicam às CustomerAddressHistorylinhas.

A CustomerAddress.IsActivecoluna BIT (booleana) deve indicar se uma determinada Addresslinha é "utilizável" por uma Customerlinha ou não; por exemplo, se estiver definido como 'false', transmitiria o fato de que um cliente não está mais usando esse endereço e, portanto, não pode ser usado para novos pedidos .


Nota : Por outro lado, eu vi alguns sistemas nos quais, toda vez que um novo pedido é efetuado, as informações do endereço devem ser inseridas (algumas vezes repetidamente) e os endereços usados ​​para pedidos anteriores nunca são apagados (portanto, os pedidos não são afetados pelas alterações de endereço ).

Esse curso de ação pode, decididamente, envolver volumes substanciais de redundância, mas é uma possibilidade - dependendo dos requisitos informacionais exatos do seu domínio de negócios - poderia funcionar, então você também pode avaliar seus prós e contras.


Recuperação de dados

A versão "presente", "atual" ou "efetiva" de uma ocorrência de Endereço deve estar contida como uma linha na Addresstabela, mas SELECIONAR os "estados" anteriores de um Endereço A partir da tabela AddressHistory(ou da CustomerAddressHistory) é fácil e pode um exercício interessante para aprimorar suas habilidades de codificação SQL.

Com relação a uma das situações mencionadas nos comentários, se você deseja recuperar a “penúltima versão” de uma Addresslinha individual DA AddressHistory, deve levar em consideração MAX(AddressHistory.AuditedDateTime)oe o AddressHistory.AddressIdque corresponde ao Address.AddressIdvalor específico em questão.

Nesse sentido, pelo menos ao criar um banco de dados relacional , é bastante conveniente definir primeiro o esquema conceitual correspondente (com base nas regras de negócios aplicáveis ) e depois declarar seu arranjo lógico de DDL subsequente . Depois de obter versões estáveis ​​e confiáveis ​​desses elementos fundamentais (que, é claro, podem evoluir com o tempo), é hora de analisar e determinar as melhores maneiras de manipular (via operações INSERT, UPDATE, DELETE e SELECT ou combinações delas) o sobre dados.

Percepção dos usuários finais, visões e assistência do (s) aplicativo (s)

Evidentemente, no nível externo da abstração, as informações de Endereço são percebidas (pelos usuários finais) como parte de um Pedido , e não há nada de errado nisso, mas isso não significa que os modeladores tenham que projetar as partes significativas do banco de dados em questão assim. Nesse ponto, se houver a necessidade de, por exemplo, imprimir um pedido "completo" (muito viável), você poderá "reproduzi-lo" sob demanda com a ajuda de alguns operadores JOIN e cláusulas WHERE (considerando o período de validade relacionado) , etc.) podem ser fixados em visualizações para consumo futuro, enviando o resultado pertinente definido para o (s) programa (s) aplicativo (s) relacionado (s) que, por sua vez, podem aprimorar sua formatação conforme necessário.

Obviamente, o (s) programa (s) aplicativo (s) também será muito útil quando uma Ordem estiver sendo efetivada; por exemplo, uma janela de aplicativo para computador / dispositivo móvel ou uma página da web pode:

  • exibir apenas os endereços que o Cliente envolvido estabeleceu como “utilizável” (via CustomerAddress.IsActive);
  • listar todos os endereços que o cliente ativou para o serviço de cobrança (via CustomerAddress.IsBilling); e
  • agrupe todos os endereços que o cliente definiu para o serviço de remessa (via CustomerAddress.IsShipping);

facilitando dessa maneira todos os processos envolvidos na GUI (ou seja, o nível externo de abstração de um sistema computadorizado).


Leitura sugerida

Você solicitou (nos comentários agora removidos) algumas dicas sobre literatura de banco de dados de som; portanto, quanto ao material teórico , eu recomendo que você leia todo o trabalho escrito pelo Dr. EF Codd , um ganhador do Prêmio Turing e, é claro, o único criador do modelo relacional de dados (talvez agora mais relevante do que nunca). Esta lista inclui alguns de seus artigos e documentos tremendamente influentes.

Dois trabalhos importantes que não constam da lista mencionada são, precisamente, sua palestra do Prêmio ACM Turing, intitulada Banco de dados relacional: uma base prática para a produtividade , de 1981, e seu livro denominado Modelo relacional para gerenciamento de banco de dados: versão 2 , publicado em 1990.

Na frente do projeto conceitual , a Definição Integrada para Modelagem de Informações (IDEF1X) é uma técnica seriamente recomendável que foi definida como padrão em dezembro de 1993 pelo Instituto Nacional de Padrões e Tecnologia dos EUA (NIST).

MDCCL
fonte
11
Desculpe, sei que a postagem é mais antiga, mas por que você está fazendo referência (por exemplo: Endereço de REFERÊNCIAS (AddressId)) no MyOrder? Por que não o CustomerAddress?
Shadrix 12/11/19
11
Não se preocupe, e boa captura: de fato, ambos MyOrder.ShippingAddressIde MyOrder.BillingAddressIddevem fazer referência a CustomerAddress.AddressId(e não a Address.AddressId); dessa maneira, assegura-se de que um Pedido possa ser associado exclusivamente aos endereços anteriormente conectados ao Cliente que fez esse Pedido . O diagrama sugere esse arranjo, para que o DDL seja mais preciso. Obrigado por solicitar esse esclarecimento.
MDCCL
2
@ Shadrix Acabei de editar o post, caso você queira dar uma olhada.
MDCCL
@MDCCL Quando você disse que não há UPDATE e DELETE na Historytabela, deveria ser o mesmo para a Addresstabela? E se o Cliente pedir algo e depois alterar apenas o código postal ou cidade apenas um campo. Devemos inserir o endereço existente Historye depois fazer uma nova inserção na Addresstabela, certo?
Mike Ross
11
OTOH, se um Cliente quiser alterar uma ou mais informações sobre um determinado Endereço , deve-se garantir que (a) a Addresslinha correspondente que estava "presente" até que a modificação ocorreu seja INSERIDA na AddressHistorytabela e também que (b ) a Addresslinha em questão é UPDATEd com os novos valores. Seria vantajoso executar esse processo como uma única unidade de trabalho dentro de uma transação.
MDCCL 12/04/19
3

Esta resposta foi compilada dos comentários à pergunta.

Uma solução seria usar um FK para a tabela de endereços na tabela de pedidos. Isso permitirá que você veja os endereços que foram usados ​​para o pedido e separa o endereço do endereço atual do usuário.

Para fazer isso funcionar, você teria que inserir um novo endereço e vincular esse novo endereço à tabela Usuário. Isso significa que os endereços são gravados uma vez e a edição é uma ilusão para o usuário final. Você pode armazenar efetivamente o histórico de todos os endereços aos quais um usuário foi associado movendo a associação da tabela Usuário para uma tabela de associação com um carimbo de data / hora. Isso forneceria um histórico de edições / endereços e manteria dados imutáveis ​​na tabela Endereço.

@MDCCL declarou:

[você deve] organizar sua estrutura de banco de dados com uma tabela para reter dados relacionados ao pedido e outra tabela para manter as informações de endereço. E, sim, você pode definitivamente ter uma tabela que representa o relacionamento muitos para muitos entre esses dois tipos de entidade. Se um usuário pode alterar seus atributos de Endereço, você deve acompanhar essas modificações, portanto, você deve habilitar o correspondente AddressHistory. Este post está relacionado ao último aspecto.

O MDCCL também forneceu uma visão geral sobre como encontrar o endereço atual de um usuário aqui:

Para obter a versão mais recente de uma tabela Histórico, você deve levar em consideração MAX(AuditedDateTime)o correspondente AddressId. O primeiro passo é modelar / projetar seus melhores arranjos conceituais e lógicos possíveis, o segundo passo é encontrar as maneiras adequadas de INSERIR, ATUALIZAR, EXCLUIR e SELECIONAR seus dados.

Erik
fonte