2016-06-10 46 views
-1

考慮這個數據庫設計的多租戶業務線的Web應用程序:如何在沒有組合鍵的情況下執行二級關係?

一個Tenant是網絡應用的租戶,一個Tenant有許多Shops和許多CustomersCustomer記錄不Tenants之間共享,所以它是有效的多個Customer記錄指代相同的現實生活中的人),並且每個Shop有許多Jobs。 A Job也與每個Customer相關聯。

存在着似乎這樣的問題:有沒有需要一個簡單的約束求解,以防止其中JobCustomerId變更爲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。有沒有什麼方法可以在數據發生變化時測試JOIN的一致性?

+1

您使用的是什麼DBMS/SQL?功能和習語各不相同。 – philipxy

+0

@philipxy MSSQL Server在大多數情況下,有時候是PostgreSQL和MySQL。 – Dai

回答

0

假設您的rdbms支持檢查約束,那麼您可以在作業表上使用檢查約束來檢查客戶ID是否指向與商店ID相同的tannent ID。
通過這種方式,您只剩下每個表上的單列主鍵。
基礎上創建表的語法我猜你正在使用所以你的檢查約束,將是這樣的:

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 DBMS的直接聲明式設計。儘管標準SQL(和關係模型)允許任意的聲明性約束(CHECK和CREATE ASSERTION),但是不幸的是,典型的SQL DBMS不僅允許聲明超級鍵(主鍵& UNIQUE NOT NULL),外鍵(FOREIGN KEY)和有限的檢查。

任意約束強制執行的典型SQL解決方案是定義在INSERT,UPDATE和DELETE和CHECKs函數中根據需要計算表達式的觸發器。不幸的是,DBMS通常不會以正確的原子/序列化方式評估約束執行代碼。

您將您的約束描述爲「測試JOIN的一致性」反映了良好的理解,因爲一般而言,爲了完整性和清晰度,我們希望對任意表達式斷言任意約束。

對任意約束的任何合理實現都必須利用「每當數據發生變化時進行測試」,以避免重新評估整個表達式,因爲當測試可能花費更少的代價時,就可以完成更改。這段代碼就是你不得不用觸發器手寫的東西。這只是大多數供應商的優先事項。請參閱由Lex deHaan & Toon Koppelaars提供的Applied Mathematics for Database Professionals),以便對這些問題和解決方案進行很好的介紹。

1

複合外鍵約束完全有效且有用,但您不需要複合主鍵來使用它們!你只需要在引用表中使用組合索引。由於FK約束,Jobs中的冗餘TenantId不會產生更新異常的風險。

例如:

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

如果你擔心存儲空間,我建議你計算出空間的實際成本基於數據和基準FK約束VS觸發VS之間的性能差異的現實卷檢查涉及子查詢的約束。不要只是假設一個額外的屬性將是低效的。

+0

你能解釋一下「綜合指數」嗎?如果索引中不存在多個列,它們如何包含在索引中? – Dai

+0

我的意思是將主鍵與外鍵引用分開。我希望這個例子澄清。 – reaanb

+0

關於組合索引優化,另請參閱http://stackoverflow.com/questions/2292662/how-important-is-the-order-of-columns-in-indexes – reaanb

相關問題