Memorizzazione di una best practice per l'indirizzo di fatturazione nella tabella degli ordini


10

Qualcuno può aiutarmi a capire la risposta di questo utente per una tabella CustomerLocation . Voglio davvero un buon metodo per memorizzare gli indirizzi nella tabella degli ordini.

Quello che sto cercando è come posso impostare i miei indirizzi, quindi quando li modifico, l'ordine non viene effettuato dal fatto che un cliente aggiorna il suo indirizzo o si trasferisce.

Allo stato attuale il mio schema è simile a:

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

Risposte:


16

Concettualmente parlando, sebbene nel tuo ambiente aziendale Ordine e Indirizzo siano idee strettamente associate, sono in effetti due tipi di entità distinti, ognuno con il proprio set di proprietà (o attributi) e vincoli applicabili.

Pertanto, come precedentemente indicato nei commenti, sono d'accordo con @Erik e dovresti organizzare il layout logico del tuo database dichiarando tra gli altri elementi:

  • una tabella discreta per conservare le informazioni di Address ;
  • una tabella per conservare i dettagli specifici del cliente ;
  • una tabella per racchiudere i punti dati dell'Ordine ; e
  • una tabella per contenere i fatti relativi alle associazioni tra Cliente (i) e Indirizzo (i) ;

come esemplificerò di seguito.

Diagramma IDEF1X dell'esposizione

Un'immagine vale più di mille parole, quindi ho creato il diagramma IDEF1X mostrato nella Figura 1 per illustrare alcune delle possibilità aperte dal mio suggerimento:

Figura 1 - Diagramma IDEF1X dell'esposizione per clienti, ordini e indirizzi

Cliente , indirizzo e loro associazioni

Come dimostrato, ho rappresentato un'associazione con un rapporto di cardinalità molti-a-molti (M: N) tra i tipi di entità Cliente a e Indirizzo ; questo approccio fornirebbe flessibilità futura perché, come sapete, un cliente può mantenere più indirizzi nel tempo, o anche contemporaneamente, e lo stesso indirizzo può essere condiviso da più clienti .

Un particolare indirizzo può essere utilizzato in diversi modi con uno-a-molti (1: M) I clienti ; ad esempio, può essere definito come fisico e / o può essere impostato per la spedizione e / o per la fatturazione . Forse, la stessa istanza di Indirizzo può servire ciascuno dei suddetti scopi contemporaneamente, oppure può coprire due usi mentre una diversa occorrenza di Indirizzo copre quella rimanente.

a In alcuni ambienti aziendali, un cliente può essere una persona o un'organizzazione (situazione che implicherebbe un accordo leggermente distinto, come dettagliato in questa risposta su una struttura di sottotipo di sottotipo) ma con l'obiettivo di fornire un esempio semplificato, ho deciso non includere questa possibilità qui. Nel caso in cui sia necessario coprire tale situazione nel database, il post del collegamento precedente mostra il metodo per risolvere tale requisito.

Ordine , indirizzo , CustomerAddress e indirizzo ruoli

Comunemente, un ordine richiede solo due tipi di indirizzi , uno per la spedizione e uno per la fatturazione . In questo modo, la stessa istanza dell'indirizzo potrebbe riempire entrambi i ruoli per un singolo ordine , ma ogni ruolo è rappresentato dalla rispettiva proprietà, ovvero ShippingAddressId o BillingAddressId .

L'ordine è collegato con l' indirizzo tramite il tipo di entità associativa CustomerAddress con l'aiuto di due CHIAVI ESTERI multi-proprietà, ovvero

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

entrambi puntano al CHIAVE PRIMARIA multi-proprietà CustomerAddress mostrato come

  • ( CustomerNumber , AddressId )

... che aiuta a rappresentare una regola aziendale che stabilisce che (a) un'istanza dell'Ordine deve essere collegata esclusivamente con (b) le occorrenze dell'indirizzo precedentemente associate al Cliente specifico che ha effettuato tale Ordine e mai con (c) un non Cliente casuale - indirizzo correlato .

Cronologia per (1) indirizzo e per (2) l' associazione CustomerAddress

Se si vuole fornire la possibilità di modificare Indirizzo pezzi di informazioni, allora devi tenere traccia di tutte le modifiche dei dati. In questo modo ho descritto Address come un tipo di entità "controllabile" che mantiene la propria AddressHistory .

Poiché la natura di una connessione tra un cliente e un indirizzo può anche subire una o più modifiche, ho anche descritto la possibilità di gestire tale associazione come una "verificabile" in virtù del tipo di entità CustomerAddressHistory .

A tale proposito, vari fattori trattati nelle domande e risposte n. 1 e domande e risposte n. 2 , "entrambi sull'abilitazione delle capacità temporali in un database" sono veramente rilevanti.

Layout logico illustrativo SQL-DDL

