Денормализация связей многие-ко-многим, через битовые маски

Ситуация довольно банальная: есть статьи и тэги, связанные многие-ко-многим, и необходимо быстро находить статьи с определенным тэгом или несколькими тэгами. Необходимо обойтись без JOIN и одним простым запросом.

В mysql есть тип данных set, который вполне для этого подходит. Но можно и ручками

Посмотрим взлетит оно или нет:

CREATE TABLE  `tests`.`article` (
  `id` int(10) unsigned NOT NULL auto_increment,
  `mask` int(10) unsigned default NULL,
  `cset` set('1','2','3','4','5','6','7','8','9','10','11','12','13','14','15','16','17','18','19','20') default NULL,
  PRIMARY KEY  (`id`),
  KEY `mask` (`mask`),
  KEY `cset` (`cset`)
) ENGINE=InnoDB;

Добавление записей:

INSERT INTO article VALUES(NULL, 3, '1,2');

Выбор по одному тэгу:

select count(*) from article where mask & 1; //помеченные тэгом #1
select count(*) from article where cset & 1;

Выбор по нескольким тэгам – объединение:

select count(*) from article where mask & 3; //помеченные тэгом #1 ИЛИ #2
select count(*) from article where cset & 3;

Выбор по нескольким тэгам – пересечение:

select count(*) from article where mask & 1 AND mask & 2; //помеченные и  тэгом #1 И тэгом #2
select count(*) from article where cset & 1 AND cset & 2;

По скорости варианты равноценны, и на табличке с 500К записей (20 разных тэгов, по 1-20 тэгов на статью) все запросы обрабатывались за, примерно, 0.3 секунды, на средненькой домашней машинке.

Несколько комментариев

  1. zerkms пишет:

    ну если закрыть глаза на ограничение в 64 значения…

  2. Да ну не такое уж строгое ограничение, если судить по реальной жизни. Да и никто не мешает добавить еще одно поле и спрятать работу с ним, в каком-нибудь criteria-классе.

Оставить комментарий