繁体   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