Przechowywanie adresu rozliczeniowego - najlepsza praktyka w tabeli zamówień

10

Czy ktoś może mi pomóc zrozumieć odpowiedź tego użytkownika dla tabeli CustomerLocation . Naprawdę chcę dobrej metody przechowywania adresów w tabeli zamówień.

Szukam tego, jak mogę skonfigurować moje adresy, więc kiedy je edytuję, zamówienie nie jest realizowane przez fakt, że klient aktualizuje swój adres lub przenosi się.

W obecnej formie mój schemat wygląda podobnie do:

 Person           |EntityID|
 EntityAddress    |EntityID|AddressID|
 Address          |AddressID|AddressType|AddressLine1|AddressLine2|
 Order            |OrderID|BillingAddressID|
Społeczność
źródło

Odpowiedzi:

16

Mówiąc koncepcyjnie, chociaż w środowisku biznesowym Zamówienie i Adres są pomysłami ściśle ze sobą powiązanymi, w rzeczywistości są to dwa odrębne typy jednostek, każdy z własnym zestawem odpowiednich właściwości (lub atrybutów) i ograniczeń.

Dlatego, jak wcześniej wspomniano w komentarzach, zgadzam się z @Erik i powinieneś zorganizować logiczny układ swojej bazy danych, deklarując między innymi:

  • jedna dyskretna tabela do przechowywania informacji o adresie ;
  • jedna tabela do przechowywania danych specyficznych dla klienta ;
  • jedna tabela do załączenia punktów danych zamówienia ; i
  • jedna tabela zawierająca fakty dotyczące powiązań między klientem (klientami) a adresem (adresami) ;

jak zilustruję poniżej.

Schemat IDEF1X dla ekspozytora

Obraz jest wart tysiąca słów, więc stworzyłem schemat IDEF1X pokazany na rycinie 1, aby zilustrować niektóre możliwości, które otworzyły moje sugestie:

Rysunek 1 - Schemat IDEF1X dla klientów, zamówień i adresów

Klient , adres i ich powiązania

Jak wykazano, przedstawiłem związek ze współczynnikiem liczności wiele do wielu (M: N) między typami jednostek Klient a i Adres ; takie podejście zapewniłoby elastyczność w przyszłości, ponieważ, jak wiadomo, Klient może przechowywać wiele Adresów w czasie lub nawet jednocześnie, a ten sam Adres może być współdzielony przez wielu Klientów .

Szczególnym Adres może być stosowany na wiele sposobów przez jeden-do-wielu (1: M) Klientów ; np. można go zdefiniować jako fizyczny i / lub można ustawić na wysyłkę i / lub na fakturowanie . Być może ta sama instancja adresu może służyć jednocześnie do każdego z wyżej wymienionych celów lub może obejmować dwa zastosowania, podczas gdy inne wystąpienie adresu obejmuje pozostałe.

a W niektórych środowiskach biznesowych Klientem może być Osoba lub Organizacja (sytuacja, która sugerowałaby nieco odmienne ustalenia, jak szczegółowo opisano w tej odpowiedzi na temat struktury podtypu), ale w celu zapewnienia uproszczonego przykładu, zdecydowałem nie uwzględniać tej możliwości tutaj. W przypadku konieczności uwzględnienia tej sytuacji w bazie danych, post poprzedniego linku pokazuje metodę rozwiązania wspomnianego wymagania.

Zamówienie , adresowe , AdresKlienta i adresowe Role

Zwykle Zamówienie wymaga tylko dwóch rodzajów adresów , jednego do wysyłki i jednego do fakturowania . W ten sposób ta sama instancja adresu może wypełnić obie role dla pojedynczego zamówienia , ale każdą rolę obrazuje odpowiednia właściwość, tj. ShippingAddressId lub BillingAddressId .

Zamówienie jest powiązane z adresem za pośrednictwem typu podmiotu stowarzyszonego CustomerAddress za pomocą dwóch KLUCZOWYCH KLUCZÓW OBCYCH, tj.

  • ( CustomerNumber , ShippingAddressId ) i ( CustomerNumber , BillingAddressId ),

