Таблицы: Объекты Свойства Справочник значений свойств Значения свойств объектов Нужен полноценный поиск по любому набору полей типа Россия, 27-32 года, без детей, владеет С++ и C#, опыт не менее 2-х лет. Сейчас я это реализовал, но как-то не слишком здорово. Версии, вопросы, комментарии?
поясняю | users | CREATE TABLE `users` ( `id` int(11) NOT NULL auto_increment, `name` varchar(255) NOT NULL default '', `id_usertype` int(11) NOT NULL default '3', `login` varchar(255) NOT NULL default 'anonym', `pw` varchar(255) NOT NULL default 'anonym', `email` varchar(255) default NULL, `_order` int(11) NOT NULL default '0', PRIMARY KEY (`id`) ) ENGINE=MyISAM DEFAULT CHARSET=latin1 | | profilefields | CREATE TABLE `profilefields` ( `id` int(11) NOT NULL auto_increment, `id_message` int(11) NOT NULL default '0', `type` varchar(25) NOT NULL default 'input', `id_group` int(11) NOT NULL default '0', `_order` int(11) NOT NULL default '0', PRIMARY KEY (`id`), KEY `i_message` (`id_message`), KEY `i_group` (`id_group`), KEY `i_order` (`_order`) ) ENGINE=MyISAM DEFAULT CHARSET=latin1 | | profiledata | CREATE TABLE `profiledata` ( `id` int(11) NOT NULL auto_increment, `id_user` int(11) NOT NULL default '0', `fieldname` varchar(255) default NULL, `value` text, `thetime` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=MyISAM DEFAULT CHARSET=latin1 | чтобы организовать поиск по полю "возраст", сейчас рождается примерно следующее select distinct u.id from ((profiledata as pd left join profilefields as pf on concat("f-",pf.id)=pd.fieldname) inner join users as u on u.id=pd.id_user) where (pd.fieldname="f-6" and pd.value > "10") and (pd.fieldname="f-6" and pd.value < "15") чтобы искать по двум полям: страна и возраст - придется ДВАЖДЫ джоинить на таблицу значений полей
200 полей, десятки тысяч пользователей, тысячи значений справочников, частота поиска - 1 запрос в секунду
Понятно. Но еще одно. Можешь ошибиться в построенном запросе и обрежутся данные. А статика все-таки лучше и надежнее. Может быть конечно найти золотую середину и что-то сделать больше справочниками и джойнить меньшие размеры или засунуть все в одну таблицу.
а вот код не слишком отточено, но идея тут // Класс "Запрос" class request { var $units; function request() { $this->units = array(); } // порождает sql-запрос типа Select на выборку function render() { global $global; $table = ' '.$global['tables']['users'].' as u '; $res = ''; foreach ($this->units as $num=>$unit) { $fragment = $unit->render($num); $glue = ($res) ? ' and ' : ''; $res .= ($fragment ? $glue.$fragment : ''); $table = '('.$table.' inner join ('.$global['tables']['profiledata'].' as pd'.$num.' inner join '.$global['tables']['profilefields'].' as pf'.$num.' on concat("f-",pf'.$num.'.id)=pd'.$num.'.fieldname) on u.id=pd'.$num.'.id_user)'; } $res = 'select distinct u.id from '.$table.($res ? (' where '.$res) : ''); // убираем парные пробелы while (strpos($res,' ')!=false) $res = str_replace(' ',' ',$res); return $res; } } // Класс "запросная единица " class unit { var $field; var $constraint; function unit($field,$constraint) { $this->field = $field; $this->constraint = $constraint; } function render($num) { $res = ''; if ( ($this->field) && ($this->field->name) && ($this->constraint) && (($this->constraint->value>'') || ($this->constraint->min>'') || ($this->constraint->max>'')) ) { // Есть что рендерить $comma = ($this->field->isstring) ? '"' : ''; // значение if ($this->constraint->type=='value') { // "=" или "like" $sign = ($this->constraint->comparison == 'strong') ? ' = ' : (($this->constraint->comparison == 'like') ? ' like ' : ''); if ($sign) { if ($this->field->virtual) $res .= ' (pd'.$num.'.fieldname="'.$this->field->name.'" and pd'.$num.'.value'.$sign.' '. $comma.$this->constraint->value.$comma.')'; else $res .= ' (`'.$this->field->name.'` '.$sign.' '. $comma.$this->constraint->value.$comma.')'; } } // интервал if ($this->constraint->type=='interval') { // левая граница if ($this->constraint->min>'') { // ">" или ">=" $sign = ($this->constraint->comparison == 'strong') ? ' > ' : (($this->constraint->comparison == 'not strong') ? ' >= ' : ''); if ($sign) { if ($this->field->virtual) $res .= ' (pd'.$num.'.fieldname="'.$this->field->name.'" and pd'.$num.'.value'.$sign.' '. $comma.$this->constraint->min.$comma.')'; else $res .= ' (`'.$this->field->name.'` '.$sign.' '. $comma.$this->constraint->min.$comma.')'; } } // " and " if ( ($this->constraint->max>'') && ($this->constraint->min>'') ) $res .= ' and '; // правая граница if ($this->constraint->max>'') { // "<" или "<=" $sign = ($this->constraint->comparison == 'strong') ? ' < ' : (($this->constraint->comparison == 'not strong') ? ' <= ' : ''); if ($sign) { if ($this->field->virtual) $res .= ' (pd'.$num.'.fieldname="'.$this->field->name.'" and pd'.$num.'.value'.$sign.' '. $comma.$this->constraint->max.$comma.')'; else $res .= ' (`'.$this->field->name.'` '.$sign.' '. $comma.$this->constraint->max.$comma.')'; } } } } return $res; } } class field { var $isstring; var $name; var $caption; var $virtual; // =1 if this field was added in editor and is not physically exists in `users` table function field($name, $isstring='1', $virtual='0') { $this->name = $name; $this->isstring = $isstring; $this->virtual = $virtual; } } // ограничение. class constraint { var $value; var $min; var $max; // 'strong' => '<' // 'not strong' =? '<=' // 'like' =? 'like' var $comparison; // тип: value или interval var $type; // Есть два варианта вызова конструктора // при type=value передают одно значение // при type=interval передают два значения: меньшее, потом большее function constraint($type,$value1,$value2 = '',$comparison = 'strong') { $this->type = $type; $this->comparison = $comparison; if ($this->type=='value') { $this->value = $value1; } if ($this->type=='interval') { $this->min = $value1; $this->max = $value2; } } }
Все конечно супер. И классы вроде бы обрабатывают правильно, но скажи мне, а у тебя лефт джойны тоже будут задействованы или иннер джойнами можно обойтись - я в коде увидел только иннеры. А иннеры все будут постепенно резать - этакий разгон. А вот с лефтами тяжелые будут запросы. Идея получать только ид в данном запросе - это хорошая идея, так не придется много индексировать.
лефты нужны, чтобы можно было найти человека, не заполнившего данное поле это безусловная дыра, я ее буду фиксить, забивая некое default'ное значение в поле при добавлении поля или юзера, то есть чтобы в profiledata лежали все возможные сочетания полей-объектов
проблема в следующем mysql> select from users inner join profiledata as pd1 inner join profilefields as pf1 on pd1.fieldname = concat("f-",pf1.id) and pf1.id=6 on users.id=pd1.id_ user; выдает возрасты пользователей mysql> select from users left join profiledata as pd1 inner join profilefields as pf1 on pd1.fieldname = concat("f-",pf1.id) and pf1.id=6 on users.id=pd1.id_u ser; выдает список всех свойств юзера, в серединке которой будет и возраст несмотря на то, что profiledata и profilefields я делаю иннер джоин!
ситуация какая есть профиль человек имеет полное право поле "возраст" не заполнять. как мне получить всех юзеров, джоиня их на таблицу значений свойств, если запись про возраст в этой таблице значений свойств пользователей вообще не существует? сейчас это решено таким образом: select (if(pd1.fieldname = concat("f-",pf1.id),1,0)) as haveage, (if(pd2.fieldname = concat("f-",pf2.id),1,0)) as havecountry, pd1.value as age, pd2.value as country from ((((users as u left join photos as p on u.id=p.id_user ) left join online as o on u.id=o.id_user ) left join (profiledata as pd1 inner join profilefields as pf1 on pd1.fieldname = concat("f-",pf1.id) and pf1.id=6) on pd1.id_user=u.id ) left join (profiledata as pd2 inner join profilefields as pf2 on concat"f-",pf2.id)=pd2.fieldname and pf2.id=13) on pd2.id_user=u.id) where u.id=421 order by (if(pd1.fieldname = concat("f-",pf1.id) and pd2.fieldname = concat("f-",pf2.id),0,1)) limit 1