Сохранение оптимальной практики адреса выставления счетов в таблице заказов

10

Может ли кто-нибудь помочь мне понять ответ этого пользователя для таблицы CustomerLocation . Мне очень нужен хороший способ хранения адресов в таблице заказов.

То, что я ищу, - это то, как я могу настроить свои адреса, поэтому, когда я их редактирую, заказ не зависит от того, что клиент обновляет свой адрес или перемещает.

Моя схема выглядит примерно так:

 Person           |EntityID|
 EntityAddress    |EntityID|AddressID|
 Address          |AddressID|AddressType|AddressLine1|AddressLine2|
 Order            |OrderID|BillingAddressID|
Сообщество
источник

Ответы:

16

Концептуально говоря, хотя в вашей бизнес-среде « Порядок» и « Адрес» являются тесно связанными идеями, в действительности они представляют собой два отдельных типа сущностей, каждый из которых имеет свой собственный набор применимых свойств (или атрибутов) и ограничений.

Поэтому, как уже говорилось в комментариях, я согласен с @Erik , и вы должны организовать логическую структуру вашей базы данных, объявляя среди других элементов:

  • одна дискретная таблица для хранения информации об элементах адреса ;
  • одна таблица для хранения специфичных для клиента данных;
  • одна таблица для размещения точек данных заказа ; и
  • одна таблица, содержащая факты об отношениях между Клиентом (-ами) и Адресом (-ами ) ;

как я приведу в качестве примера ниже.

Описательная схема IDEF1X

Изображение стоит тысячи слов, поэтому я создал диаграмму IDEF1X, показанную на рисунке 1, чтобы проиллюстрировать некоторые возможности, открываемые моим предложением:

Рисунок 1 - Диаграмма IDEF1X для клиентов, заказов и адресов

Заказчик , адрес и их ассоциации

Как продемонстрировано, я изобразил связь с отношением ко многим (M: N) кардинальности между типами сущностей Customer a и Address ; этот подход обеспечит гибкость в будущем, поскольку, как вы знаете, клиент может хранить несколько адресов во времени или даже одновременно, и один и тот же адрес может совместно использоваться несколькими клиентами .

Конкретный адрес может быть использован несколькими способами одним клиентом (1: M) ; например, он может быть определен как физический , и / или он может быть установлен для доставки и / или для выставления счетов . Возможно, один и тот же экземпляр адреса может одновременно служить каждой из вышеупомянутых целей, или он может охватывать два варианта использования, в то время как другой экземпляр адреса охватывает оставшееся.

a В некоторых бизнес-средах заказчиком может быть либофизическое лицо, либо организация (ситуация, которая подразумевает несколько отличную договоренность, как подробно описано в этом ответе о структуре подтипа супертипа), но с целью предоставления упрощенного примера я решил не включать эту возможность здесь. В случае, если вам нужно охватить эту ситуацию в вашей базе данных, пост предыдущей ссылки показывает метод решения указанного требования.

Заказ , адрес , CustomerAddress и адреса Роли

Обычно для заказа требуются только два вида адресов : один для доставки и один для выставления счетов . Таким образом, один и тот же экземпляр Address может заполнить обе роли для отдельного заказа , но каждая роль отображается соответствующим свойством, то есть ShippingAddressId или BillingAddressId .

Порядок связан с Address через тип ассоциативного объекта CustomerAddress с помощью двух многофункциональных FOREIGN KEY, т.е.

  • ( CustomerNumber , ShippingAddressId ) и ( CustomerNumber , BillingAddressId ),

оба указывают на многозначный PRIMARY KEY CustomerAddress, показанный как

  • ( CustomerNumber , AddressId )

... который помогает представить бизнес - правило , которое гласит , что (а) заказ экземпляр должно быть связанно исключительно с (б) адрес вхождением ранее связанные с конкретным заказчиком , который сделал что заказ , и никогда с (с) случайным не- Заказчиком - связанный адрес .

История для (1) адреса и (2) ассоциации CustomerAddress

Если вы хотите предоставить возможность изменения адресов информации, то вы должны отслеживать все изменения данных. Таким образом, я изобразил Address как «проверяемый» тип сущности, который поддерживает свою собственную AddressHistory .

Поскольку характер связи между Заказчиком и Адресом также может подвергаться одной или нескольким модификациям, я также изобразил возможность обработки такой ассоциации как «проверяемой» с помощью типа сущности CustomerAddressHistory .

В этом отношении различные факторы рассматриваются в Q & A нет. 1 и Q & A нет. 2 , - о включении временных возможностей в базе данных - действительно актуальны.