oba wskazują na KLUCZ PODSTAWOWY wielu właściwości CustomerAddress pokazany jako

  • ( CustomerNumber , AddressId )

… Co pomaga reprezentować regułę biznesową, która stanowi, że (a) instancja Zamówienia musi być powiązana wyłącznie z (b) Adresować zdarzenia wcześniej powiązane z konkretnym Klientem, który złożył to Zamówienie , a nigdy z (c) przypadkowym podmiotem niebędącym Klientem - powiązany adres .

Historia dla (1) adresu i (2) stowarzyszenia CustomerAddress

Jeśli chcesz zapewnić możliwość modyfikowania informacji o adresie , musisz śledzić wszystkie zmiany danych. W ten sposób przedstawiłem Adres jako „encję podlegającą kontroli ”, która utrzymuje swoją własną AddressHistory .

Ponieważ charakter połączenia między klientem a adresem może również podlegać jednej lub większej liczbie modyfikacji, przedstawiłem również możliwość obsługi takiego powiązania jako „podlegającego kontroli” na podstawie typu jednostki CustomerAddressHistory .

W związku z tym różne czynniki omówione w pytaniach i odpowiedziach nr. 1 oraz pytania i odpowiedzi nr. 2 - zarówno o włączeniu możliwości czasowych w bazie danych - są naprawdę istotne.

Ilustracyjny układ logiczny SQL-DDL

W związku z tym pod względem diagramu wyświetlonego i objaśnionego powyżej zadeklarowałem następujący układ na poziomie logicznym (który możesz dokładnie dostosować do swoich potrzeb):

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

Jeśli chcesz rzucić okiem, przetestowałem to w tym skrzynce db <>, która działa na SQL Server 2017.

te Historytabele

Poniższy fragment twojego pytania jest bardzo ważny:

Szukam tego, jak mogę skonfigurować moje adresy, więc kiedy je edytuję, na porządek nie ma wpływu fakt, że klient aktualizuje swój adres lub przenosi się.

AddressHistoryI CustomerAddressHistorytabele pomoc w zapewnieniu, że zamówienie nie wpływa Adres zmian, jak wszystkie „poprzedni” Wiersze powinny być przechowywane w odpowiedniej Historytabeli i może być sprawdzony, gdy jest to konieczne. Operacje UPDATE i DELETE na tych dwóch tabelach powinny być zabronione (próby zmiany historii mogą mieć nawet negatywne konsekwencje prawne).

Odstęp obejmowały między wartościami w zamkniętych AddressHistory.CreatedDateTimei AddressHistory.AuditedDateTimestoi na cały okres , w trakcie którego pewne „obok” Addressrząd został uznany za „obecny”, „obecny” lub „skuteczny”. Podobne uwagi dotyczą CustomerAddressHistorywierszy.

CustomerAddress.IsActive(Logiczna) Kolumna BIT ma wskazać, czy dany Addressrząd jest „użytkowej” przez Customerrzędzie czy też nie; np. jeśli jest ustawione na „fałsz”, oznacza to, że klient nie korzysta już z tego adresu, a zatem nie można go wykorzystać do nowych zamówień .


Uwaga : Z drugiej strony widziałem niektóre systemy, w których za każdym razem, gdy realizowane jest nowe zamówienie, należy wprowadzić informacje o adresie (kilka razy wielokrotnie), a adresy używane w poprzednich zamówieniach nigdy nie są usuwane (stąd zmiany adresu nie mają wpływu na zamówienia ).

Taki sposób postępowania może zdecydowanie wymagać znacznej liczby redundancji, ale istnieje możliwość, że - w zależności od dokładnych wymagań informacyjnych Twojej domeny biznesowej - może działać, więc możesz również ocenić jego zalety i wady.


Odzyskiwanie danych

W „obecny”, „obecny” lub „skuteczny” w wersji o Adres wystąpienia musi być zawarty jako wiersz w Addresstabeli, ale wybierając wcześniejsze „państw” wydane Adres Z AddressHistory(lub z CustomerAddressHistory) stół jest łatwe, a to może być ciekawym ćwiczeniem, które poprawi Twoje umiejętności kodowania SQL.

W odniesieniu do jednej z sytuacji, o których wspomniałeś w komentarzach, jeśli chcesz odzyskać „od drugiej do ostatniej wersji” indywidualnego Addresswiersza z niego AddressHistory, musisz wziąć pod uwagę MAX(AddressHistory.AuditedDateTime)i, AddressHistory.AddressIdktóry odpowiada konkretnej dostępnej Address.AddressIdwartości.

W związku z tym - przynajmniej podczas budowania relacyjnej bazy danych - całkiem wygodne jest najpierw zdefiniowanie odpowiedniego schematu pojęciowego (w oparciu o obowiązujące reguły biznesowe ), a następnie zadeklarowanie jego późniejszego logicznego układu DDL. Po uzyskaniu stabilnych i niezawodnych wersji tych podstawowych elementów (które oczywiście mogą ewoluować w miarę upływu czasu) nadszedł czas, aby przeanalizować i określić najlepsze sposoby manipulowania (za pomocą operacji INSERT, UPDATE, DELETE i SELECT lub ich kombinacji) dotyczące danych.

Postrzeganie przez użytkowników końcowych, widoki i pomoc dla aplikacji

Oczywiście na zewnętrznym poziomie abstrakcji informacje adresowe są postrzegane (przez użytkowników końcowych) jako część Zakonu i nie ma w tym nic złego, ale to nie znaczy, że projektanci muszą zaprojektować znaczące części taka baza danych. W tym punkcie, jeśli istnieje potrzeba np. Wydrukowania „pełnego” zamówienia (bardzo wykonalne), można „odtworzyć” je na żądanie przy pomocy kilku operatorów JOIN i klauzul WHERE (biorąc pod uwagę odpowiedni okres ważności) itp.) może zostać naprawiony w widokach do wykorzystania w przyszłości, wysyłając odpowiedni zestaw wyników do powiązanych programów aplikacji, które z kolei mogą ulepszyć jego formatowanie w razie potrzeby.

Oczywiście programy aplikacyjne będą również bardzo pomocne podczas realizacji Zamówienia ; np. okno aplikacji komputerowej / mobilnej lub strona internetowa mogą:

  • wyświetlać tylko adresy, które dany Klient określił jako „użyteczne” (via CustomerAddress.IsActive);
  • wymień razem wszystkie adresy, które Klient włączył do usługi fakturowania (via CustomerAddress.IsBilling); i
  • grupa wszystkie adresy że klient ma określone dla wysyłki usługa (przez CustomerAddress.IsShipping);

ułatwiając w ten sposób wszystkie zaangażowane procesy w GUI (tj. zewnętrzny poziom abstrakcji systemu komputerowego).


Sugerowane czytanie

Zażądałeś (w teraz usuniętych komentarzy) pewnych wskazówek na temat literatury w solidnej bazie danych; Dlatego, podobnie jak dla teoretycznego materiału, gorąco zalecamy, aby przeczytać całą pracę napisany przez dr EF Codd , a Nagroda Turinga odbiorcy i, oczywiście, jedyny inicjator tego modelu relacyjnego danych (Może teraz bardziej niż kiedykolwiek). Ta lista zawiera niektóre z jego niezwykle wpływowych artykułów i artykułów.

Dwa ważne dzieła, których nie ma na wyżej wymienionej liście, to właśnie jego wykład ACM Turinga zatytułowany Relacyjna baza danych: Praktyczna podstawa produktywności z 1981 r. Oraz jego książka o nazwie Relacyjny model zarządzania bazami danych: wersja 2 , która została opublikowana w 1990.

