Skip to content

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

Битовые операции при работе с БД применяются редко. Тем не менее, работа с отдельными битами поддерживается в БД Oracle, и в некоторых случаях может быть использована в весьма элегантном виде.

Тестовые данные

sql
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);
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:

A011
B110
C010

Таким образом, чтобы убедиться, что интересующий нас набор бит (предположим, этот набор бит хранится в числе b) установлен в числе a, нужно применить побитовое "И" к числам a и b, и получившийся результат сравнить с числом b(еще это число называют битовой маской) - если результат совпал, значит, все биты, установленные в числе b установлены и в числе a.

В таблице числа мы храним в десятичной системе, и чтобы было проще ориентироваться, распишем, что означают текущие данные в таблице:

bit_accessдвоичное представлениеДоступ
0000Ничего нельзя делать
1001Чтение
3011Чтение и редактирование
7111Чтение, редактирование, удаление

BIN_TO_NUM

Эта функция принимает список нулей и единиц, превращая их в десятичное число:

sql
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
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
ABCD
0137

BITAND. Побитовое "И"

Функция BITAND выполняет побитовое "И" между двумя числами.

Выведем список всех документов, которые доступны для чтения:

sql
select *
from docs
where bitand(bit_access, 1) = 1
select *
from docs
where bitand(bit_access, 1) = 1
IDDOC_NUMBIT_ACCESS
22-11
43-13
54-17

Список всех документов, которые доступны для чтения и редактирования:

sql
select *
from docs
where bitand(bit_access, 3) = 3
select *
from docs
where bitand(bit_access, 3) = 3
IDDOC_NUMBIT_ACCESS
43-13
54-17

Чтобы было более наглядно и читаемо, последний запрос можно переписать с использованием фукнции bin_to_num:

sql
select *
from docs
where bitand(bit_access, bin_to_num(0,1,1)) = bin_to_num(0,0,1)
select *
from docs
where bitand(bit_access, bin_to_num(0,1,1)) = bin_to_num(0,0,1)

Для улучшения читаемости кода лучше использовать bin_to_num для записи битовых масок.

Выведем список всех документов, и добавим к выборке три колонки, каждая из которых будет отвечать за наличие доступа к определенному действию:

sql
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
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
IDDOC_NUMBIT_ACCESSREAD_ACCESSEDIT_ACCESSDELETE_ACCESS
11-10НетНетНет
22-11ДаНетНет
32-24НетНетДа
43-13ДаДаНет
54-17ДаДаДа