2014-12-09 3 views
4

У меня есть 3 таблицы таблиц с именами «Проекты», «Контракты» и «Инциденты». Конструкция предназначена для системы технического обслуживания на основе проекта. Клиенты могут заключать контракты на проект по обслуживанию различных установок. Кроме того, отдельные инциденты, которые могут или не могут быть связаны с контрактом по проекту, должны быть отчетными, например, как дефектные установки.Внешние ключи как к родителям, так и к родительским родителям

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

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

Чтобы усугубить ситуацию, в Договоре также упоминается «Должник» из другой таблицы. Поэтому в отсутствие Контракта Инцидент должен также иметь возможность обратиться к Должнику.

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

Для чего это стоит, я разработчик, отвечающий за письмо приложения, которое будет работать с этой базой данных. Проект должен быть создан в WPF с LINQ над SQL. Одним из требований является то, что он должен иметь возможность запрашивать запись Project для всех ее инцидентов, в том числе те, на которые ссылаются через Контракты.

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

+0

Альтернатива условным нулевым внешним ключам для одной из трех таблиц, я думаю, будет иметь отдельные таблицы «ProjectIncident», «ContractIncident» и «DebtorIncident», которые вы могли бы придать фантазии и суперкласса «Таблице за класс» наследование ('BaseIncident') без внешних ключей? – StuartLC

ответ

1

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

Это упрощает дизайн базы данных, но в нем возникают другие проблемы. Например, чтобы найти инциденты без контракта, вы не будете искать NULL в колонке контракта. Вы бы искали «не совсем контракт» в таблице контрактов. В зависимости от обстоятельств это может быть более элегантное решение. Это также решает проблему с Debtor.

Это вызывает еще одну проблему, которая может быть связана с несколькими контрактами. Фактически, вы можете закончить курс в направлении, где вам нужно поддерживать еще-другую таблицу, которая представляет собой n-m-сопоставление между инцидентами и проектами.

+0

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

0

У меня нет предпочтений или опыта. На первый взгляд мне нравится идея «фиктивного» контракта. Если вы сделаете это, я предлагаю добавить в контракт конкретный столбец, чтобы вы могли легко увидеть, является ли он фиктивным или реальным контрактом.

В одном фиктивном контракте могут содержаться все инциденты без контракта. Риск фиктивного контракта возникает, когда вы начинаете использовать поля фиктивного контракта (например, Debtor). Если контракта нет, то Должник тот же для всех случаев одного и того же проекта? Если это не будет означать, что вы закончите с несколькими фиктивными контрактами (по одному на должника).Возможно, в будущем у вас будут другие поля, которые в конечном итоге приведут к фиктивному контракту за инцидент.

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

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

0

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

CREATE TABLE dbo.Project 
(
     ProjectID INT IDENTITY, 
     Filler CHAR(1) NULL, 
    CONSTRAINT PK_Project__ProjectID PRIMARY KEY (ProjectID) 
); 

CREATE TABLE dbo.Contract 
(
     ContractID INT IDENTITY, 
     ProjectID INT NOT NULL, 
     Filler CHAR(1) NULL, 
    CONSTRAINT PK_Contract__ContractID PRIMARY KEY (ContractID), 
    CONSTRAINT FK_Contract__ProjectID FOREIGN KEY (ProjectID) REFERENCES dbo.Project (ProjectID), 
    CONSTRAINT UQ_Contract__ContractID_ProjectID UNIQUE (ContractID, ProjectID) 
); 

CREATE TABLE dbo.Incident 
(
     IncidentID INT IDENTITY, 
     ProjectID INT NOT NULL, 
     ContractID INT NULL, 
     Filler CHAR(1) NULL, 
    CONSTRAINT PK_Incident__IncidentID PRIMARY KEY (IncidentID), 
    CONSTRAINT FK_Incident__ProjectID FOREIGN KEY (ProjectID) REFERENCES dbo.Project (ProjectID), 
    CONSTRAINT FK_Incident__ContractID FOREIGN KEY (ContractID, ProjectID) REFERENCES dbo.Contract (ContractID, ProjectID) 
); 

-- CREATE TWO DUMMY PROJECTS 
INSERT dbo.Project DEFAULT VALUES; 
INSERT dbo.Project DEFAULT VALUES; 

-- ADD A CONTRACT TWO EACH 
INSERT dbo.Contract (ProjectID) 
SELECT ProjectID 
FROM Project; 

-- ADD AN INCIDENT TO EACH WITH NO CONTRACT 
INSERT dbo.Incident (ProjectID) 
SELECT ProjectID 
FROM Project; 

-- ADD A VALID INCIDENT TO EACH CONTRACT 
INSERT dbo.Incident (ContractID, ProjectID) 
SELECT ContractID, ProjectID 
FROM  dbo.Contract; 

-- TRY AND ADD INVALID CONTRACT TO FIRST PROJECT 
INSERT dbo.Incident (ContractID, ProjectID) 
SELECT c.ContractID, p.ProjectID 
FROM dbo.Project AS p 
     CROSS JOIN dbo.Contract AS c 
WHERE c.ProjectID != p.ProjectID; 

Это завершаться с ошибкой:

>The INSERT statement conflicted with the FOREIGN KEY constraint "FK_Incident__ContractID". The conflict occurred in database "TestDB", table "dbo.Contract". 

Внешний ключ может ссылаться на единственное ограничение на Contract, которое позволяет обеспечить целостность в dbo.Incident, то есть вы не можете ввести проект, который неправильно отображает введенный контракт. Единственное реальное падение сценария заключается в том, что вы дублируете ProjectID, когда заполняется ContractID, но я не думаю, что это серьезная проблема. Конечно (на мой взгляд) меньше проблемы, чем фиктивные данные.

Это то очень просто определить фиктивные контракты:

SELECT * 
FROM dbo.Incident 
WHERE ContractID IS NULL; 
0

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

public static readonly int DummyContractId = 25; // Or whatever the ID is of your dummy contract ID. 

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

0

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

Смежные вопросы