2016-06-10 2 views
-1

Рассмотрим структуру базы данных для нескольких аренды линии прямой-бизнеса веб-приложением:Как я могу применять отношения второй степени без составных клавиш?

Tenant является арендатором веб-приложения, Tenant имеет много Shops и много Customers (Customer записи не распределяются между Tenants , поэтому он действителен для нескольких записей Customer, чтобы обратиться к одному и тому же человеку в реальной жизни), и у каждого Shop есть много Jobs. A Job также связан с каждым Customer.

Там существует проблема в том, что там, кажется, не тривиальное ограничение решение, чтобы предотвратить случай, когда Job «s CustomerId изменяется на Customer, который не принадлежит к родителю Tenant, создавая тем самым недопустимые данные.

Вот настоящая схема:

CREATE TABLE Tenants (
    TenantId bigint IDENTITY(1,1) PRIMARY KEY 
    ... 
) 

CREATE TABLE Shops (
    TenantId bigint FOREIGN KEY(Tenants.TenantId), 
    ShopId bigint IDENTITY(1,1) PRIMAREY KEY, 
    ... 
) 

CREATE TABLE Customers (
    TenantId bigint FOREIGN KEY(Tenants.TenantId), 
    CustomerId bigint IDENTITY(1,1) PRIMARY KEY 
    ... 
) 

CREATE TABLE Jobs (
    ShopId bigint FOREIGN KEY(Shops.ShopId) 
    JobId bigint IDENTITY(1,1) PRIMARY KEY, 
    CustomerId bigint FOREIGN KEY(Customers.CustomerId) 
) 

В настоящее время единственное решение, я могу думать о том, чтобы изменить дизайн, чтобы использовать составные ключи, которые всегда включают в родительский Tenant.TenantId, которые затем совместно соответственно:

CREATE TABLE Shops (
    TenantId bigint, 
    ShopId bigint IDENTITY(1,1), 
    ... 

    PRIMARY KEY(TenantId, ShopId) 
    FOREIGN KEY(TenantId REFERENCES Tenants (TenantId)) 
) 

CREATE TABLE Customers (
    TenantId bigint, 
    CustomerId bigint IDENTITY(1,1) 
    ... 

    PRIMARY KEY(TenantId, CustomerId) 
    FOREIGN KEY(TenantId REFERENCES Tenants (TenantId)) 
) 

CREATE TABLE Jobs (
    TenantId bigint 
    ShopId bigint 
    JobId bigint IDENTITY(1,1), 
    CustomerId bigint 

    PRIMARY KEY(TenantId, ShopId, JobId) 

    FOREIGN KEY(TenantId REFERENCES Tenants (TenantId)) 
    FOREIGN KEY(TenantId, ShopId REFERENCES Shops(TenantId, ShopID)) 
    FOREIGN KEY(TenantId, CustomerId REFERENCES Customers(TenantId, CustomerId)) 
) 

... похоже, что-то вроде взлома, с большим количеством избыточных данных - особенно, поскольку IDENTITY используется в любом случае. Есть ли способ, которым РСУБД может тестировать JOINs для согласованности всякий раз, когда данные мутируются?

+1

Какие СУБД/SQL вы используете? Особенности и идиомы различаются. – philipxy

+0

@philipxy MSSQL Server в большинстве случаев, иногда PostgreSQL и MySQL. – Dai

ответ

0

Предполагая, что ваш rdbms поддерживает ограничения проверки, вы можете использовать ограничение проверки в своей таблице заданий, чтобы проверить, что идентификатор клиента относится к тому же идентификатору tannent, что и идентификатор магазина.
Таким образом, вы остаетесь с первичным ключом одного столбца на каждой таблице.
на основе синтаксиса создания таблицы я предполагаю, что Вы используете поэтому такая проверка ограничение будет что-то вроде этого:

ALTER TABLE Jobs 
    ADD CONSTRAINT chk_jobs_customer_shop 
    CHECK dbo.fnCheckCustomerAndShopRelationship(customerId, shopId) = 1 

и, конечно же, вам нужно будет сначала создать UDF:

CREATE FUNCTION dbo.fnCheckCustomerAndShopRelationship 
(
    @customerId int, 
    @shopId int 
) 
RETURNS int 
AS 
BEGIN 

    IF EXISTS 
    (
     SELECT 1 
     FROM Customers c 
     INNER JOIN Shops s ON c.TenantId = s.TenantId 
    ) 
     RETURN 1 
    ELSE 
     RETURN 0 
END; 
GO 
0

Ваш второй проект - простой декларативный дизайн для типичных СУБД SQL.

Хотя стандарт SQL (и реляционная модель) позволяет произвольным декларативные ограничения (ПРОВЕРЬТЕ и CREATE ASSERTION), типичный SQL СУБД, к сожалению, только позволяют декларацию superkeys (PRIMARY KEY & UNIQUE NOT NULL), иностранные superkeys (FOREIGN KEY) и ограниченным проверяю.

Типичное SQL-решение для принудительного принудительного принуждения - это определение триггеров, которые оценивают выражения по мере необходимости на INSERT, UPDATE и DELETE и функционируют в CHECK. К сожалению, СУБД обычно не оценивают код принудительного принуждения соответствующим образом атомарным/сериализованным способом.

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

Любая разумная реализация произвольных ограничений должна использовать «тестирование всякий раз, когда данные мутируются», чтобы избежать переоценки целых выражений, когда может быть проведено гораздо менее дорогостоящий тест, только что изменившийся. Этот код - это то, к чему вы, к сожалению, пишете вручную триггеры. Это не приоритет большинства вендоров. См. Applied Mathematics for Database Professionals от Lex deHaan & Toon Koppelaars) для хорошей презентации этих проблем и решений.

1

Сложные ограничения внешнего ключа являются абсолютно правильными и полезными, но для их использования вам не нужны составные первичные ключи! Вам просто нужны составные индексы в таблицах с ссылками. Резервный TenantId в Jobs не создаст риск аномалий обновления благодаря ограничениям FK.

Например:

CREATE TABLE Shops (
    ShopId bigint IDENTITY(1,1), 
    TenantId bigint, 
    PRIMARY KEY (ShopId), 
    UNIQUE KEY (TenantId, ShopId), 
    FOREIGN KEY (TenantId) REFERENCES Tenants (TenantId) 
) 

CREATE TABLE Customers (
    CustomerId bigint IDENTITY(1,1), 
    TenantId bigint, 
    PRIMARY KEY (CustomerId), 
    UNIQUE KEY (TenantId, CustomerId), 
    FOREIGN KEY (TenantId) REFERENCES Tenants (TenantId) 
) 

CREATE TABLE Jobs (
    JobId  bigint IDENTITY(1,1), 
    TenantId bigint, 
    ShopId  bigint, 
    CustomerId bigint, 
    PRIMARY KEY (JobId), 
    FOREIGN KEY (TenantId, ShopId) REFERENCES Shops (TenantId, ShopID), 
    FOREIGN KEY (TenantId, CustomerId) REFERENCES Customers (TenantId, CustomerId) 
) 

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

+0

Можете ли вы объяснить «Композитные индексы»? Как включить несколько столбцов в индекс, если он отсутствует в родительской таблице? – Dai

+0

Я хотел отделить первичный ключ от ссылки внешнего ключа. Надеюсь, что этот пример прояснится. – reaanb

+0

Также см. Http://stackoverflow.com/questions/2292662/how-important-is-the-order-of-columns-in-indexes в отношении оптимизации составных индексов – reaanb

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