Как оптимально хранить следующие данные SQL в SQL Server 2008 - PullRequest
2 голосов
/ 22 февраля 2010

Я создаю страницу, где люди могут публиковать статьи. Когда пользователь публикует статью, она появляется в списке, как и связанные вопросы в переполнении стека (когда вы добавляете новый вопрос). Это довольно просто.

Моя проблема в том, что у меня есть 2 типа пользователей. 1) Незарегистрированные частные пользователи. 2) Компания.

Незарегистрированным пользователям необходимо ввести свое имя, адрес электронной почты и телефон. Принимая во внимание, что пользователи компании просто должны ввести название своей компании / пароль. Довольно просто.

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

Теперь к моей проблеме в руках:

Итак, у меня есть одна таблица с информацией о компаниях, ID (guid), Name, email, phone и т. Д.

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

Одна таблица с информацией о незарегистрированных пользователях, их ID, имени, электронной почте и телефоне.

Как связать таблицу статей с таблицей компаний / незарегистрированных пользователей. Можно ли сделать целое число, содержащее 2 значения: 1 = незарегистрированный пользователь и 2 = компания, а затем одно поле с идентификационным номером для указанного пользователя / компании. Похоже, вам нужно много дополнительного кода для запроса базы данных. Спектакль? Как я мог тогда вернуть статью вместе с контактной информацией? Вы также должны иметь возможность вернуть все статьи определенной компании.

Итак, таблица компании будет:

ID (guid), company name, phone, email, password, street, zip, country, state, www, description, contact person and a few more that i don't have here right now.

Таблица незарегистрированных пользователей:

ID (guid), name, phone, email

Столовый артикул:

ID (int/guid/short guid), headline, content, published date, is_company, id_to_user

Есть ли лучший подход?

Качества, которые я ищу, это: производительность, простота запроса и простота обслуживания (добавление новых полей, индексов и т. Д.)

Ответы [ 9 ]

11 голосов
/ 27 февраля 2010

Теория

Проблема, которую вы описали, называется Табличным наследованием в теории моделирования данных. В книге Мартина Фаулера решения следующие:

Таким образом, с точки зрения теории и отраслевой практики приемлемы все три решения: одна таблица Posters с столбцами NULLable столбцы (т. Е. Одна таблица), три таблицы Posters, Companies и Persons (т. Е. Наследование классов) и две таблицы Companies и лица (т.е. конкретное наследство).

Теперь о плюсах и минусах.

Стоимость пустых столбцов

Структура записи обсуждается в Внутри механизма хранения: анатомия записи :

Растровое изображение NULL

  • два байта для подсчета столбцов в записи
  • переменное число байтов для хранения одного бита на столбец в запись, независимо от того, столбец обнуляемый или нет (это отличается и проще, чем SQL Server 2000, который имел один бит на обнуляемый только столбец)

Таким образом, если у вас есть хотя бы один столбец NULLable, вы оплачиваете стоимость растрового изображения NULL в каждой записи, как минимум, 3 байта. Но стоимость идентична , если у вас есть 1 или 8 столбцов! 9-й столбец NULLable добавит байт к растровому изображению NULL в каждой записи. формула описана в Оценка размера кластеризованного индекса : 2 + ((Num_Cols + 7) / 8)

Фактор вождения Peformance

В системе баз данных на самом деле есть только один фактор, который влияет на производительность: количество отсканированных данных. Насколько велика запись, отсканированная планом запроса, и сколько записей нужно отсканировать. Поэтому для повышения производительности вам необходимо:

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

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

  • первая страница постов: как и SO, страница последних постов с заголовком, выдержкой, временем публикации и основной информацией об авторе (имя, граватар). Чтобы отобразить эту страницу, вам нужно присоединиться к сообщениям с авторами, но вам нужно только имя автора и gravatar. И наследование отдельных таблиц, и наследование таблиц классов будут работать, но конкретное наследование таблиц будет неудачным. Это связано с тем, что вы не можете позволить такому запросу выполнять условные объединения (т. Е. Присоединяться к статьям, опубликованным в компаниях или Persons), такой запрос будет менее чем оптимальным.

  • сообщений на автора: сначала пользователи должны войти в систему, а затем они увидят свои собственные сообщения (это характерно для непубличных сайтов, ориентированных на публикации, например, отслеживание инцидентов). Для такой схемы подойдут все три схемы наследования таблиц.

