Хранение набора чекбоксов в одном поле БД. Битовая маска.

    Часто при составлении сайтов с каталогами нам приходится оперировать с однотипными карточками каталога. Рассмотрим пример:
    У нас имеется каталог СТО которые оказывают различные услуги клиентам. Есть предопределенный список услуг, среди которых производится выбор. Список услуг представляет собой набор чекбоксов, если услуга оказывается то чекбокс помечается, в противоположном случае отметка не ставится. Предположим количество услуг в каталоге равняется десяти(просто для определенности).
    Самым простым способом было бы хранение состояния каждого чекбокса в отдельном поле, однако при этом мы для каждой новой услуги(которая будет указываться на сайте и учавствовать в поиске) будем вынуждены расширять таблицу, и изменять поиск.
    Но мы пойдем другим путем.

    В ходе обсуждения среди разрабтчиков возникла идея хранить состояние всех чекбоксов в одном поле. Поскольку чекбоксы могут принимать только два состояния(1(флаг поставлен) и 0(флаг не поставлен)), получается простая битовая логика. В итоге мы можем хранить в mysql в поле целого типа 32 чекбокса(при условии что поле может содержать 32-х битные значения). При сохранении значения формы мы просто берем значение чекбокса(если оно не нулевое)смещаем его на номер чекбокса влево (нумерация чекбоксов начинается с нуля(как и нумерация бит)), полученное значение соедияем с полученным на предыдущем шаге с помощью побитовой операции OR. Пример кода на php:
    <?
    //Количество полей
    $n=10;
    //Результирующее значение
    $resultValue=0;
    for($i=0;$i<$n;++$i){
      if(!empty($formValue[$i])){
        $resultValue|=$formValue[$i]<<$i;
      }
    }
    ?>
    


    При извлечении данных мы выполняем обратное преобразование для получения исходных данных.
    Однако простого хранения и извлечения данных нам недостаточно, очень бы хотелось сделать еще и поиск. При этом поиск может быть строгим и не строгим. В первом случае надо выбрать все карточки каталога, в которых есть все отмеченные позиции, в втором случае только те карточки, в которых есть хотя бы одна отмеченная позиция.
    Mysql позволяет выполнять побитовые операции прямо в запросе. В результате мы получим следующий запрос, где n это число, которое получается в результате упаковки значений чекбоксов, которое мы с помощью побитовой операции AND накладываем на поле с данными(предполагается что упакованные данные хранятся в поле services):
    SELECT * FROM `dataTable` WHERE `services` & n = n

    Если поиск строгий, то в итоге нам нужны записи, у которых после выполнения побитовых операций над полем services результат будет равен нашему числу. Если поиск нестрогий, то запрос будет таким:
    SELECT * FROM `dataTable` WHERE `services` & n > 0

    В этому случае нам нужны записи, у которых результат данной операции будет больше нуля.
    Хотелось бы отметить что в данном виде можно хранить не только набор чекбоксов.
    P.S. Данный материал не претендует на истину в последней инстанции и является концептуальным(хотя уже использовался на нескольких проектах)
    UPD. По просьбе пользователя Roxis изменено название
    Поделиться публикацией

    Похожие публикации

    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 64

      +2
      Правильное название: Битовая маска
        +5
        SET?
          +2
          что — SET?
            +1
            Использовать тип SET.
              0
              мне кажется, битовая маска проще будет — у нас у поля может быть только два состояния, 0 и 1
              возможно я не прав
                +6
                SET в MySQL тоже хранится как битовая маска.
                  0
                  Чем это она проще? Необходимостью дописывать то, что уже было написано до вас?

                  Для подобных ситуаций используется либо дополнительная таблица, либо SET. Остальное — от лукавого :)
                    0
                    так ничего же дописывать не надо
                      0
                      Как ничего? А приведённый в тексте код?
                      0
                      хм, как это реализуется при использовании SET?
                        0
                        Получить список меток — простая выборка, не нужно потом переводить в человеко-понятный вид.

                        Хотя в целом, что SET, что INT при использовании в этом контексте имеют кучу минусов. SET — это именнованные списки, реализованные как гибкий INT (размер поля зависит от количества элементов), и унаследовавший все его недостатки.
                    • НЛО прилетело и опубликовало эту надпись здесь
                +3
                Как-то баловался таким-же способом хранения аттрибутов записи в блоге, появились проблемы с производительностью. Помоему в этом случае индексы не используются.
                  +2
                  Ценное наблюдение, проверю по поводу индексов.
                    0
                    EXPLAIN в mysql говорит что possible keys null, однако в графе key стоит название используемого индекса, а также указана его длина.
                      0
                      Не совсем так. Индекс можно сделать, но он будет полезен при выборке записей с идентичными флагами. А, скажем, если надо выбрать записи, у которых установлен некий бит, а значения остальных не важно — тогда для каждой записи нужно выполнить операцию по выделению значения этого бита, а потом сравнение, что будет явно медленней поиска по индексу. В общем, как мне кажется, удобства от использования битовых масок сомнительные.
                        0
                        У нас так и сделано — проблема с индексацией есть, выявилась тогда, когда менять стало поздно. В следующей версии движка решили от такого откреститься.
                          0
                          вообще индекс использован не будет ибо: `services` & n высчитываемое значение, но похоже, что если поразмыслить, то можно эту операцию разбить на индексируемые, хотя не уверен.
                      –1
                      а не проще было хранить строку с отмеченными id чекбоксов и разделяя их запятыми? ваш метод тоже конечно интересный, но вот тестировать легче со строкой.
                        +2
                        В этом случае поиск придется вести LIKE '%, id,%', при этом не будут использоваться индексы.
                          +1
                          если придется подобное делать, учту. спасибо :)
                            0
                            Можно ещё и через FIND_IN_SET(), только на счёт производительности ничего сказать не могу.
                              +2
                              в данной реализации (field & n) индексы использоваться так же не будут
                            +2
                            Мне кажется или что-то такое уже предлагали?
                              0
                              Поиском я не нашел, буду благодарен, если укажете мне на статью, в которой это уже предлагалось.
                                +1
                                Предлагал coylOne, но потом заметку удалил.
                                  0
                                  Благодарю.
                                +5
                                сравнивали скорость поиска и внесения изменений?
                                скорость извлечения?
                                какие результаты? — сколько процентов дискового пространства экономится применительно к базе, где это применялось и какой проигрыш(выигрыш?) в скорости? как увеличивается нагрузка на базу?
                                  +4
                                  наитипичнейшая схема «многие ко многим». Со всеми вытекающими. В большинстве случаев отдельной таблицы связей достаточно.
                                    0
                                    что вы, нормализация нынче не в моде.
                                      0
                                      у себя в проекте не выпендривался и так и сделал, плюс от использования индексов очевиден, а на лишний гиг-два места наплевать.
                                      +6
                                      А как же первая нормальная форма БД?
                                      Ещё, средствами SQL просматривать таблички будет проблематично…
                                        +6
                                        > Список услуг представляет собой набор чекбоксов, если услуга оказывается то чекбокс помечается, в противоположном случае отметка не ставится. Предположим количество услуг в каталоге равняется десяти(просто для определенности).

                                        У вас уже грубейшая ошибка в проектировании. Услуг уже завтра будет 11, а верез неделю 15. И вы 2 недели потеряете на перекраивание приложения, которое работает с вашей базой.
                                          0
                                          полностью поддерживаю, единственное проблемы возникают не при увеличении а при уменьшении кол-ва услуг.
                                          Кстати, чем не угодила дефакто используемая при таких операциях связь многие ко многим?
                                          0
                                          А не будет ли проще использовать для хранения настроек т.н. повернутые на 90 градусов таблицы?
                                          Конечно пару байд дискового пространства потеряется, но переписывание кода, при введении нового параметра и изменение структуры БД отпадет совсем.
                                            0
                                            да да тока генератор SQL придется состряпать
                                            0
                                            Сэкономили пусть даже 15 байт за одну компанию, зато очень и очень потеряли в скорости обработки данных (как при выборке — полный просмотр, так и при вставке/редактировании — цикл). В общем, поменяли шило на мыло.
                                              –3
                                              Когда-то и я придумал подобный метод для django :)
                                              www.alrond.com/ru/2008/apr/23/save-boolean-condition-in-django-in-bitmask/
                                                –2
                                                М… Это вы придумали битовые маски? Может и условный переход тоже ваших рук дело? ;)
                                                0
                                                Когда-то и я такой велосипед изобретал. Слава богу нашлись люди, которые сумели мне объяснить, почему так делать не надо ни в коем случае.
                                                  0
                                                  Было бы намного прекрасней, если бы вы объяснили почему так делать нельзя)))

                                                  А вообще побитовые оптимизации вещь известная и где-то точно применения находит, не уверен правда что тут — не представляю как MySql будет такое оптимизировать — смотрю чисто с точки зрения программиста.
                                                    0
                                                    Вы совершенно правы — MySQL никак не сможет оптимизировать.
                                                    Собственно по этому и не стоит так делать. Ведь все чекбоксы в дальнейшем используются в дальнейшем как фильтры по которым в дальнейшем будет строиться выборка, расчитываться статистика и прочее.
                                                    Единственное где можно использовать (по моему мнению) это в местах с узким каналом, например устройства передающие данные через GPRS.
                                                    Или при необходимости одновременного хранения большого числа объектов в оперативной памяти.
                                                    Но в таком случае можно/нужно делать параллельно хранение в БД для поиска и в записи для хранения и передачи.
                                                  0
                                                  Хранение нескольких переменных булевого типа в одной численно не нова. А вот в БД, как сказали выше, его применение нецелесообразно.
                                                    +1
                                                    вы бы для наглядности показали что за биты-то такие.
                                                    слева двоичная система, справа — десятеричная.
                                                    00001 = 1 - первый флаг
                                                    00010 = 2 - второй
                                                    00100 = 4 - третий
                                                    01000 = 8 - четвёртый
                                                    10000 = 16 - пятный
                                                    и так далее
                                                    01011 = 1+2+8 = 11 - перый, второй и четвёртый
                                                    
                                                      –3
                                                      «Расширенный CHMOD»
                                                      0
                                                      Пробовал как-то такую штуку для админки (отдельные задачи обозначал константами, в БД хранил сумму флагов) — больше так делать не буду, ибо нерасширяемо.
                                                      Лучше отдельной таблицы ничего не придумано?
                                                      • НЛО прилетело и опубликовало эту надпись здесь
                                                          0
                                                          Собирается на клиенте на раз два, представление — это его задача.
                                                          +1
                                                          Экономия на спичках с неизвестными граблями сайд-эффектами.
                                                            0
                                                            Для PHP есть небольшая и малозаметная проблема на 32х битных системах, которая может возникнуть при работе с битовыми масками, полученными из БД. Заключается в приведении типов. Описание самой проблемы и решение
                                                              0
                                                              Основной недостаток — при поиске. Ни один движок не сделает индекс по битовой маске.
                                                                +2
                                                                Вы уточняйте: поиск бита в битовой маске.

                                                                Индекс то сделается, но зачем он тогда нужен, если все записи будут перебираться.

                                                                Но в процессе построения запросов, самое удивительное то,
                                                                что оптимизатор mysql сам всегда выбирает, какой индекс использовать, и в 99% случаев не ошибается.

                                                                Так вот, 99% того что оптимизатор сначала сделает «выборку» по другим удобным индексам и только потом начнет высчитывать поля с битовыми масками.

                                                                Вывод: если вы знатете что при выборке до манипуляций с битовыми полями у вас будет результат не очень большой, можете спокойно пользоваться битовыми масками, SUN не зря купили MySQL GmbH за миллиард.

                                                                +2
                                                                Битовую маску можно использовать, только надо знать где… да вообще explain и оптимизации никто не отменял.
                                                                Учтите одно, если вы в запросе «высчитываете/просчитываете» поле битовой маски — забудьте про индексы при выборке, правда, если вы join выполнятете к выбранному результату и потом битовую маску считаете, то на скорости это не скажется, если конечно вы не используете поле битовой маске в ON.

                                                                Далее, например очень удобно хранить «доступ» по группам.
                                                                Конечно 32 бита — это 32 группы, но этого всегда вполне хватит (кто-то наблюдал более 32 групп в природе ;)?

                                                                Кстати mysql, да вообще все «языки» очень быстро выполняют битовые операции — не забывайте об этом!
                                                                  0
                                                                  кто-то наблюдал более 32 групп в природе ;)
                                                                  Смотря где, если это какая-то (пардон за набившее оскомину слово) очередная «социальная сеть», то там будет всяко больше 30 групп. Но даже если это небольшой проект, то все равно иметь жесткое ограничение в 32 группы очень нехочется. Кто знает, что будет завтра — может вам жизненно потребуется 33-я группа, а сделать ее будет нереально без полной ломки приложения.
                                                                    0
                                                                    В соц. сети более 32 групп? ну возможно, пусть скажут например работающие в хабре, но я сомневаюсь,
                                                                    Хотя всё может быть, но «новое веенье» — это не засывывать юзеров по мелким группам, а давать каждому юзеру — свои права.

                                                                    И далее… Никто не мешает плугинсом добавить еще одно поле + 32 группы и еще одно и т.п.

                                                                    При нормальной архитектуре — полной ломки приложения не надо. Всё спокойно делают плугинсы или (они же) модули
                                                                      +1
                                                                      Все же зря вы отрицаете групповую раздачу прав. На мой взгляд, удобнее организовать пользователей в группы («Админы», «Менеджеры», «Техподдержка» и т.п.) и разом дать им права, чем возиться с каждым.

                                                                      Если так добавлять поля, то потребуется менять структуру таблицы.
                                                                  –2
                                                                  АХАХАХАХААААА =) я под столом просто.
                                                                  coylone.habrahabr.ru/blog/37131/
                                                                  За этот пост я лишился 10 пунктов кармы, а сам пост ушел в минус +)))))))

                                                                  Для тех кто не понял — это идентичный пост, написанный ранее.
                                                                    0
                                                                    Поиском он не находился
                                                                      +1
                                                                      как говорится, телепаты в отпуске :)
                                                                      0
                                                                      Напишите пожалуйста, как лучше всего реализовать этто с помощью отдельной таблицы )
                                                                        0
                                                                        Уточните, пожалуйста, задачу. Вы хотите хранить битовые маски в отдельной таблице, или же просто права и(или) группы?
                                                                          0
                                                                          Chrome, я имею в виду поставленную в посте задачу.
                                                                          Из комментариев я понял, что битовые маски — не самый удобный по скорости и удобству использования вариант реализации. Несколько раз в комментах прозвучало мнение, что лучше всего использовать отдельную таблицу для решения задачи.

                                                                          Хотелось бы разобраться и найти оптимальный способ )

                                                                    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                                                    Самое читаемое