Pull to refresh

SQL и флаги

Reading time3 min
Views11K
Конечно же речь пойдёт не о режиме игры Capture The Flag на сервере SQL, а об использования bit флагов. Битовые операции знакомы, наверное, всем, кто занимается панорамированием, независимо от среды и языка разработки. Однако использование флагов, на мой взгляд, для многих является экзотикой нежели повседневным инструментом. На Хабре не раз упоминали возможность удобную возможность .NET работать с флагами через enum, но ведь и SQL даёт нам отличные возможности для использования флагов!

И так, рассмотрим простой пример — в некой аппликации должна быть некая система оповещения пользователей. Допустим вы строите форум и хотите дать возможность пользователю получать оповещения по почте: новый ответ в избранной теме, новое личное сообщение, новости форума.Беглый взгляд на задачу даст тривиальный дизайн таблиц:
tblUsers {userID (PK) as int, name as nvarchar(50), password as nvarchar(50)}
tblUserAlerts {userID (FK) as int, alertID (FK) as int}
tblAlerts{alertID (PK) as int, message as nvarchar(50)}


То есть существует таблица пользователей, таблица оповещений и связь между ними реализуется через вспомогательную таблицу дабы дать возможность каждому пользователю выбрать больше чем одно оповещение. Данные будут выглядеть примерно так:
tblUsers
1, «Вася», «хитрыйп4р0ль»

tblUserAlerts
1, 1

tblAlerts
1, «Вам пришло новое личное сообщение»

А теперь рассмотрим пример с пользованием флага:
tblUsers {userID (PK) as int, name as nvarchar(50), password as nvarchar(50), alerts as int},
tblAlerts{alertID (PK) as int, message as nvarchar(50)}

В данном варианте мы получаем ту же функциональность и при этом обходимся без дополнительной таблицы. В таблице tblAlerts мы задаём alertID как бит флаг с помощью то го же int (размер зависит от количества вариантов оповещений), а в таблице tblUsers поле alerts отображает бит маску оповещений. Допустим мы создаём 3 вида оповещений, значит в таблице tblAlerts будет 4 (нет оповещений + упомянутые 3 вида) строки флагами в поле alertID. Первым ID будет 0 — в битах будет выглядеть как 0000, значение, соответственно — нет оповещений. Затем мы добавляем наши оповещения зажигая каждый раз другой бит (каждый идентификатор будет степенью двойки ): 0001 = 1, 0010 = 2, 0100 = 4:
tblAlerts
0, NULL
1, «Новый ответ в избранной теме»
2, «Вам пришло новое личное сообщение»
4, «У нас новость!»

Теперь нам надо подписать пользователя, допустим, на новости и на оповещения о личных сообщения. Для этого мы суммируем идентификаторы этих оповещений и получаем 0110 = 6:
tblUsers
1, «Вася», «хитрыйп4р0ль», 6

Теперь построим запрос в таблицу tblUsers, чтобы узнать кто из пользователей подписан на новости:
SELECT * FROM tblUsers WHERE (tblUsers.alert & 4) > 0

Смысл данной операции прост:
0110 — наша маска
0100 — флаг новостей
0100 — результат ( то есть есть есть пересечение маски и флага, то результат будет больше нуля )

Приятный аспект — можно сравнивать и маски. Допустим мы хотим получить список пользователей подписанных на все виды оповещений 1+2+4 = 7:
SELECT * FROM tblUsers WHERE (tblUsers.alert & 7) = 7

Заметьте, что между маской 6 и 7 есть пересечение, но результат будет отличным от 7
0110 — маска пользователя
0111 — маска проверки
0110 — результат (пересечение или минимум из 7 и 6 = 6)

С той же лёгкостью можно сделать запрос на список пользователей подписанных либо на оповещение об ответах либо на уведомление о личных сообщениях 1+2 = 3:
SELECT * FROM tblUsers WHERE (tblUsers.alert & 3) > 0

Положительным результатом будет считаться проверка маски пользователя со значениями и 1, и 2, и 3 (то есть либо одно из двух оповещений, либо оба).

Вот таким нехитрым образом можно избавиться от связующей таблицы и даже упростить запросы. Однако минусом может стать не возможность сделать некоторые действия ( допустим JOIN ) через стандартный визуальный редактор запросов, так как стандарт будет сравнивать идентификаторы. Надо будет в ручную заменять сравнение на битовую операцию:
SELECT tblUsers.*, tblAlerts.*
FROM tblUsers INNER JOIN tblAlerts ON (tblUsers.alerts & tblAlerts.alertID) = tblAlerts.alertID
Tags:
Hubs:
Total votes 5: ↑4 and ↓1+3
Comments22

Articles