Иллюстративная логическая схема SQL-DDL

Следовательно, с точки зрения диаграммы, показанной и объясненной выше, я объявил следующую схему логического уровня (которую вы можете точно адаптировать для удовлетворения своих потребностей):

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

Если вы хотите посмотреть, я протестировал его в этой скрипте db <>, которая работает на SQL Server 2017.

В Historyтаблицах

Следующая выдержка из вашего вопроса очень важна:

То, что я ищу, - это то, как я могу настроить свои адреса, чтобы при их редактировании на заказ не влиял тот факт, что клиент обновляет свой адрес или перемещает его.

AddressHistoryИ CustomerAddressHistoryстолы помощи в обеспечении , что заказ не зависят от адресных изменений, так как все «предыдущие» строки должны быть сохранены в соответствующей Historyтаблице и могут быть запрошены при необходимости. Операции UPDATE и DELETE в этих двух таблицах должны быть запрещены (попытки изменить историю могут даже иметь негативные юридические последствия).

Интервал охватывает между значениями , вложенных в AddressHistory.CreatedDateTimeи AddressHistory.AuditedDateTimeвыступает за весь период , в течение которого некоторая «прошлого» Addressстрока была сочтена «присутствует», «текущий» или «эффективный». Аналогичные соображения применимы к CustomerAddressHistoryстрокам.

CustomerAddress.IsActiveСтолбец БИТ (булев) предназначаются , чтобы указать, является ли некоторая Addressявляется «полезной» с помощью строки Customerстроки или нет; например, если для него установлено значение «false», это будет означать, что Клиент больше не использует этот адрес и, следовательно, его нельзя использовать для новых заказов .


Примечание : С другой стороны, я видел некоторые системы , в которых каждый раз, когда новый заказ будет осуществляться в адресной информации необходимо ввести ( в несколько раз повторно), и адрес (адрес) , используемые для последних Заказов никогда не стертые (отсюда что заказы не зависит от адресных изменений).

Этот курс действий может включать значительные объемы избыточности, но есть вероятность, что - в зависимости от точных информационных требований вашей бизнес-сферы - может сработать, поэтому вам также может понравиться оценить ее плюсы и минусы.


Поиск данных

«Присутствует», «текущая» или «эффективная» версия Адреса возникновения должна содержаться в виде строки в Addressтаблице, но выбор предыдущих «состояний» в адрес ОТ AddressHistory(или из CustomerAddressHistoryтаблицы) легко, и это может быть интересным упражнением для улучшения ваших навыков кодирования SQL.

Что касается одной из ситуаций, которые вы упомянули в комментариях, если вы хотите получить «от второй до последней версии» отдельной Addressстроки из ее AddressHistory, вы должны принять во внимание MAX(AddressHistory.AuditedDateTime)то, AddressHistory.AddressIdчто соответствует конкретному Address.AddressIdзначению под рукой.

В этом отношении - по крайней мере, при создании реляционной базы данных - довольно удобно сначала определить соответствующую концептуальную схему (на основе применимых бизнес-правил ), а затем объявить ее последующее логическое расположение DDL. Как только вы получите стабильные и надежные версии этих основных элементов (которые, конечно, могут развиваться с течением времени), настало время проанализировать и определить лучшие способы манипуляции (с помощью операций INSERT, UPDATE, DELETE и SELECT или их комбинаций) относительно данных.

Восприятие конечных пользователей, взгляды и помощь прикладных программ

Очевидно, что на внешнем уровне абстракции адресная информация воспринимается (конечными пользователями) как часть Ордена , и в этом нет ничего плохого, но это не означает, что разработчики моделей должны проектировать значимые части Ордена. база данных в вопросе, как это. На этом этапе, если есть необходимость, например, напечатать «полный» Заказ (очень выполнимо), вы можете «воспроизвести» его по требованию с помощью нескольких операторов JOIN и предложений WHERE (с учетом соответствующего периода действия и т. д.) может быть зафиксировано в представлениях для будущего потребления, посылая соответствующий набор результатов в соответствующие прикладные программы, которые, в свою очередь, могут при необходимости улучшить его форматирование.

Конечно, прикладная программа (-ы) также будет очень полезна, когда исполняется Орден ; например, окно настольного / мобильного приложения или веб-страница могут:

  • отображать только тот адрес (а), который вовлеченный клиент определил как «используемый» (через CustomerAddress.IsActive);
  • составить список всех адресов, которые Клиент включил для выставления счетов (через CustomerAddress.IsBilling); и
  • сгруппировать все адреса, которые Клиент определил для службы доставки (через CustomerAddress.IsShipping);

