Как стать автором
Обновить

Упаковка булевых переменных для хранения и поиска в базе

Время на прочтение3 мин
Количество просмотров2.7K
Столкнулся с необходимостью хранить в базе формы, большая часть из которых являлась «галочками», то есть, по сути булевыми переменными. Создавать под тридцать галочек тридцать полей INT 1 не хотелось стал искать другой метод.

И нашел — битовый массив.
Точнее это не совсем битовый массив, это представление его в целом типе.
Сделал так:
— Все «галочки» пронумеровал от единицы примерно вот так:
<input type="checkbox" name="variable[]" value="1">
<input type="checkbox" name="variable[]" value="2">
<input type="checkbox" name="variable[]" value="3">
<input type="checkbox" name="variable[]" value="3">

— Далее — сделал две функции: упаковывающую массив в «битовый массив»:

 function binary_array_pack($array){
	$ret = 0;

// Нашумевший каламбур =)
	if (!is_array($array))
	    $array = array($array);	
		
	foreach ($array as $val){
		if ($val > 32 || $val < 1)
			throw new Exception("Значения не должны превышать 32 и быть меньше 1");
		$ret |= bindec('1'.str_repeat('0',$val-1));
	}
	return $ret;
}

function binary_array_unpack($val){
	$ret = array();
	for ($i=0; $i<32; $i++){
		if ($val & bindec(str_repeat('0',31-$i).'1'.str_repeat('0',$i))){
			$ret[] = $i;
		}
	}
	return $ret;
}


Итого, мы получаем следующее:
— Пользователь отметил, к примеру, первый, второй и четвёртый пункты
— Мы получили массив $variable = array(1,2,4);
— Далее, мы скормили этот массив функции binary_array_pack($variable)
— Функция вернула целое число, которое в двоичном представлении выглядит так — 1011, а в десятичном оно равно 11

Собственно, мы имеем, что i-1 й двоичный разряд показывает на наличие i-й «галочки». В данном случае у нас есть нулевой, первый и третий разряды, что означает наличие первой, второй и четвёртой галочки.

Теперь это число можно записать в одно поле базы.

Вопрос — как же теперь делать выборку по этим полям?
Ответ — очень просто. Нам помогут побитовые операции, которые поддерживаются (на сколько мне известно) всеми СУБД.

Например:
— Пользователь выбрал для поиска вторую галочку
— Мы запаковали её в наш как бы «битовый массив», получили число 2.
— Сделали запрос SELECT * FROM t WHERE field & 2 != 0
— То есть мы сделали побитовое умножение и, например, число 11, которое в двоичном представлении записывается как 1011 даст нам при умножении на 0010 число 0010. Если же в поле не было записана вторая «галочка», то эта операция даст 0. То же самое будет, если из множественного выбора не будет ни одного соответствия.

Для других видов поиска можно использовать другие побитовые операции.

Вот так можно сократить количество полей в базе без ущерба для функциональности.

По поводу производительности такой выборки точных данных [пока] нет (тесты последуют), хотя ИМХО, побитовые операции нативны для процессора и должны летать.

Приведённые функции лишь отражают суть подхода и, конечно, не претендуют на роль окончательной реализации. Все примечания и замечания приветствуются.

Основное ограничение этого подхода — максимум 64 «галочки» (разрядность BIGINT), а при данной реализации — максимум 32 «галочки» (максимум для функции php bindec).

UPD
Спасибо всем комментировавшим. Комментарии все по делу (к счастью).
Итого: Такой подход можно использовать для фиксирования множественного выбора (например, не известно заранее, сколько галок может поставить юзер, так что не понятно, сколько полей водить), но его использование приведёт к фулскану, что, естесвенно, гораздо хуже, чем обычная выборка по полям. Однако, это выведено из теории, экспериментов не ставилось =)
Теги:
Хабы:
Всего голосов 12: ↑6 и ↓60
Комментарии25

Публикации

Истории

Ближайшие события

2 – 18 декабря
Yandex DataLens Festival 2024
МоскваОнлайн
11 – 13 декабря
Международная конференция по AI/ML «AI Journey»
МоскваОнлайн
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань