[英]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.