Используйте BLOB как тип данных и напишите функцию, которая будет:
- извлекать байт, который необходимо обновить
- изменить бит в байте
- вставить измененный байт в большой двоичный объект в исходной позиции
Вот одна реализация:
delimiter //
create function set_bit(b blob, pos int, val int) returns blob reads sql data
comment 'changes the bit at position <pos> (0: right most bit) to <val> in the blob <b>'
begin
declare len int; -- byte length of the blob
declare byte_pos int; -- position of the affected byte (1: left most byte)
declare bit_pos int; -- position within the affected byte (0: right most bit)
declare byte_val int; -- value of the affected byte
set len = length(b);
set byte_pos = len - (pos div 8);
set bit_pos = pos mod 8;
set byte_val = ord(substring(b, byte_pos, 1)); -- read the byte
set byte_val = byte_val & (~(1 << bit_pos)); -- set the bit to 0
set byte_val = byte_val | (val << bit_pos); -- set the bit to <val>
return insert(b, byte_pos, 1, char(byte_val)); -- replace the byte and return
end //
delimiter ;
Простой тест:
create table test(id int, b blob);
insert into test(id, b) select 1, 0x000000;
insert into test(id, b) select 2, 0xffffff;
У нас есть два битовые маски BLOB-объектов (по 3 байта в каждой) - один полный нулей и один полный единиц. В обоих случаях мы устанавливаем бит в позиции 10
(11-й бит справа) в 1
, а бит в позиции 11
(12-й бит справа) в 0
.
update test set b = set_bit(b, 10, 1);
update test set b = set_bit(b, 11, 0);
select id, hex(b), to_base2(b) from test;
Result :
| id | hex(b) | to_base2(b) |
| --- | ------ | -------------------------- |
| 1 | 000400 | 00000000 00000100 00000000 |
| 2 | FFF7FF | 11111111 11110111 11111111 |
Просмотр на БД Fiddle
Примечание: to_base2()
- это пользовательская функция, которая возвращает строку с битовым представлением BLOB и используется только
Работает как для MySQL 5.x, так и для 8.0.
Возможно встроить его в одном выражении (без необходимости использования функции) - Но это довольно нечитаемо:
update test t
cross join (select 10 as pos, 1 as val) i -- input
set t.b = insert(
t.b,
length(t.b) - (i.pos div 8),
1,
char(ord(
substring(t.b, length(t.b) - (i.pos div 8), 1))
& ~(1 << (i.pos mod 8))
| (i.val << (i.pos mod 8)
))
);
Просмотр на БД Fiddle
В MySQL 8.0 это немного проще, так как нам не нужно извлекать байт и может использовать битовые операции с BLOB-объектами. Но нам нужно убедиться, что опреранды имеют одинаковую длину:
update test t
cross join (select 10 as pos, 1 as val) i -- input
set t.b = t.b
& (~(concat(repeat(0x00,length(t.b)-1),char(1)) << i.pos))
| (concat(repeat(0x00,length(t.b)-1),char(i.val)) << i.pos)
Просмотр на БД Fiddle
Другой способ:
update test t
cross join (select 10 as pos, 1 as val) i -- input
set t.b =
case when i.val = 1
then t.b | concat(repeat(0x00,length(t.b)-1),char(1)) << i.pos
else t.b & ~(concat(repeat(0x00,length(t.b)-1),char(1)) << i.pos)
end
Просмотр на БД Fiddle