Форум программистов
 

Восстановите пароль или Зарегистрируйтесь на форуме, о проблемах и с заказом рекламы пишите сюда - alarforum@yandex.ru, проверяйте папку спам!

Вернуться   Форум программистов > Скриптовые языки программирования > PHP
Регистрация

Восстановить пароль
Повторная активизация e-mail

Купить рекламу на форуме - 42 тыс руб за месяц

Ответ
 
Опции темы Поиск в этой теме
Старый 22.04.2011, 02:34   #1
AlexanderGalkin
Пользователь
 
Регистрация: 17.04.2011
Сообщений: 11
Лампочка Хранение логических переменных в одном числе

Решил поделиться одной идеей по хранению нескольких значений в одной переменной.

Задача заключалась в сохранении / считывании различных настроек пользователя, которые являлись логическими переменными (т.е. выбор заключался только в “да” либо “нет”). Значений было порядка 20-ти, а поэтому желания хранить каждое значение в отдельном столбце таблицы базы данных не было (таблица на тот момент уже содержала много столбцов, а создавать отдельную для настроек желания так же не было).

Решение пришло неожиданно. В памяти всплыло уже найденное решение, используемое для указания прав на файлы в UNIX-подобных системах. Так три числа 6 / 4 / 4 содержат информацию о правах для владельца файла / группы / других (110 / 100 / 100).

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

00100000000000000001 (2) = 131073 (10)

Слева показано число в двоичной системе (20 значений, каждое из которых может быть “0” или “1”), а справа - то же число но в десятичной системе для хранения в базе.

Чтобы было нагляднее, далее приведен пример реализации решения при работе с PHP и MySQL.

Структупра MySQL базы данных:

