Подтвердить что ты не робот

Лучшая практика проектирования баз данных SQL (адреса)

Конечно, я понимаю, что нет единого "правильного" способа проектирования базы данных SQL, но я хотел бы получить некоторые мнения о том, что лучше или хуже в моем конкретном сценарии.

В настоящее время я разрабатываю модуль ввода заказов (приложение Windows.NET 4.0 с SQL Server 2008), и я разрываюсь между двумя проектными решениями, когда речь заходит о данных, которые можно применять более чем в одном месте. В этом вопросе я буду ссылаться конкретно на адреса.

Адреса могут использоваться различными объектами (заказами, клиентами, сотрудниками, отправками и т.д.), И они почти всегда содержат одни и те же данные (адрес1/2/3, город, штат, почтовый индекс, страна и т.д.). Первоначально я собирался включить каждое из этих полей в виде столбца в каждую из связанных таблиц (например, Заказы будут содержать Адрес1/2/3, Город, Штат и т.д., А Клиенты также будут содержать этот же макет столбца). Но часть меня хочет применить принципы СУХОЙ/Нормализации к этому сценарию, то есть иметь таблицу под названием "Адреса", на которую ссылается внешний ключ в соответствующей таблице.

CREATE TABLE DB.dbo.Addresses
    (
        Id          INT
                    NOT NULL
                    IDENTITY(1, 1)
                    PRIMARY KEY
                    CHECK (Id > 0),

        Address1    VARCHAR(120)
                                NOT NULL,

        Address2    VARCHAR(120),

        Address3    VARCHAR(120),

        City        VARCHAR(100)
                    NOT NULL,

        State       CHAR(2)
                    NOT NULL,

        Country     CHAR(2)
                    NOT NULL,

        PostalCode  VARCHAR(16)
                    NOT NULL
    )

CREATE TABLE DB.dbo.Orders
    (
        Id          INT
                    NOT NULL
                    IDENTITY(1000, 1)
                    PRIMARY KEY
                    CHECK (Id > 1000),

        Address     INT
                    CONSTRAINT fk_Orders_Address
                    FOREIGN KEY REFERENCES Addresses(Id)
                    CHECK (Address > 0)
                    NOT NULL,

        -- other columns....
    )

CREATE TABLE DB.dbo.Customers
    (
        Id          INT
                    NOT NULL
                    IDENTITY(1000, 1)
                    PRIMARY KEY
                    CHECK (Id > 1000),

        Address     INT
                    CONSTRAINT fk_Customers_Address
                    FOREIGN KEY REFERENCES Addresses(Id)
                    CHECK (Address > 0)
                    NOT NULL,

        -- other columns....
    )

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

Я думаю, мне просто интересно, если какие-либо разработчики SQL уровня предприятия когда-либо успешно использовали этот подход, или количество создаваемых при этом JOIN-соединений создавало бы проблему с производительностью?

4b9b3361

Ответ 1

Вы на правильном пути, разбив адрес на свою собственную таблицу. Я бы добавил несколько дополнительных предложений.

  • Рассмотрите возможность использования столбцов Address FK из таблиц Customers/Orders и создания таблиц соединений. Другими словами, относитесь к Клиентам/Адресам и Ордерам/Адресам как к отношениям "многие ко многим" в вашем дизайне, поэтому вы можете легко поддерживать несколько адресов в будущем. Да, это означает введение большего количества таблиц и объединений, но гибкость, которую вы получаете, стоит усилий.

  • Рассмотрите возможность создания таблиц поиска для городских, государственных и сельских организаций. Столбцы city/state/country таблицы адресов затем состоят из FK, указывающих на эти таблицы поиска. Это позволяет гарантировать согласованное написание по всем адресам и дает вам место для хранения дополнительных метаданных (например, популяции города), если это необходимо в будущем.

Ответ 2

У меня есть некоторые предостережения. Для каждого из них существует более чем один способ устранить проблему.

Во-первых, нормализация не означает "заменить текст номером id".

Во-вторых, у вас нет ключа. Я знаю, у вас есть столбец, объявленный "PRIMARY KEY", но этого недостаточно.

insert into Addresses 
  (Address1, Address2, Address3, City, State, Country, PostalCode)
values
  ('President Obama', '1600 Pennsylvania Avenue NW', NULL, 'Washington', 'DC', 'US', '20500'),
  ('President Obama', '1600 Pennsylvania Avenue NW', NULL, 'Washington', 'DC', 'US', '20500'),
  ('President Obama', '1600 Pennsylvania Avenue NW', NULL, 'Washington', 'DC', 'US', '20500'),
  ('President Obama', '1600 Pennsylvania Avenue NW', NULL, 'Washington', 'DC', 'US', '20500');

select * from Addresses;

1;President Obama;1600 Pennsylvania Avenue NW;;Washington;DC;US;20500
2;President Obama;1600 Pennsylvania Avenue NW;;Washington;DC;US;20500
3;President Obama;1600 Pennsylvania Avenue NW;;Washington;DC;US;20500
4;President Obama;1600 Pennsylvania Avenue NW;;Washington;DC;US;20500

При отсутствии каких-либо других ограничений ваш "первичный ключ" идентифицирует строку; он не идентифицирует адрес. Идентификация строки обычно недостаточно хороша.

В-третьих, "Address1", "Address2" и "Address3" не являются атрибутами адресов. Это атрибуты почтовых меток. (Строки на ярлыке рассылки). Это различие может быть для вас неважным. Это действительно важно для меня.

В-четвертых, адреса имеют всю жизнь. Между рождением и смертью они иногда меняются. Они меняются, когда улицы перенаправляются, здания разделяются, здания становятся неразделенными, а иногда (я уверен), когда у городского служащего слишком много пинты. Стихийные бедствия могут уничтожить целые сообщества. Иногда здания нумеруются. В нашей базе данных, которая является крошечной по сравнению с большинством, примерно 1% в год меняются так.

Когда адрес умирает, вам нужно сделать две вещи.

  • Убедитесь, что никто не использует этот адрес для отправки почты, отправки и т.д.
  • Убедитесь, что его смерть не влияет на исторические данные.

Когда сам адрес изменяется, вы должны сделать две вещи.

  • Некоторые данные должны отражать это изменение. Убедитесь, что он делает.
  • Некоторые данные не должны отражать это изменение. Убедитесь, что это не так.

В-пятых, DRY не применяется к внешним ключам. Их цель должна повторяться. Вопрос только в том, насколько широкий ключ? Номер идентификатора является узким, но требует соединения. (10 идентификаторов могут потребовать 10 соединений). Адрес широк, но не требует объединения. (Я говорю здесь о правильном адресе, а не о почтовом ярлыке.)

Это все, что я могу вспомнить с головы.

Ответ 3

Я думаю, что есть проблема, о которой вы не знаете, и что некоторые из этих данных чувствительны к времени. Вы не хотите, чтобы ваши записи показывали, что вы отправили заказ на 35 State St, Chicago Il, когда вы действительно отправили его на 10 King Street Martinsburg WV, но клиент переехал через два года после отправки заказа. Итак, да, создайте таблицу адресов, чтобы получить адрес в тот момент времени, пока любое изменение адреса для кого-то вроде клиента приводит к новому адресу, не изменяющему текущий адрес, который нарушит историю заказа.

Ответ 4

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

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

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

TLDR: используйте отдельную таблицу, если адрес действительно является сущностью, со своей собственной индивидуальной идентификацией и собственным жизненным циклом. В противном случае нет.

Ответ 5

То, что вы должны сами ответить, - это вопрос, является ли тот же адрес в повседневном языке фактически одним и тем же адресом в вашей базе данных. Если кто-то "меняет свой адрес" (разговорно), он действительно связывает себя с другим адресом. Адрес сам по себе изменяется только тогда, когда улица переименована, происходит реформа почтового кода или попадание ядерного оружия. И это редкие события (надеюсь, по большей части). Там ваша основная прибыль: изменение в одном месте для нескольких строк (из нескольких таблиц).

Если вы действительно должны изменить адрес для своей модели - в смысле UPDATE на адрес таблицы - это может работать или не работать для других строк, которые ссылаются на нее. Кроме того, по моему опыту, даже тот же адрес должен выглядеть по-разному для разных целей. Поймите семантические различия, и вы придете к правильной модели, которая лучше всего соответствует вашему реальному миру.

У меня есть несколько баз данных, где я использую общую таблицу улиц (в которой используется таблица городов (в которой используется таблица стран,...)). В сочетании с номером улицы думают об этом как о геокодах (lat/lon), а не о названиях улиц. Адреса не совместно используются для разных таблиц (или строк). Изменения имен улиц и почтовых индексов каскада, другие изменения нет.

Ответ 6

Обычно вы нормализуете данные, насколько это возможно, поэтому используйте таблицу "Адреса".

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

Количество объединений не должно быть серьезной проблемой, основанные на индексе соединения не являются слишком большими издержками.

Ответ 7

Хорошо иметь таблицу разделенных адресов.

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

Ответ 8

Я предпочитаю использовать таблицу XREF, содержащую ссылку FK для таблицы person/business, ссылку FK на таблицу адресов и, как правило, ссылку FK на таблицу ролей (HOME, OFFICE и т.д.), чтобы очертить фактический тип адреса. Я также включаю флаг ACTIVE, чтобы разрешить мне игнорировать старый адрес, сохраняя при этом возможность сохранять историю адресов.

Этот подход позволяет мне поддерживать несколько адресов различных типов для каждого первичного объекта

Ответ 9

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

Вы можете просто сопоставить CityId в таблице адресов как внешний ключ, как показано ниже, вместо того, чтобы все три поля были отдельно (Город, Штат и Страна) в виде простого текста в самой таблице адресов.

Address: {
    CityId
    // With other fields
}

City: {
   CityId
   StateId
  // Other fields
}

State: {
   StateId
   CountryId
 // Other fields
}

Country: {
  CountryId
  // Other fields
}

Если вы сохраняете все три идентификатора (CityId, StateId и CountryId) в таблице адресов, в конце вы должны объединиться с этими таблицами. Следовательно, я предлагаю иметь только cityId, а затем извлекать остальную часть необходимой информации, хотя и объединяется с приведенной выше структурой таблицы.