使用者特徵
我真的在與一個問題作鬥爭,我有一個使用者表,該表具有越來越多的使用者特徵(宗教、吸煙偏好等)。到目前為止,我使用的策略是為每個偏好添加一個列,該列與另一個表相關。
例如,如果使用者 XYZ 的 RelgionId 為 3,則可能意味著他們是基督徒。在執行時,如果我需要他們的宗教信仰,我會加入另一張桌子。
到目前為止,這一策略已經奏效。但是,隨著偏好數量的增加,我開始擔心表格中的列數。此外,如果我需要獲取單個使用者的所有值,此策略會導致許多連接。
我想找出表示這些數據的最規範化的方式。有人有什麼想法嗎?
你可以通過創建本質上是這些值的屬性包來做這種事情。這不是我非常喜歡的一種方法,但與 SQL Server 中的所有內容一樣,它有它的位置,你的場景很可能就是這種情況。
這本質上是 EAV 方法,Aaron Bertrand 在這裡有一篇很棒的文章,更詳細地描述了利弊
CREATE TABLE Users ( UserID int identity(1, 1) primary key clustered ,UserName varchar(200) ); CREATE TABLE PreferenceType ( PreferenceTypeID int identity(1, 1) primary key clustered ,PreferenceName varchar(200) ); CREATE TABLE PreferenceValue ( PreferenceValueID int identity(1, 1) primary key clustered ,PreferenceValueName varchar(200) ,PreferenceTypeID int ); CREATE TABLE UserPreference ( UserPreferenceID int identity(1, 1) primary key clustered ,UserID int ,PreferenceValueID int ); ALTER TABLE PreferenceValue ADD CONSTRAINT FKValue_Type FOREIGN KEY (PreferenceTypeID) REFERENCES PreferenceType(PreferenceTypeID); ALTER TABLE UserPreference ADD CONSTRAINT FK_UserPreference_User FOREIGN KEY (UserID) REFERENCES Users(UserID); ALTER TABLE UserPreference ADD CONSTRAINT FK_UserPreference_Value FOREIGN KEY (PreferenceValueID) REFERENCES PreferenceValue(PreferenceValueID); insert into Users (UserName) Values ('User1' ) --tblUserID = 1 , ('User2' ); --tblUserID = 2 Insert into PreferenceType (PreferenceName) Values ('Religion') --Type1 , ('Smoker'); --Type 2 Insert into PreferenceValue (PreferenceValueName, PreferenceTypeID) Values ('Christian', 1) --Value 1 , ('Muslim', 1) --Value 2 , ('Non Smoker', 2) --Value 3 , ('Smoker' , 2); -- Value 4 --User 1 is a Christian Smoker --User 2 is a Muslim Non-Smoker Insert into UserPreference (UserID, PreferenceValueID) Values (1, 1) , (1, 4) , (2, 2) , (2, 3);
視覺上:
因此,當您想獲取使用者 1 的資訊時,您將執行此選擇:
select u.UserID, u.UserName, pv.PreferenceValueName, pt.PreferenceName From Users u join UserPreference up on u.UserID = up.UserID join PreferenceValue pv on up.PreferenceValueID = pv.PreferenceValueID join PreferenceType pt on pv.PreferenceTypeID = pt.PreferenceTypeID Where u.UserName = 'User1';
你會得到以下格式的結果
使用者 ID、使用者名、PreferenceValue、PreferenceType
1、User1、基督教、宗教
1,使用者1,吸煙者,吸煙者
要在與此類似的模式中包含 Date 值,使用已創建但添加兩個新表的表,您可以執行以下操作:
CREATE TABLE DateType ( DateTypeID int identity(1, 1) primary key clustered ,DateTypeName varchar(200) ); CREATE TABLE UserDateType ( UserDateTypeID int identity(1, 1) primary key clustered ,UserID int ,DateTypeID int ,DateValue datetime ); CREATE UNIQUE NONCLUSTERED INDEX uq_UserDateType on UserDateType (UserID, DateTypeID); --Optional. To only allow one row per type. ALTER TABLE UserDateType ADD CONSTRAINT FKDateType FOREIGN KEY (DateTypeID) REFERENCES DateType(DateTypeID); ALTER TABLE UserDateType ADD CONSTRAINT FKUserDate_UserID FOREIGN KEY (UserID) REFERENCES Users(UserID); Insert into DateType (DateTypeName) Values ('BirthDate') --Type1 , ('Anniversary'); --Type 2 Insert into UserDateType (UserID, DateTypeID, DateValue) Values (1, 1, '01/01/1980') --User 1 has a Birthday of 01/01/1980 , (1, 2, '01/01/2000') --User 1 has an Anniversary of 01/01/2000 , (2, 1, '02/01/1995'); --User 2 has an Birthday of 01/01/2000
通過擁有這些類型的表,您可以動態添加新的日期類型並以這種方式將它們與使用者相關聯。
如上所述,我不確定這是不是理想的方法,因為它肯定有缺陷,但這是一種你可以用 Date 值做這類事情的方法。
規範化是一個很好的概念,對於性能良好的關係數據庫至關重要。然而,它可能會適得其反。例如,您幾乎從未見過這樣分解的地址:
StateRegionTable CityTable ZipTable StateId CityId ZipId StateDesc CityDesc ZipDesc AddressTable AddressId Address1 Address2 CityId StateId ZipId
它並不是那麼有用,並且通過額外的連接減慢了速度。在您的情況下,我建議刪除表只有 Id/Description 對的額外表。
因此,沒有一個 ReligionTable 和一個 SmokingTable 和一個 GenderTable 等,而是只有帶有 CHECK 約束的描述列。這確實意味著如果您想向列表中添加額外的值,您將不得不修改檢查約束,但對於這些類型的表,它不應該經常發生。當您的查找表需要額外資訊時,您不想這樣做。例如,如果您的 SmokingTable 有一個“附加保險費用”列或類似的內容,那麼您需要一個單獨的查找表。
你的桌子看起來像這樣:
CREATE TABLE #Users ( UserId INT, Religion VARCHAR(50) CONSTRAINT ck_Users_Religion CHECK (Religion IN ('Christian','Jew','Budist')), SmokingPreference CHAR(3) CONSTRAINT ck_Users_Smoking CHECK (SmokingPreference IN ('YES','NO')) )