Битовые операции

Битовые операции при работес БД применяются редко. Тем не менее, работа с отдельными битами поддерживается в БД 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 | Да          | Да          | Да            |

Комментарии