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:
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 History
tabele
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ę.
AddressHistory
I CustomerAddressHistory
tabele pomoc w zapewnieniu, że zamówienie nie wpływa Adres zmian, jak wszystkie „poprzedni” Wiersze powinny być przechowywane w odpowiedniej History
tabeli 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.CreatedDateTime
i AddressHistory.AuditedDateTime
stoi na cały okres , w trakcie którego pewne „obok” Address
rząd został uznany za „obecny”, „obecny” lub „skuteczny”. Podobne uwagi dotyczą CustomerAddressHistory
wierszy.
CustomerAddress.IsActive
(Logiczna) Kolumna BIT ma wskazać, czy dany Address
rząd jest „użytkowej” przez Customer
rzę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 Address
tabeli, 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 Address
wiersza z niego AddressHistory
, musisz wziąć pod uwagę MAX(AddressHistory.AuditedDateTime)
i, AddressHistory.AddressId
który odpowiada konkretnej dostępnej Address.AddressId
wartoś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).
MyOrder.ShippingAddressId
iMyOrder.BillingAddressId
należy dokonać odniesienia doCustomerAddress.AddressId
(nie doAddress.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.History
tabeli, czy powinno być tak samo w przypadkuAddress
tabeli? 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,History
a następnie zrobić nową wstawkę doAddress
tabeli, prawda?Address
wiersz, który był „obecny”, aż modyfikacja wziął miejsce jest włożona doAddressHistory
stołu, a także, że (b )Address
wiersz, o którym mowa, jest UPDATEd o nowe wartości. Korzystne byłoby przeprowadzenie tego procesu jako pojedynczej jednostki pracy w ramach transakcji.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ł:
MDCCL przedstawił także tutaj przegląd tego, jak znaleźć aktualny adres użytkownika:
źródło