Reputation: 600
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. In order to prevent creating 20+ boolean columns in our table, we store the integer value of our flagged enum.
Now, to use this data in our templating system, we need an efficiënt way of querying the objects by their state.
In the following example I'll query for all objects flagged as '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.
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:
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.
To solve this, we would need a relationship table, which links every state the object is in, to the 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.
Upvotes: 1
Views: 2039
Reputation: 13009
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.
In the case of Enum, you are specifying range of values for the Enum. Eg. EmployeeTypeEnum can have values : FullTimeEmployee, ContractEmployee
Below is the approach to handle enum types in SQL:
In SQL, you have to create separate table for holding the enum
EmployeeType
+----------------+------------------+
| EmployeeTypeId | EmployeeTypeName |
+----------------+------------------+
| 1 | FullTimeEmployee |
| 2 | ContractEmployee |
+----------------+------------------+
You need to define PRIMARY KEY for the identifier.
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.
Employee
+------------+--------------+----------------+
| EmployeeId | EmployeeName | EmployeeTypeId |
+------------+--------------+----------------+
| 1 | Venkat | 1 |
+------------+--------------+----------------+
You need to define FOREIGN KEY for the domain identifier.
ALTER TABLE Employee ADD CONSTRAINT FK_Employee_EmployeeType FOREIGN KEY (EmployeeTypeId) REFERENCES EmployeeType(EmployeeTypeId)
UPDATE In C#, you get integer representation of enum
int EmployeeType = (int) EmployeeEnum.Type;
In SQL, you pass this integer against the enum type, to get the corresponding value.
SELECT EmployeeId, EmployeeName
FROM Employee
Where EmployeeTypeId = @EmployeeType
Upvotes: 1
Reputation: 62157
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. What is obvious is using one boolean field per flag field.
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". Admins MANAGE, Programmers develop.
As often in SQL how a programmer develops it depends on how the programmer uses it. 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. 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.
Upvotes: 2
Reputation: 9650
Bitwise based flags are only really of value if the object can have multiple states simultaneously.
In this case, you either;
This has the advantage of storing just 1 field, but the disadvantage that every client must decode the value.
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.
But if your enum just represents a single state, then there is no need for a Bitwise approach.
Then you can either;
This is simple, but doesn't support DB normalisation principals. It is OK if you just store the state on a single Object type (table).
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).
Upvotes: 2