Код:
DROP TABLE IF EXISTS `db_users`.`table_users`;
CREATE TABLE  `db_users`.`table_users` (
  `user_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `user_options` bigint(20) unsigned NOT NULL,
  `user_name` varchar(45) NOT NULL,
  PRIMARY KEY (`user_id`)
) ENGINE=MyISAM AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
При использовании BIGINT типа для поля user_options, возможно хранить до 64 параметров пользователя в одной переменной.

Нумерацию параметров будем вести с конца, т.е. если у пользователя 1-я, 3-я и 6-я опции выбраны, тогда значение будет следующим:

000100101 - значение user_options в двоичной системе
987654321 - порядковый номер

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

//$user_id - ID пользователя, для которого ищется значение параметров
//функция возвращает десятичное число, которое содержит значения всех параметров пользователя
//если пользователя такого нет - тогда функция вернёт false

PHP код:
function getIntOptions($user_id){
    
$query 'SELECT user_options FROM table_users WHERE user_id='.$user_id.';';
    
$result mysql_query($query);
    if(
mysql_num_rows($result)>0){
        
$inf mysql_fetch_assoc($result);
        return 
$inf['user_options'];
    }else{
        return 
false;
    }


затем функцию нахождения значения для указанного параметра

//$opt_ind - порядковый номер параметра (отсчет справа),
//$int_val - десятичное значение, найденное функцией getIntOptions($user_id)
//фунция возвращает значение указанного параметра ("0" или "1")

function getOptionFromInt($opt_ind, $int_val) {
$cur_options = decbin($int_val);
if ((strlen($cur_options) - $opt_ind) < 0) {
$cur_val = 0;
} else {
$cur_val = substr($cur_options, strlen($cur_options) - $opt_ind, 1);
}
return $cur_val;
}


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

PHP код:
$user_id 1//ID пользователя
$int_options getIntOptions($user_id);
echo 
'Option 1 = '.getOptionFromInt(1$int_options).' <br/>';
echo 
'Option 2 = '.getOptionFromInt(2$int_options).' <br/>'

Для указания и сохранения параметра используем функцию ниже:

//$user_id - ID пользователя, $opt_ind - порядковый номер (справа) параметра в опциях, $val - значение, которое нужно присвоить параметру ("0" либо "1")
//функция возвращает true при успешном присвоении; в противном случае - false

PHP код:
function setOptionValue($user_id$opt_ind$val){
    
//находим текущее значение
    
$query 'SELECT user_options FROM table_users WHERE user_id='.$user_id.';';
    
$result mysql_query($query);
    if(
mysql_num_rows($result)>0){
        
$inf mysql_fetch_assoc($result);
        
$int_val $inf['user_options'];
        
$cur_val getOptionFromInt($opt_ind$int_val);
        if (
$cur_val != $val){
            if(
$val == 1){
                
$new_val $int_val pow(2,$opt_ind-1);
            }else{
                
$new_val $int_val pow(2,$opt_ind-1);
            }
            
$query 'UPDATE table_users SET user_options='.$new_val.' WHERE user_id='.$user_id.';';
            
$result mysql_query($query);
            if(
$result){
                
//при успешном изменении возвращаем true
                
return true;
            }else{
                return 
false;
            }
        }else{
            
//если текущее совпадает с тем, которое нужно присвоить - возвращаем true
            
return true;
        }
    }else{
        return 
false;
    }

AlexanderGalkin вне форума Ответить с цитированием
Старый 22.04.2011, 02:35   #2
AlexanderGalkin
Пользователь
 
Регистрация: 17.04.2011
Сообщений: 11
По умолчанию

---

Для проверки параметра пользователя непосредственно в запросе MySQL можно использовать следующее условие:

Код:
IF(LENGTH(BIN(N))<K, 0,  LEFT(RIGHT(BIN(N),K),1))
где N - значение десятичное, хранимое в базе, а K - порядковый номер параметра, который необходимо проверить

Например, результатом следующей операции

Код:
IF(LENGTH(BIN(4))<2, 0,  LEFT(RIGHT(BIN(4),2),1)) будет 0, а
IF(LENGTH(BIN(4))<3, 0,  LEFT(RIGHT(BIN(4),3),1)) вернёт 1
Для того, чтобы вывести список всех пользователей, у которых 9й параметр равен единице, можно использовать следующий запрос:

Код:
SELECT * FROM table_users WHERE IF(LENGTH(BIN(table_users.user_options))<9, 0,  LEFT(RIGHT(BIN(table_users.user_options),9),1))=1;

P.S. Описанные функции и запросы были протестированы, проблем не встречал.

P.P.S. Если значения не только “да” / “нет” (1 / 0), а к примеру 3-4 варианта для одной опции, тогда вполне можно использовать ту же логику но с несколькими параметрами (для одного параметра будет уделяться несколько ячеек).
AlexanderGalkin вне форума Ответить с цитированием
Старый 22.04.2011, 08:23   #3
ADSoft
Старожил
 
Регистрация: 25.02.2007
Сообщений: 4,160
По умолчанию

Идея не нова.... упаковка параметров в числа итд ... часто встречается в ассабмлере ))))) где нжна такая оптимизация. В случае с Мускулем при современных технологиях - на экономию в 10-ки и сотни байт вряд ли кто-то обратит внимание, особенно если извлечение этих параметров столь ресурсоемко.
А так - имеет место жить такой способ, единствоенное как у него с расширяемостью? например не 64 понадобилось а 164 параметра? - как бестро и безболезнено поменять в коде запросы, а в базе значения?
ADSoft вне форума Ответить с цитированием
Старый 22.04.2011, 09:31   #4
Виталий Желтяков
Старожил
 
Аватар для Виталий Желтяков
 
Регистрация: 19.04.2010
Сообщений: 2,702
По умолчанию

AlexanderGalkin, убейте себя об стену.
Сначала Я подумал, что Вы взяли этот пример с Хабра (там его какой-то мудак представил как гениальное решение), но оказывается к такому решению могут прийти и обычные программисты.

Проблема данного метода в том, что основан на идиотском представлении о хранении данных в БД. Вышеперечисленная задача легко и оптимально решается при помощи сессий и сериализации.
Виталий Желтяков вне форума Ответить с цитированием
Старый 22.04.2011, 12:03   #5
AlexanderGalkin
Пользователь
 
Регистрация: 17.04.2011
Сообщений: 11
По умолчанию

Не исключал такой реакци)
Просто хотел поделиться соображениями. Не судите строго.
AlexanderGalkin вне форума Ответить с цитированием
Старый 22.04.2011, 12:07   #6
AlexanderGalkin
Пользователь
 
Регистрация: 17.04.2011
Сообщений: 11
По умолчанию

ADSoft, для большего количества значений я делал отдельный столбец в базе (например, user_options_email), при этом стоит всего лишь подкорректировать функцию getOptionFromInt для указания параметра, с какой колонкой в таблице работаете.

Последний раз редактировалось AlexanderGalkin; 22.04.2011 в 12:09.
AlexanderGalkin вне форума Ответить с цитированием
Старый 22.04.2011, 12:11   #7
ssdm
Форумчанин
 
Регистрация: 20.05.2009
Сообщений: 506
По умолчанию

Сам метод - не плох.
Но в контексте вашей задачи , имхо не удачен. Так как надо ведь ещё и хранить соответствие разряда числа- опция в настройке юзера.

Последний раз редактировалось ssdm; 22.04.2011 в 13:16.
ssdm вне форума Ответить с цитированием
Старый 22.04.2011, 12:18   #8
ssdm
Форумчанин
 
Регистрация: 20.05.2009
Сообщений: 506
По умолчанию

AlexanderGalkin
Не лучше ли собирать ассоциативный массив из настроек, сериализировать его сохранять в бд ?
ssdm вне форума Ответить с цитированием
Старый 22.04.2011, 12:34   #9
Виталий Желтяков
Старожил
 
Аватар для Виталий Желтяков
 
Регистрация: 19.04.2010
Сообщений: 2,702
По умолчанию

Этот метод крайне плох.

Во-первых, сохранённые по отдельности значения в поля byte(1) будут занимать меньше места, чем строка данных. И работать с ними можно будет напрямую. Где экономия? не понятно.

Во-вторых, MySQL работает нормально с таблицами, где много полей. У меня в проекте есть таблицы, где почти сотня полей - снижения скорости никакой.

В-третьих, насколько Я понимаю, всё упирается в желание запихнуть в одну строку. Так создайте массив настроек, сериализуйте его и сохраняйте. При инициализации системы вытаскивайте его и помещайте в сессию, что бы в дальнейшем не обращаться к базе.
Хотя лучше разбить на отдельные поля.
Виталий Желтяков вне форума Ответить с цитированием
Старый 22.04.2011, 14:24   #10
AlexanderGalkin
Пользователь
 
Регистрация: 17.04.2011
Сообщений: 11
По умолчанию

По поводу сериализации согласен - метод хорош. Но есть необходимость администрирования базы. А по сему подскажите пожалуйста как построить запрос к базе в случае хранения ассоциативного массива в сериализированной строке?

Скажем, есть ID пользователя и нужно проверить что первая опция и пятая равны единице.

По поводу работы MySQL c большим количеством столбцов - я не говорил что это плохо, либо есть недостатки. Но ИМХО - мне удобнее работать с обним столбцом, а не с 50-тью.

И ещё один вопрос:

Цитата:
Во-первых, сохранённые по отдельности значения в поля byte(1) будут занимать меньше места, чем строка данных
- насколько я знаю BIGINT имеет размер 8 байт. При хранении 50-ти значений мы используем те же 8 байт. При 50-ти byte(1) - мы используем 50 байт. Разве не так?
AlexanderGalkin вне форума Ответить с цитированием
Ответ


Купить рекламу на форуме - 42 тыс руб за месяц



Похожие темы
Тема Автор Раздел Ответов Последнее сообщение
Хранение БД в одном файле D_E_N БД в Delphi 3 18.07.2009 14:01
изменение нескольких переменных в одном методе. goog Общие вопросы по Java, Java SE, Kotlin 5 01.03.2009 20:46
Прибавление логических функций Slavik Microsoft Office Excel 19 26.01.2009 23:39
Использование логических функций Клубничка Microsoft Office Excel 52 15.01.2009 15:01
Свой тип данных в Delphi - сверх длинные числа - хранение в переменных размером до 1 MB KLaiM Общие вопросы Delphi 9 16.06.2007 09:13