Заключение

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

  • Какие поля из компаний и частных лиц будут отображаться на целевой странице сайта (т. Е. Наиболее часто и критически важный для производительности запрос)? Вы не хотите объединять 5 таблиц, чтобы показать эти поля.
  • Некоторые поля информации о компании / персонале необходимы только на странице информации о пользователе? Возможно разделить таблицу по вертикали на таблицы CompaniesExtra и PersonsExtra. Или используйте индекс, который будет охватывать часто используемые поля (этот подход упрощает код и его легче поддерживать согласованным за счет дублирования данных)

PS

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

4 голосов
/ 27 февраля 2010

В идеале, если бы вы могли использовать ORM (как упомянуто TFD), я бы так и сделал. Поскольку вы не прокомментировали это, а также всегда возвращаетесь с вопросом «производительности», я предполагаю, что вы не хотели бы использовать его.

Используя чистый SQL, я бы предложил следующий подход: структура таблицы должна быть такой:

ActicleOwner [ID (guid)]
Company [ID (guid) - PK as well as FK to ActicleOwner.ID, 
    company name, phone, email, password, street, zip, ...]
UnregisteredUser [ID (guid) - PK as well as FK to ActicleOwner.ID, 
    name, phone, email]
Article = [ID (int/guid/short guid), headline, content, published date, 
    ArticleOwnerID - FK to ActicleOwner.ID]

Позволяет увидеть использование:

INSERT : накладные расходы - это необходимость добавить строку в таблицу ActicleOwner для каждой компании / подразделения. Это не та операция, которая происходит так часто, нет необходимости оптимизировать производительность

ВЫБРАТЬ :

  • Company / UU: ну, как UU, так и Company легко найти, так как вам не нужно присоединяться к какой-либо другой таблице, так как вся информация о требуемом объекте находится в одной таблице
  • Акты одной компании / UU: опять же, вам просто нужно отфильтровать по GUID компании / UU, и вы идете: SELECT (list fields) FROM Acticle WHERE ArticleOwnerID = @AOID

Также подумайте, что однажды вам может понадобиться поддержка нескольких Владельцев в Статье. При использовании вышеприведенного подхода к родительской таблице (или упомянутого Винсентом) вам просто нужно будет ввести таблицу отношений, в то время как с двумя NK-совместимыми ограничениями FK для каждой таблицы-владельца это решение, которое вы застряли.


Производительность:
Вы уверены, что у вас проблемы с производительностью? Какова ваша цель?

Одна вещь, которую я могу порекомендовать, глядя на вашу модель в отношении производительности, это не использовать GUID в качестве кластерного индекса (который используется по умолчанию для PK). Потому что в основном ваши операторы INSERT будут вставлять данные случайным образом в таблицу.
Альтернативы:

Так что, если вы так сильно настроены на производительность, вы можете попробовать сделать следующее:

ActicleOwner (ID (int identity) - PK, UID (guid) - UC)
Company [ID (int) - PK as well as FK to ActicleOwner.ID,
         UID (guid) - UC as well as FK to ActicleOwner.UID, company name, ...]
...
Article = [ID (int/guid/short guid), headline, content, published date, 
    ArticleOwnerID - FK to ActicleOwner.ID (int)]

Для ВСТАВИТЬ пользователя (Company / UU) вы делаете следующее:

  1. Имея UID (возможно, последовательный) из кода, вы делаете INSERT в таблицу ActicleOwner. Вы получаете автоматически сгенерированный целочисленный идентификатор.
  2. Вы вставляете все данные в Company / UU, включая только что полученный целочисленный ID.

ActicleOwner.ID будет целым числом, поэтому поиск по нему будет быстрее, чем по UID, особенно если у вас есть индекс для него.

2 голосов
/ 27 февраля 2010

Я бы предложил супер-тип Author для Person и Organization подтипов.

article_owner



Обратите внимание, что AuthorID служит одновременно первичным и внешним ключом для таблиц Person и Organization.

Итак, давайте сначала создадим таблицы:

CREATE TABLE Author( 
   AuthorID integer IDENTITY NOT NULL
  ,AuthorType char(1)
  ,Phone varchar(20)
  ,Email varchar(128) NOT NULL
  );
ALTER TABLE Author ADD CONSTRAINT pk_Author PRIMARY KEY (AuthorID);

CREATE TABLE Article ( 
   ArticleID integer IDENTITY NOT NULL
  ,AuthorID integer NOT NULL
  ,DatePublished date
  ,Headline varchar(100)
  ,Content varchar(max)
  );
ALTER TABLE Article ADD 
   CONSTRAINT pk_Article PRIMARY KEY (ArticleID)
  ,CONSTRAINT fk1_Article FOREIGN KEY (AuthorID) REFERENCES Author(AuthorID) ;

CREATE TABLE Person ( 
   AuthorID integer NOT NULL
  ,FirstName varchar(50)
  ,LastName varchar(50)
  );
ALTER TABLE Person ADD 
   CONSTRAINT pk_Person PRIMARY KEY (AuthorID)
  ,CONSTRAINT fk1_Person FOREIGN KEY (AuthorID) REFERENCES Author(AuthorID);

CREATE TABLE Organization ( 
   AuthorID integer NOT NULL
  ,OrgName varchar(40)
  ,OrgPassword varchar(128)
  ,OrgCountry varchar(40)
  ,OrgState varchar(40)
  ,OrgZIP varchar(16)
  ,OrgContactName varchar(100)
  );
ALTER TABLE Organization ADD 
   CONSTRAINT pk_Organization PRIMARY KEY (AuthorID)
  ,CONSTRAINT fk1_Organization FOREIGN KEY (AuthorID) REFERENCES Author(AuthorID);

При вставке в Author вам нужно захватить автоматически увеличенный идентификатор, а затем использовать его для вставки оставшихся данных человеку или организации, в зависимости от AuthorType. Каждая строка в Author имеет только одну совпадающую строку в Person или Organization, но не в обеих. Вот пример того, как захватить AuthorID.

-- Insert into table and return the auto-incremented AuthorID
INSERT  INTO Author ( AuthorType, Phone, Email )
    OUTPUT INSERTED.AuthorID
VALUES  ( 'P', '789-789-7899', 'dudete@mmahoo.com' );

Вот несколько примеров того, как запрашивать авторов:

-- Return all authors (org and person)
SELECT  *
FROM    dbo.Author AS a
        LEFT JOIN dbo.Person AS p ON a.AuthorID = p.AuthorID
        LEFT JOIN dbo.Organization AS c ON c.AuthorID = a.AuthorID ; 

-- Return all-organization authors
SELECT  *
FROM    dbo.Author AS a
        JOIN dbo.Organization AS c ON c.AuthorID = a.AuthorID ; 

-- Return all person-authors
SELECT  *
FROM    dbo.Author AS a
        JOIN dbo.Person AS p ON a.AuthorID = p.AuthorID

А теперь все статьи с авторами.

-- Return all articles with author information
SELECT  *
FROM    dbo.Article AS x
        JOIN dbo.Author AS a ON a.AuthorID = x.AuthorID
        LEFT JOIN dbo.Person AS p ON a.AuthorID = p.AuthorID
        LEFT JOIN dbo.Organization AS c ON c.AuthorID = a.AuthorID ; 

Существует два способа вернуть все статьи, принадлежащие организациям. В первом примере возвращаются только столбцы из таблицы Organization, а во втором - столбцы из таблицы Person со значениями NULL.

-- (1) Return all articles belonging to organizations
SELECT  *
FROM    dbo.Article AS x
        JOIN dbo.Author AS a ON a.AuthorID = x.AuthorID
        JOIN dbo.Organization AS c ON c.AuthorID = a.AuthorID;