облегчая таким образом все вовлеченные процессы в GUI (то есть внешний уровень абстракции компьютеризированной системы).


Предлагаемое чтение

Вы запросили (в уже удаленных комментариях) несколько указателей о звуковой литературе по базам данных; Поэтому, как и для теоретического материала, я настоятельно советую вам прочитать всю работу , написанную доктором Е. Ф. Кодда , в премии Тьюринга получателя и, конечно же , единственным виновником в реляционной модели данных (возможно , сейчас более актуальна , чем когда - либо). Этот список включает некоторые из его чрезвычайно влиятельных статей и статей.

Двумя важными работами, которые не включены в вышеупомянутый список, являются, в частности, его лекция ACM Turing Award, озаглавленная « Реляционная база данных: практическая основа для повышения производительности» от 1981 года, и его книга под названием «Реляционная модель для управления базами данных: версия 2» , которая была опубликована в 1990 году.

На переднем плане концептуального проектирования Интегрированное определение для информационного моделирования (IDEF1X) является серьезно рекомендуемой техникой, которая была определена в качестве стандарта в декабре 1993 года Национальным институтом стандартов и технологий США (NIST).

MDCCL
источник
1
Извините, я знаю, что пост старше, но почему вы ссылаетесь (например, REFERENCES Address (AddressId)) в MyOrder? Почему не CustomerAddress?
Шадрикс
1
Не беспокойтесь, и хороший улов: На самом деле, оба MyOrder.ShippingAddressIdи MyOrder.BillingAddressIdдолжны ссылаться CustomerAddress.AddressId(а не на Address.AddressId); таким образом гарантируется, что Заказ может быть связан исключительно с адресом (ами), ранее связанным с Клиентом, который сделал этот Заказ . Диаграмма предполагает такое расположение, поэтому DDL будет более точным. Спасибо за запрос об этом разъяснении.
MDCCL
2
@Shadrix Я только что отредактировал пост, если хочешь посмотреть.
MDCCL
@MDCCL Когда вы сказали нет UPDATE и DELETE для Historyтаблицы, должно ли быть то же самое для Addressтаблицы? Что делать, если Клиент заказывает что-то, а затем меняет только почтовый индекс или город только на одно поле. Мы должны вставить существующий адрес в Historyи затем сделать новую вставку в Addressтаблицу, верно?
Майк Росс
1
OTOH, если Клиент хочет изменить одну или несколько частей информации о данном адресе , необходимо убедиться, что (a) соответствующая Addressстрока, которая «присутствовала» до того, как произошло изменение, вставлена ​​в AddressHistoryтаблицу, а также (b) ) рассматриваемая Addressстрока ОБНОВЛЕНА новыми значениями. Было бы выгодно выполнять этот процесс как единую единицу работы внутри транзакции.
MDCCL
3

Этот ответ был составлен из комментариев к вопросу.

Одним из решений будет использование FK для таблицы адресов в таблице заказов. Это позволит вам увидеть адреса, которые использовались для заказа, и отделить адрес от текущего адреса пользователя.

Чтобы сделать это, вам нужно будет вставить новый адрес и связать этот новый адрес с таблицей пользователей. Это означает, что адреса пишутся один раз, и редактирование является иллюзией для конечного пользователя. Вы можете эффективно хранить историю всех адресов, с которыми был связан пользователь, путем перемещения ассоциации из таблицы User в таблицу ассоциации с отметкой времени. Это даст вам историю изменений / адресов и сохранит неизменные данные в таблице адресов.

@MDCCL заявил:

[Вы должны] организовать свою структуру базы данных, имеющую одну таблицу для хранения данных, связанных с Заказом, и другую таблицу для хранения информации об адресе. И да, у вас определенно может быть таблица, представляющая отношение «многие ко многим» между этими двумя типами сущностей. Если пользователь может изменить свои атрибуты адреса (адресов), то вы должны отслеживать такие изменения, поэтому вы должны включить соответствующие AddressHistory. Этот пост связан с последним аспектом.

MDCCL также дал обзор о том, как найти текущий адрес для пользователя здесь:

Для того чтобы получить последнюю версию имеющейся у вас истории, вы должны принять во внимание MAX(AuditedDateTime)соответствующую версию AddressId. Первый шаг - это моделирование / разработка ваших наилучших концептуальных и логических схем, а второй - поиск правильных способов ВСТАВИТЬ, ОБНОВИТЬ, УДАЛИТЬ и ВЫБРАТЬ ваши данные.

Erik
источник