Di conseguenza, in termini di diagramma visualizzato e spiegato sopra, ho dichiarato la seguente disposizione a livello logico (che puoi adattare per soddisfare le tue esigenze con precisione):

-- 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 vuoi dare un'occhiata, l'ho testato in questo violino db che funziona su SQL Server 2017.

I Historytavoli

Il seguente estratto dalla tua domanda è molto importante:

Quello che sto cercando è come posso impostare i miei indirizzi, quindi quando li modifico, l'ordine non è influenzato dal fatto che un cliente aggiorna il suo indirizzo o si trasferisce.

Le tabelle AddressHistorye CustomerAddressHistoryaiutano a garantire che un ordine non sia influenzato dalle modifiche dell'indirizzo , poiché tutte le righe "precedenti" devono essere conservate nella rispettiva Historytabella e possono essere interrogate quando necessario. Le operazioni di AGGIORNAMENTO e ELIMINAZIONE su queste due tabelle dovrebbero essere vietate (il tentativo di modificare la cronologia può anche avere implicazioni legali negative).

L' intervallo racchiuso tra i valori racchiusi AddressHistory.CreatedDateTimee AddressHistory.AuditedDateTimerappresenta l'intero Periodo durante il quale una determinata Addressriga "passata" è stata considerata "presente", "corrente" o "effettiva". Considerazioni simili si applicano alle CustomerAddressHistoryrighe.

La CustomerAddress.IsActivecolonna BIT (booleana) intende indicare se una determinata Addressriga è "utilizzabile" da una Customerriga o meno; ad esempio, se impostato su "falso", ciò significherebbe che un cliente non utilizza più tale indirizzo e quindi non può essere utilizzato per nuovi ordini .


Nota : D'altra parte, ho visto alcuni sistemi in cui ogni volta che un nuovo Ordine viene effettuato, l' indirizzo informazioni deve essere inserito (a volte più volte), e l' indirizzo (i) utilizzati per ultimi ordini non vengono mai cancellati (da qui gli ordini non sono interessati dalle modifiche dell'indirizzo ).

Questa linea di condotta può comportare decisamente notevoli volumi di ridondanza, ma è possibile che, in base agli esatti requisiti informativi del proprio dominio aziendale, possa funzionare, quindi è possibile valutare anche i suoi pro e contro.


Recupero dei dati

“Presente”, “di” o “efficace” versione di un indirizzo occorrenza deve essere contenuto come una riga nella Addresstabella ma selezionando il “stati” precedenti di un indirizzo DAL AddressHistory(o da CustomerAddressHistorytavolo) è facile e può essere un esercizio interessante per migliorare le tue abilità di codifica SQL.

Per quanto riguarda una delle situazioni menzionate nei commenti, se si desidera recuperare la "seconda all'ultima versione" di una singola Addressriga DA AddressHistory, è necessario prendere in considerazione la MAX(AddressHistory.AuditedDateTime)e la AddressHistory.AddressIdcorrispondente al Address.AddressIdvalore specifico a portata di mano.

A questo proposito, almeno quando si costruisce un database relazionale , è abbastanza conveniente prima definire lo schema concettuale corrispondente (basato sulle regole aziendali applicabili ) e successivamente dichiarare la sua successiva disposizione logica DDL. Una volta ottenute versioni stabili e affidabili di questi elementi fondamentali (che, ovviamente, possono evolversi nel tempo), è tempo di analizzare e determinare i modi migliori per manipolare (tramite le operazioni INSERT, UPDATE, DELETE e SELECT o le relative combinazioni) il per quanto riguarda i dati.

Percezione degli utenti finali, visualizzazioni e assistenza dei programmi applicativi

Evidentemente, al esterna livello di astrazione, Indirizzo informazione viene percepita (dagli utenti finali) come parte di un Ordine , e non c'è niente di sbagliato in questo, ma ciò non significa che i modellisti devono progettare le parti significative del database in questione come quello. Su questo punto, se non v'è la necessità di, ad esempio, stampare un “pieno” Order (molto fattibile), si può “riprodurre” on-demand con l'aiuto di alcuni operatori di join e le clausole WHERE (considerando il periodo di validità relativa , ecc.) potrebbe essere corretto nelle viste per il consumo futuro, inviando il set di risultati pertinente ai programmi applicativi correlati che, a loro volta, possono migliorare la sua formattazione, se necessario.

Naturalmente, anche i programmi applicativi saranno molto utili quando viene eseguito un Ordine ; ad esempio, una finestra dell'app desktop / mobile o una pagina Web può:

  • visualizzare solo gli indirizzi che il cliente interessato ha definito "utilizzabili" (tramite CustomerAddress.IsActive);
  • elencare tutti gli indirizzi che il cliente ha abilitato per il servizio di fatturazione (tramite CustomerAddress.IsBilling); e
  • raggruppare tutti gli Indirizzi che il Cliente ha definito per il servizio di spedizione (via CustomerAddress.IsShipping);