-- (2) Return all articles belonging to organizations
SELECT  *
FROM    dbo.Article AS x
        JOIN dbo.Author AS a ON a.AuthorID = x.AuthorID
        LEFT JOIN dbo.Person AS p ON a.AuthorID = p.AuthorID
        LEFT JOIN dbo.Organization AS c ON c.AuthorID = a.AuthorID
WHERE AuthorType = 'O';

И чтобы вернуть все статьи, принадлежащие конкретной организации, снова два метода.

-- (1) Return all articles belonging to a specific organization
SELECT  *
FROM    dbo.Article AS x
        JOIN dbo.Author AS a ON a.AuthorID = x.AuthorID
        JOIN dbo.Organization AS c ON c.AuthorID = a.AuthorID
WHERE c.OrgName = 'somecorp';

-- (2) Return all articles belonging to a specific organization
SELECT  *
FROM    dbo.Article AS x
        JOIN dbo.Author AS a ON a.AuthorID = x.AuthorID
        LEFT JOIN dbo.Person AS p ON a.AuthorID = p.AuthorID
        LEFT JOIN dbo.Organization AS c ON c.AuthorID = a.AuthorID
WHERE c.OrgName = 'somecorp';

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

Напоминаем, что статья обычно имеет нескольких авторов, поэтому таблица Article_Author «многие ко многим» будет в порядке.

2 голосов
/ 26 февраля 2010

Это распространенная проблема программирования ОО, которая не должна решаться в домене SQL. Должно быть обработано вашим ORM

Создайте два класса в своем программном коде по мере необходимости и позвольте ORM сопоставить их с подходящим представлением SQL. Для производительности подойдет одна таблица с нулями, единственные накладные расходы - столбец дискриминатора

Некоторые примеры hibernate-наследование

0 голосов
/ 27 февраля 2010

Я не уверен, что вам нужно различать компании и людей; только зарегистрированные и незарегистрированные авторы.

Я добавил это для ясности. Вы можете просто использовать проверочное ограничение для таблицы авторов, чтобы ограничить значения U и R.

Create Table dbo.AuthorRegisteredStates
(
      Code char(1) not null Primary Key Clustered
      , Name nvarchar(15) not null
      , Constraint UK_AuthorRegisteredState Unique ( [Name])
)
Insert dbo.AuthorRegisteredState(Code, Name) Values('U', 'Unregistered')
Insert dbo.AuthorRegisteredState(Code, Name) Values('R', 'Registered')
GO

Ключ в любой системе баз данных - целостность данных. Итак, мы хотим убедиться, что имена пользователей уникальны и, возможно, что имена уникальны. Вы хотите, чтобы два человека с одинаковым именем опубликовали статью? Как читатель будет их различать? Обратите внимание, что мне все равно, представляет ли Автор компанию или человека. Если кто-то регистрирует компанию или человека, они могут указать имя и фамилию, если захотят. Однако требуется, чтобы все вводили имя (воспринимайте его как отображаемое имя). Мы никогда не будем искать авторов на основании чего-либо, кроме имени.

Create Table dbo.Authors
(
      Id int not null identity(1,1) Primary Key Clustered
      , AuthorStateCode char(1) not null
      , Name nvarchar(100) not null
      , Email nvarchar(300) null
      , Username nvarchar(20) not null
      , PasswordHash nvarchar(50) not null
      , FirstName nvarchar(25) null
      , LastName nvarchar(25) null
      ...
      , Address nvarchar(max) null
      , City nvarchar(40) null
      ...
      , Website nvarchar(max) null
      , Constraint UK_Authors_Name Unique ( [Name] )
      , Constraint UK_Authors_Username Unique ( [Username] )
      , Constraint FK_Authors_AuthorRegisteredStates
           Foreign Key ( AuthorStateCode )
           References dbo.AuthorRegisteredStates ( Code )

      -- optional. if you really wanted to ensure that an author that was unregistered
      -- had a firstname and lastname. However, I'd recommend enforcing this in the GUI
      -- if anywhere as it really does not matter if they 
      -- enter a first name and last name.
      -- All that matters is whether they are registered and entered a name.
      , Constraint CK_Authors_RegisteredWithFirstNameLastName
           Check ( State = 'R' Or ( State = 'U' And FirstName Is Not Null And LastName Is Not Null ) )
)