W zakresie projektowania koncepcyjnego zintegrowana definicja modelowania informacji (IDEF1X) jest bardzo godną polecenia techniką, która została zdefiniowana jako standard w grudniu 1993 r. Przez amerykański Narodowy Instytut Norm i Technologii (NIST).

MDCCL
źródło
1
Przykro mi, wiem, że post jest starszy, ale dlaczego odwołujesz się do niego (na przykład: REFERENCES Address (AddressId)) w MyOrder? Dlaczego nie CustomerAddress?
Shadrix,
1
Nie ma obaw, a miła haczyk: jak w rzeczywistości, zarówno MyOrder.ShippingAddressIdi MyOrder.BillingAddressIdnależy dokonać odniesienia do CustomerAddress.AddressId(nie do Address.AddressId); w ten sposób zapewnia się, że Zamówienie może być powiązane wyłącznie z Adresami poprzednio powiązanymi z Klientem, który dokonał tego Zamówienia . Schemat sugeruje takie ustawienie, więc DDL będzie dokładniejszy. Dziękujemy za prośbę o wyjaśnienie.
MDCCL,
2
@Shadrix Właśnie edytowałem post, na wypadek gdybyś chciał rzucić okiem.
MDCCL
@MDCCL Kiedy powiedziałeś, że nie ma UPDATE i DELETE w Historytabeli, czy powinno być tak samo w przypadku Addresstabeli? Co jeśli Klient coś zamówi, a następnie zmieni tylko kod pocztowy lub miasto tylko jedno pole. Powinniśmy wstawić istniejący adres, Historya następnie zrobić nową wstawkę do Addresstabeli, prawda?
Mike Ross
1
OTOH, jeśli klient chce zmienić jedną lub więcej części informacji na temat danego adresu , należy upewnić się, że (a) odpowiedni Addresswiersz, który był „obecny”, aż modyfikacja wziął miejsce jest włożona do AddressHistorystołu, a także, że (b ) Addresswiersz, o którym mowa, jest UPDATEd o nowe wartości. Korzystne byłoby przeprowadzenie tego procesu jako pojedynczej jednostki pracy w ramach transakcji.
MDCCL,
3

Ta odpowiedź została opracowana na podstawie komentarzy do pytania.

Jednym rozwiązaniem byłoby użycie FK do tabeli adresów w tabeli zamówień. Umożliwi to wyświetlenie adresów użytych do zamówienia i odsprzęgnie adres od aktualnego adresu użytkownika.

Aby to zadziałało, musisz wstawić nowy adres i połączyć ten nowy adres z tabelą użytkowników. Oznacza to, że adresy są zapisywane raz, a edycja jest złudzeniem dla użytkownika końcowego. Możesz skutecznie przechowywać historię wszystkich adresów, z którymi użytkownik był powiązany, przenosząc powiązanie z tabeli użytkowników do tabeli powiązań ze znacznikiem czasu. To da ci historię edycji / adresów i utrzyma niezmienne dane w tabeli adresów.

@MDCCL stwierdził:

[powinieneś] zorganizować strukturę bazy danych z jedną tabelą do przechowywania danych związanych z zamówieniem, a drugą tabelą do przechowywania informacji o adresie. I tak, zdecydowanie możesz mieć tabelę reprezentującą relację wiele do wielu między tymi dwoma typami bytów. Jeśli użytkownik może zmienić swoje atrybuty adresu (adresów), musisz śledzić takie modyfikacje, dlatego powinieneś włączyć odpowiednie AddressHistory. Ten post dotyczy drugiego aspektu.

MDCCL przedstawił także tutaj przegląd tego, jak znaleźć aktualny adres użytkownika:

Aby pobrać najnowszą wersję tabeli historii, którą masz, musisz wziąć pod uwagę MAX(AuditedDateTime)odpowiednią AddressId. Pierwszym krokiem jest modelowanie / projektowanie najlepszych możliwych rozwiązań koncepcyjnych i logicznych, drugim krokiem jest znalezienie odpowiednich sposobów WSTAWIANIA, AKTUALIZACJI, USUWANIA i WYBORU danych.

Erik
źródło