facilitando in questo modo tutti i processi coinvolti nella GUI (ovvero il livello esterno di astrazione di un sistema computerizzato).


Lettura consigliata

Hai richiesto (nei commenti ora rimossi) alcuni suggerimenti sulla documentazione del database audio; pertanto, per quanto riguarda il materiale teorico , consiglio vivamente di leggere tutto il lavoro scritto dal Dr. EF Codd , un destinatario del Turing Award e, naturalmente, l' unico ideatore del modello relazionale di dati (forse ora più pertinente che mai). Questo elenco include alcuni dei suoi articoli e articoli straordinariamente influenti.

Due lavori importanti che non sono inclusi nell'elenco di cui sopra sono precisamente la sua conferenza del Premio ACM Turing intitolata Relational Database: A Practical Foundation for Productivity , del 1981, e il suo libro intitolato The Relational Model for Database Management: Version 2 , che è stato pubblicato nel 1990.

Sul fronte del design concettuale , Integrated Definition for Information Modeling (IDEF1X) è una tecnica seriamente raccomandabile che è stata definita come standard nel dicembre 1993 dal National Institute of Standards and Technology (NIST) degli Stati Uniti .


1
Spiacenti, so che il post è più vecchio, ma perché fai riferimento (ad esempio: REFERENCES Address (AddressId)) in MyOrder? Perché non CustomerAddress?
Shadrix,

1
Nessun problema, e bella cattura: è un dato di fatto, entrambi MyOrder.ShippingAddressIde MyOrder.BillingAddressIddevono fare riferimento a CustomerAddress.AddressId(e non a Address.AddressId); in questo modo si garantisce che un Ordine possa essere associato esclusivamente agli Indirizzi precedentemente collegati al Cliente che ha effettuato tale Ordine . Il diagramma suggerisce questa disposizione, quindi il DDL sarà più preciso. Grazie per aver richiesto questo chiarimento.
MDCCL,

2
@Shadrix Ho appena modificato il post, nel caso tu voglia dare un'occhiata.
MDCCL,

@MDCCL Quando hai detto no UPDATE e DELETE sul Historytavolo, dovrebbe essere lo stesso per il Addresstavolo? Che cosa succede se il Cliente ordina qualcosa e poi cambia solo il codice postale o la città in un solo campo. Dovremmo inserire l'indirizzo esistente in Historye quindi creare un nuovo inserimento nella Addresstabella, giusto?
Mike Ross,

1
OTOH, se un cliente desidera modificare una o più informazioni su un determinato indirizzo , è necessario assicurarsi che (a) la Addressriga corrispondente che era "presente" fino a quando non è stata effettuata la modifica sia INSERITA nella AddressHistorytabella e anche che (b ) la Addressriga in questione è AGGIORNATA con i nuovi valori. Sarebbe vantaggioso eseguire questo processo come una singola unità di lavoro all'interno di una transazione.
MDCCL

3

Questa risposta è stata compilata dai commenti alla domanda.

Una soluzione sarebbe quella di utilizzare un FK nella tabella degli indirizzi nella tabella degli ordini. Ciò ti consentirà di visualizzare gli indirizzi utilizzati per l'ordine e di disaccoppiare l'indirizzo dall'indirizzo corrente dell'utente.

Per far funzionare tutto ciò, è necessario inserire un nuovo indirizzo e collegarlo alla tabella Utente. Ciò significa che gli indirizzi vengono scritti una volta e la modifica è un'illusione per l'utente finale. È possibile archiviare efficacemente la cronologia di tutti gli indirizzi a cui è stato associato un utente spostando l'associazione dalla tabella Utente a una tabella di associazione con un timestamp. Ciò ti darebbe una cronologia di modifiche / indirizzi e manterrebbe i dati immutabili nella tabella degli indirizzi.

@MDCCL ha dichiarato:

[dovresti] organizzare la struttura del tuo database con una tabella per conservare i dati relativi all'ordine e un'altra tabella per conservare le informazioni sull'indirizzo. E sì, puoi sicuramente avere una tabella che rappresenta la relazione molti-a-molti tra questi due tipi di entità. Se un utente può modificare i suoi attributi di indirizzo, è necessario tenere traccia di tali modifiche, quindi è necessario abilitare il corrispondente AddressHistory. Questo post è legato a quest'ultimo aspetto.

MDCCL ha anche fornito una panoramica su come trovare l'indirizzo corrente per un utente qui:

Per ottenere l'ultima versione di una tabella Cronologia che hai, devi prendere in considerazione MAX(AuditedDateTime)la corrispondente AddressId. Il primo passo è modellare / progettare le migliori disposizioni concettuali e logiche possibili, il secondo passo è trovare i modi corretti per INSERIRE, AGGIORNARE, ELIMINARE e SELEZIONARE i dati.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.