[英]What's the best way to emulate a flags enum (c#) in SQL?
為此使用按位運算符是非常明顯的,因為這基本上是標志枚舉在內部使用的。 我找到了一種方法來實現這一點:
編輯:之前的查詢是錯誤的,我認為這個問題並不完全清楚。 我將首先提供一些背景;
我們有一個 object 可以有 20 多個狀態中的任何一個。 為了防止在我們的表中創建 20+ boolean 列,我們存儲了我們標記的枚舉的 integer 值。
現在,要在我們的模板系統中使用這些數據,我們需要一種有效的方法來通過對象的 state 查詢對象。
在以下示例中,我將查詢所有標記為“State_2”的對象
-- Set up the table and fill it up with some example data
create table #ObjectsWithMultipleStates (Flag int, ObjectValue nvarchar(255))
insert into #ObjectsWithMultipleStates
values
(1, 'Object_1'),(2, 'Object_2'),(3, 'Object_3'),(4, 'Object_4'),(5, 'Object_5'),
(6, 'Object_6'),(7, 'Object_7'),(8, 'Object_8'),(9, 'Object_9'),(10, 'Object_10'),
(11, 'Object_11'),(12, 'Object_12'),(13, 'Object_13'),(14, 'Object_14'),(15, 'Object_15'),
(16, 'Object_16'),(17, 'Object_17'),(18, 'Object_18'),(19, 'Object_19'),(20, 'Object_20')
-- Example flag enum, which these values relate to
create table #States (Id int, [Name] nvarchar(255))
insert into #States values (1, 'State_1'),(2, 'State_2'),(4, 'State_3'),(8, 'State_4'),(16, 'State_5')
-- For this example, we'll get the enum's int value by its name
declare @FlagValue int = (select Id from #States where [Name] = 'State_2')
-- Returns 2, 3, 6, 7, 10, 11, 14, 15, 18, 19 (which seems about right)
select * from #ObjectsWithMultipleStates
where Flag|@FlagValue = Flag
正如 TomTom 所指出的,這並沒有有效地利用索引,使得這個查詢非常慢。
解決這個問題的方法是對 memory 中所有可能的選項進行按位查詢,這樣我們就可以充分利用索引:
select * from #ObjectsWithMultipleStates where Flag in (
-- This returns all possible flag combinations (would be wrapped in a UDF in reality)
select Val
from(
SELECT ones.n + 10*tens.n + 100*hundreds.n + 1000*thousands.n[Val]
FROM (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) ones(n),
(VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) tens(n),
(VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) hundreds(n),
(VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) thousands(n)
WHERE ones.n + 10*tens.n + 100*hundreds.n + 1000*thousands.n BETWEEN 1 AND POWER(2, (select count(*) from #States)) -1
) possibleValues
where Val|@FlagValue = Val)
但這有很多開銷。
有沒有更有效的方法來解決這個問題?
編輯#2:Venkataraman R's anwser 讓我意識到存儲實際標志值是一個愚蠢的想法,我們永遠無法有效地查詢這個。
為了解決這個問題,我們需要一個關系表,它將 object 所在的每個 state 鏈接到 object。
-- Note that I've removed the flags column, and added an Id from this example
create table #ObjectsWithMultipleStates (Id int, ObjectValue nvarchar(255))
insert into #ObjectsWithMultipleStates
values
(1, 'Object_1'),(2, 'Object_2'),(3, 'Object_3'),(4, 'Object_4'),(5, 'Object_5'),
(6, 'Object_6'),(7, 'Object_7'),(8, 'Object_8'),(9, 'Object_9'),(10, 'Object_10'),
(11, 'Object_11'),(12, 'Object_12'),(13, 'Object_13'),(14, 'Object_14'),(15, 'Object_15'),
(16, 'Object_16'),(17, 'Object_17'),(18, 'Object_18'),(19, 'Object_19'),(20, 'Object_20')
-- Example flag enum, which these values relate to
create table #States (Id int, [Name] nvarchar(255))
insert into #States values (1, 'State_1'),(2, 'State_2'),(4, 'State_3'),(8, 'State_4'),(16, 'State_5')
-- Example relationship table
create table #ObjectState(ObjectId int, StateId int)
insert into #ObjectState values
(1, 1), (2, 2), (3, 1), (3, 2) -- Etc.
declare @FlagValue int = (select Id from #States where [Name] = 'State_2')
-- Finally, we can perform a decent query
select * from #ObjectsWithMultipleStates where Id in (
select ObjectId from #ObjectState where StateId = @FlagValue
)
我認為這是我們能得到的最有效的方法。
很明顯,為此使用按位運算符,
實際上並非如此。 它違背了 SQL 的任何內容,包括索引的有效使用——在 memory 表示中您通常不關心的事情。 顯而易見的是每個標志字段使用一個 boolean 字段。
我不是數據庫管理員,所以我想知道
數據庫管理員負責管理數據庫。 您是說您不是使用數據庫的開發人員。 責備自己不是管理員就像是在告訴“我不是機械師,所以我不知道我的車從 A 到 B 的最佳方式”。 管理員管理,程序員開發。
正如 SQL 中經常出現的那樣,程序員如何開發它取決於程序員如何使用它。 SQL 將在內部存儲多個 boolean 字段作為某種標志枚舉,但這允許人們設置任何打包解決方案都不允許的各種索引。 索引是不需要表掃描的快速過濾的關鍵困境。
在 SQL 服務器的更現代版本中,您可以設置一個打包字段和一個 function 以提取一個值並索引一個定義為使用此 ZC1C425268E68385D1AB5074C17A94F 的字段。
僅當 object 可以同時具有多個狀態時,基於位的標志才真正有價值。
在這種情況下,您要么;
這具有僅存儲 1 個字段的優點,但缺點是每個客戶端都必須解碼該值。
如果將其存儲為字符串,則其優點是數據更容易被其他客戶端解碼,數據庫管理員、報告創建者等(以及可能直接訪問數據庫的人)更容易閱讀。 Even 存儲為數字,更易於閱讀。
但是,如果您的枚舉僅代表單個 state,則不需要按位方法。
然后你可以;
這很簡單,但不支持 DB 規范化原則。 如果您僅將 state 存儲在單個 Object 類型(表)上,就可以了。
這符合數據庫規范化實踐,並允許將來在添加更多枚舉時在數據庫中獲得更好的可擴展性。 如果枚舉是從多個 Object 類型(表)訪問,則應使用該枚舉。
枚舉基本上是一個域屬性。 它類似於數據類型,您可以在其中指定值的范圍。 例如,TinyInt 的值可以從 1 到 255。
在 Enum 的情況下,您正在為 Enum 指定值的范圍。 例如。 EmployeeTypeEnum 可以有值:FullTimeEmployee、ContractEmployee
下面是在 SQL 中處理枚舉類型的方法:
在 SQL 中,您必須創建單獨的表來保存枚舉
員工類型
+----------------+------------------+
| EmployeeTypeId | EmployeeTypeName |
+----------------+------------------+
| 1 | FullTimeEmployee |
| 2 | ContractEmployee |
+----------------+------------------+
您需要為標識符定義 PRIMARY KEY。
ALTER TABLE EmployeeType ADD CONSTRAINT PK_EmployeeType EmployeeType(EmployeeTypeId)
在實際表中,您應該將此 EmployeeType 引用為外鍵,以確保只有域中的值來。
員工
+------------+--------------+----------------+
| EmployeeId | EmployeeName | EmployeeTypeId |
+------------+--------------+----------------+
| 1 | Venkat | 1 |
+------------+--------------+----------------+
您需要為域標識符定義 FOREIGN KEY。
ALTER TABLE Employee ADD CONSTRAINT FK_Employee_EmployeeType FOREIGN KEY (EmployeeTypeId) REFERENCES EmployeeType(EmployeeTypeId)
更新在 C# 中,您會得到枚舉的 integer 表示
int EmployeeType = (int) EmployeeEnum.Type;
在 SQL 中,你將這個 integer 傳遞給枚舉類型,以獲得相應的值。
SELECT EmployeeId, EmployeeName
FROM Employee
Where EmployeeTypeId = @EmployeeType
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.