Битовые операции
Битовые операции при работе с БД применяются редко. Тем не менее, работа с отдельными битами поддерживается в БД Oracle, и в некоторых случаях может быть использована в весьма элегантном виде.
Тестовые данные
create table docs(
id number not null primary key,
doc_num varchar2(100 char) not null,
bit_access number default 0 not null
);
comment on table docs is 'Документы';
comment on column docs.bit_access is 'Уровни доступа(1 бит - чтение, 2 - редактирование, 3 - удаление)';
insert into docs
values(1, '1-1', 0);
insert into docs
values(2, '2-1', 1);
insert into docs
values(3, '2-2', 4);
insert into docs
values(4, '3-1', 3);
insert into docs
values(5, '4-1', 7);Колонка bit_access хранит в себе число, каждый бит которого отвечает за наличие(1) или отсутствие(0) доступа на произведение операций с данной строкой таблицы(документом). То есть, если в числе, находящемся в колонке bit_access, первый бит равен 1, это означает, что данную запись можно показывать пользователю. Если второй бит равен 1, то данную строку можно редактировать, а если третий бит установлен в 1, то данную строку можно удалять. Если мы представим наше число в виде трех бит, оно будет иметь вид 000. Порядок бит в записи числа как правило идет справа налево, т.е. если в числе установлен первый бит в 1, то оно записывается как 001, если второй, то 010.
При этом доступ может быть комбинированным - мы можем иметь доступ и на просмотр информации по документу, и на удаление самого документа, или, скажем, на просмотр и редактирование, но не на удаление. В таких случаях в нашем числе несколько бит числа будут установлены в 1(Само число в двоичной записи будет 101 в первом случае и 011 во втором).
Подобным образом можно закодировать несколько логических переменных в одно число, в котором каждый бит будет отвечать за соответствующее условие.
Для того, чтобы проверить, установлен ли определенный бит числа в 1, применяется операция, которая называется побитовой конъюнкцией, или побитовым "И". Результатом побитового "И" между числами a и b будет такое число c, у которого в 1 будут установлены только те биты, которые установлены в 1 как в a, так и в b. Вот как это будет выглядеть, если a= 011, а b=110:
| A | 0 | 1 | 1 |
|---|---|---|---|
| B | 1 | 1 | 0 |
| C | 0 | 1 | 0 |
Таким образом, чтобы убедиться, что интересующий нас набор бит (предположим, этот набор бит хранится в числе b) установлен в числе a, нужно применить побитовое "И" к числам a и b, и получившийся результат сравнить с числом b(еще это число называют битовой маской) - если результат совпал, значит, все биты, установленные в числе b установлены и в числе a.
В таблице числа мы храним в десятичной системе, и чтобы было проще ориентироваться, распишем, что означают текущие данные в таблице:
| bit_access | двоичное представление | Доступ |
|---|---|---|
| 0 | 000 | Ничего нельзя делать |
| 1 | 001 | Чтение |
| 3 | 011 | Чтение и редактирование |
| 7 | 111 | Чтение, редактирование, удаление |
BIN_TO_NUM
Эта функция принимает список нулей и единиц, превращая их в десятичное число:
select bin_to_num(0,0,0) a,
bin_to_num(0,0,1) b,
bin_to_num(0,1,1) c,
bin_to_num(1,1,1) d
from dual| A | B | C | D |
|---|---|---|---|
| 0 | 1 | 3 | 7 |
BITAND. Побитовое "И"
Функция BITAND выполняет побитовое "И" между двумя числами.
Выведем список всех документов, которые доступны для чтения:
select *
from docs
where bitand(bit_access, 1) = 1| ID | DOC_NUM | BIT_ACCESS |
|---|---|---|
| 2 | 2-1 | 1 |
| 4 | 3-1 | 3 |
| 5 | 4-1 | 7 |
Список всех документов, которые доступны для чтения и редактирования:
select *
from docs
where bitand(bit_access, 3) = 3| ID | DOC_NUM | BIT_ACCESS |
|---|---|---|
| 4 | 3-1 | 3 |
| 5 | 4-1 | 7 |
Чтобы было более наглядно и читаемо, последний запрос можно переписать с использованием фукнции bin_to_num:
select *
from docs
where bitand(bit_access, bin_to_num(0,1,1)) = bin_to_num(0,0,1)Для улучшения читаемости кода лучше использовать bin_to_num для записи битовых масок.
Выведем список всех документов, и добавим к выборке три колонки, каждая из которых будет отвечать за наличие доступа к определенному действию:
select id,
doc_num,
bit_access,
case
when bitand(bit_access,
bin_to_num(0,0,1)) = bin_to_num(0,0,1) then 'Да'
else 'Нет'
end read_access,
case
when bitand(bit_access,
bin_to_num(0,1,0)) = bin_to_num(0,1,0) then 'Да'
else 'Нет'
end edit_access,
case
when bitand(bit_access,
bin_to_num(1,0,0)) = bin_to_num(1,0,0) then 'Да'
else 'Нет'
end delete_access
from docs| ID | DOC_NUM | BIT_ACCESS | READ_ACCESS | EDIT_ACCESS | DELETE_ACCESS |
|---|---|---|---|---|---|
| 1 | 1-1 | 0 | Нет | Нет | Нет |
| 2 | 2-1 | 1 | Да | Нет | Нет |
| 3 | 2-2 | 4 | Нет | Нет | Да |
| 4 | 3-1 | 3 | Да | Да | Нет |
| 5 | 4-1 | 7 | Да | Да | Да |