Может ли один автор опубликовать две статьи в одну и ту же дату и время? Если нет (как я уже догадался здесь), то мы добавляем уникальное ограничение. Вопрос в том, нужно ли вам указывать статью. Какую информацию вы могли бы дать, чтобы найти статью, помимо общей даты ее публикации?

Create Table dbo.Articles
(
      Id int not null identity(1,1) Primary Key Clustered
      , AuthorId int not null
      , PublishedDate datetime not null
      , Headline nvarchar(200) not null
      , Content nvarchar(max) null
      ...
      , Constraint UK_Articles_PublishedDate Unique ( AuthorId, PublishedDate )
      , Constraint FK_Articles_Authors
           Foreign Key ( AuthorId )
           References dbo.Authors ( Id )
)

Кроме того, я бы добавил индекс на ОпубликованоDate для улучшения поиска по дате.

Create Index IX_Articles_PublishedDate dbo.Articles On ( PublishedDate )

Я бы также включил свободный текстовый поиск для поиска по содержанию статей.

Я думаю, что опасения по поводу "пустого пространства", вероятно, преждевременные оптимизации Влияние на производительность будет ноль. Это тот случай, когда небольшое количество денормализации ничего не стоит вам с точки зрения производительности и выигрывает с точки зрения развития. Однако, если это действительно вас касается, вы можете переместить информацию об адресе в таблицу 1: 1 следующим образом:

Create Table dbo.AuthorAddresses
(
    AuthorId int not null Primary Key Clustered
    , Street nvarchar(max) not null
    , City nvarchar(40) not null
    ...
    , Constraint FK_AuthorAddresses_Authors
        Foreign Key ( AuthorId )
        References dbo.Authors( Id )
)

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

0 голосов
/ 25 февраля 2010

Я решил подобные проблемы с помощью подхода, подобного этому:

Компания -> Компания
Статьи пользователя -> Статьи пользователя

Статья

CompanyArticles содержит отображение из Company в Article UserArticles содержит отображение от пользователя к статье

Статья ничего не знает о том, кто ее создал.

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

Получение всех статей и контактной информации будет выглядеть так:

SELECT name, phone, email FROM 
user 
JOIN userarticles on user.user_id = userarticles.user_id
JOIN articles on userarticles.article_id = article.article_id

UNION

SELECT name, phone, email FROM 
company
JOIN companyarticles on company.company_id = companyarticles.company_id
JOIN articles on companyarticles.article_id = article.article_id
0 голосов
/ 22 февраля 2010

Интересным подходом будет использование модели Node, за которой следует Drupal, где все фактически является Node, а все остальные данные хранятся во вторичной таблице. Это очень гибкий инструмент, о чем свидетельствует широкое использование Drupal на крупных сайтах публикаций и обсуждений.

Макет будет примерно таким:

Node

ID Тип (пользователь, гость, статья) TypeID (PKey связанных данных) созданный Модифицированный

Article

ID Field1 Field2 И т.д.

User

ID Field1 Field2 И т.д.

Guest

ID Field1 Field2 Etc.

Это альтернативный вариант с некоторыми хорошими преимуществами. Наибольшая гибкость.

0 голосов
/ 22 февраля 2010

Я предпочитаю использовать таблицу, которая действует как супер-таблица для обоих.

ArticleOwner = (ID (guid), company name, phone, email)
company = (ID, password)
unregistereduser = (ID)
article = (ID (int/guid/short guid), headline, content, published date, owner)

Тогда для запроса базы данных потребуется JOIN для 3 таблиц, но в этом случае у вас нет пустых полей.

0 голосов
/ 22 февраля 2010

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

Автор:
ID (guid), тип, имя, телефон, электронная почта, пароль

где тип равен 1 для компании, 2 - для незарегистрированного пользователя.

OR

Держите пользователей и компании отдельно, но требуйте, чтобы каждая компания имела пользователя в таблице пользователей. Эта таблица должна иметь поле CompanyID. Я думаю, что это было бы более логичным и элегантным.

...