簡體   English   中英

在 SQL 中模擬標志枚舉(c#)的最佳方法是什么?

[英]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. 將位的總和存儲到數據庫中的單個字段中。

這具有僅存儲 1 個字段的優點,但缺點是每個客戶端都必須解碼該值。

  1. 將每個 state 存儲到一個單獨的表中,鏈接到 object。

如果將其存儲為字符串,則其優點是數據更容易被其他客戶端解碼,數據庫管理員、報告創建者等(以及可能直接訪問數據庫的人)更容易閱讀。 Even 存儲為數字,更易於閱讀。

但是,如果您的枚舉僅代表單個 state,則不需要按位方法。

然后你可以;

  1. 將值或字符串存儲到數據庫中的單個字段中。

這很簡單,但不支持 DB 規范化原則。 如果您僅將 state 存儲在單個 Object 類型(表)上,就可以了。

  1. 將值或字符串存儲到單獨的表中,通過外鍵 ID 將 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM