简体   繁体   English

在 SQL 中模拟标志枚举(c#)的最佳方法是什么?

[英]What's the best way to emulate a flags enum (c#) in SQL?

It was quite obvious to use bitwise operators for this, as that's basically what a flags enums uses internally.为此使用按位运算符是非常明显的,因为这基本上是标志枚举在内部使用的。 I've found a way to accomplish this:我找到了一种方法来实现这一点:

Edit: The previous query was wrong, and I think the question wasn't entirely clear.编辑:之前的查询是错误的,我认为这个问题并不完全清楚。 I'll provide some background first;我将首先提供一些背景;

We have an object that can have any of 20+ states.我们有一个 object 可以有 20 多个状态中的任何一个。 In order to prevent creating 20+ boolean columns in our table, we store the integer value of our flagged enum.为了防止在我们的表中创建 20+ boolean 列,我们存储了我们标记的枚举的 integer 值。

Now, to use this data in our templating system, we need an efficiënt way of querying the objects by their state.现在,要在我们的模板系统中使用这些数据,我们需要一种有效的方法来通过对象的 state 查询对象。

In the following example I'll query for all objects flagged as 'State_2'在以下示例中,我将查询所有标记为“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

As TomTom has noted, this doesn't make efficient use of indices, making this query pretty slow.正如 TomTom 所指出的,这并没有有效地利用索引,使得这个查询非常慢。

A solution to this problem could be to perform the bitwise query all possible options in memory, so we can make good use of indices:解决这个问题的方法是对 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)

But this has quite a lot over overhead.但这有很多开销。

Is there a more effective way to deal with this?有没有更有效的方法来解决这个问题?

EDIT #2: Venkataraman R's anwser made me realize that storing the actual flag value is a dumb idea, and we'll never be able to query this efficiëntly.编辑#2:Venkataraman R's anwser 让我意识到存储实际标志值是一个愚蠢的想法,我们永远无法有效地查询这个。

To solve this, we would need a relationship table, which links every state the object is in, to the object.为了解决这个问题,我们需要一个关系表,它将 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
)

I think this is the most efficiënt we'll be able to get it.我认为这是我们能得到的最有效的方法。

It was quite obvious to use bitwise operators for this,很明显,为此使用按位运算符,

Actually it is not.实际上并非如此。 It goes against anything SQL is, including efficient use of indices - something you generally do not care about on a in memory representation.它违背了 SQL 的任何内容,包括索引的有效使用——在 memory 表示中您通常不关心的事情。 What is obvious is using one boolean field per flag field.显而易见的是每个标志字段使用一个 boolean 字段。

I'm no database admin, so I was wondering我不是数据库管理员,所以我想知道

A database ADMIN deals with managing the database.数据库管理员负责管理数据库。 You are saying you are not a developer that delas with databases.您是说您不是使用数据库的开发人员。 Blaming not being an admin is like telling "I am not a mechanic, so I do not know the best way for my car from A to B".责备自己不是管理员就像是在告诉“我不是机械师,所以我不知道我的车从 A 到 B 的最佳方式”。 Admins MANAGE, Programmers develop.管理员管理,程序员开发。

As often in SQL how a programmer develops it depends on how the programmer uses it.正如 SQL 中经常出现的那样,程序员如何开发它取决于程序员如何使用它。 SQL will store multiple boolean fields internally as some sort of flag enum, but this allow one to set up various indices which any packed solution does not allow. SQL 将在内部存储多个 boolean 字段作为某种标志枚举,但这允许人们设置任何打包解决方案都不允许的各种索引。 And indices are a key predicament for fast filtering which does not require a table scan.索引是不需要表扫描的快速过滤的关键困境。

In more modern versions of SQL Server you COULD set up a packed field and a function to extract one value and index a field that is defined as using this function.在 SQL 服务器的更现代版本中,您可以设置一个打包字段和一个 function 以提取一个值并索引一个定义为使用此 ZC1C425268E68385D1AB5074C17A94F 的字段。

Bitwise based flags are only really of value if the object can have multiple states simultaneously.仅当 object 可以同时具有多个状态时,基于位的标志才真正有价值。

In this case, you either;在这种情况下,您要么;

  1. Store the sum of the bits into a single field in the DB.将位的总和存储到数据库中的单个字段中。

This has the advantage of storing just 1 field, but the disadvantage that every client must decode the value.这具有仅存储 1 个字段的优点,但缺点是每个客户端都必须解码该值。

  1. Store each state into a separate table, linked to the object.将每个 state 存储到一个单独的表中,链接到 object。

If you store it as a string, then this has the advantage that the data is easier decoded by other clients, easier to read for DB admins, report creators etc. (and those who might access the DB directly).如果将其存储为字符串,则其优点是数据更容易被其他客户端解码,数据库管理员、报告创建者等(以及可能直接访问数据库的人)更容易阅读。 Even is stored as a number, it is easier to read. Even 存储为数字,更易于阅读。

But if your enum just represents a single state, then there is no need for a Bitwise approach.但是,如果您的枚举仅代表单个 state,则不需要按位方法。

Then you can either;然后你可以;

  1. Store the value or string into a single field in the DB.将值或字符串存储到数据库中的单个字段中。

This is simple, but doesn't support DB normalisation principals.这很简单,但不支持 DB 规范化原则。 It is OK if you just store the state on a single Object type (table).如果您仅将 state 存储在单个 Object 类型(表)上,就可以了。

  1. Store the value or string into a separate table, link your object to this table via a foreign Key ID.将值或字符串存储到单独的表中,通过外键 ID 将 object 链接到此表。

This conforms to DB normalisation practices and allows for better extendability in the DB in the future if/when you add more enums.这符合数据库规范化实践,并允许将来在添加更多枚举时在数据库中获得更好的可扩展性。 And should be used if the enum is access from multiple Object types (tables).如果枚举是从多个 Object 类型(表)访问,则应使用该枚举。

Enum is basically a domain attribute.枚举基本上是一个域属性。 It is something similar to datatype, where you are specifying range of values.它类似于数据类型,您可以在其中指定值的范围。 Eg.TinyInt can have values from 1 to 255.例如,TinyInt 的值可以从 1 到 255。

In the case of Enum, you are specifying range of values for the Enum.在 Enum 的情况下,您正在为 Enum 指定值的范围。 Eg.例如。 EmployeeTypeEnum can have values: FullTimeEmployee, ContractEmployee EmployeeTypeEnum 可以有值:FullTimeEmployee、ContractEmployee

Below is the approach to handle enum types in SQL:下面是在 SQL 中处理枚举类型的方法:

In SQL, you have to create separate table for holding the enum在 SQL 中,您必须创建单独的表来保存枚举

EmployeeType员工类型

+----------------+------------------+
| EmployeeTypeId | EmployeeTypeName |
+----------------+------------------+
|              1 | FullTimeEmployee |
|              2 | ContractEmployee |
+----------------+------------------+

You need to define PRIMARY KEY for the identifier.您需要为标识符定义 PRIMARY KEY。

ALTER TABLE EmployeeType ADD CONSTRAINT PK_EmployeeType EmployeeType(EmployeeTypeId)

In the actual table, you should refer to this EmployeeType as foreign key to make sure that only the values in the domain are coming.在实际表中,您应该将此 EmployeeType 引用为外键,以确保只有域中的值来。

Employee员工

+------------+--------------+----------------+
| EmployeeId | EmployeeName | EmployeeTypeId |
+------------+--------------+----------------+
|          1 | Venkat       |              1 |
+------------+--------------+----------------+

You need to define FOREIGN KEY for the domain identifier.您需要为域标识符定义 FOREIGN KEY。

ALTER TABLE Employee ADD CONSTRAINT FK_Employee_EmployeeType FOREIGN KEY (EmployeeTypeId) REFERENCES EmployeeType(EmployeeTypeId)

UPDATE In C#, you get integer representation of enum更新在 C# 中,您会得到枚举的 integer 表示

int EmployeeType = (int) EmployeeEnum.Type;

In SQL, you pass this integer against the enum type, to get the corresponding value.在 SQL 中,你将这个 integer 传递给枚举类型,以获得相应的值。

SELECT EmployeeId, EmployeeName 
FROM Employee
Where EmployeeTypeId = @EmployeeType

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM