Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > PHP: Базы Данных > Помогите написать нормальный запрос для поиска


Автор: Borland_Delphi_6 3.2.2006, 18:52
Уже замучался с попытками сделать нормальный поисковик для своего сайта. В оригинале используется такой запрос:
SQL
$sql = ldu_mysql_query("SELECT * FROM $db_news WHERE (news_title = '%$sqlsearch%' OR news_text = '%$sqlsearch%') AND news_minlevel<='".$usr['level']."' AND news_date<'".$sys['now_offset']."' ORDER by news_date ", "Plugin/simplesearch/news");


Но он выдает какие-то совершенно дикие результаты, что поиском назвать ну никак нельзя. Например, по запросу "Рома" выдает страницу со словом "романтика" ну и т.д. Если же убрать "%", то не выдает почти ничего. Возможно, кто-то уже пытался реализовать более "интеллектуальный" поиск и будет не жалко поделится? Ну или хоть советы какие-то дайте чтоли... smile

Стоит ли применять "полнотекстовый" поиск, если таблица не очень большая? Какие это даст преимущества/недостатки?

Автор: AntonioBanderaz 4.2.2006, 02:41
Поищи доки про полнотекстовой поиск

Код

SELECT table.* 
             MATCH (table.<поле где искать>) 
             AGAINST ('что искать') AS score --Здесь будет на сколько твоя строка совподает с найденой
             FROM table 
                  WHERE MATCH (table.<поле где искать>)
                  AGAINST ('что искать')
             ORDER BY score


Автор: -=Ustas=- 4.2.2006, 06:30
Или LIKE заюзай.
http://dev.mysql.com/doc/refman/4.1/en/string-comparison-functions.html
http://dev.mysql.com/doc/refman/4.1/en/fulltext-search.html

Автор: Borland_Delphi_6 4.2.2006, 16:24
AntonioBanderaz
Я его уже реализовал, но нужно ли оно мне? Насколько снизится скорость?

-=Ustas=-

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

Автор: AntonioBanderaz 5.2.2006, 17:00
Цитата(Borland_Delphi_6 @ 4.2.2006, 16:24 Найти цитируемый пост)

Насколько снизится скорость?

Сам замерь... Я думаю не на много.
Добавлено @ 17:01
Цитата(Borland_Delphi_6 @ 4.2.2006, 16:24 Найти цитируемый пост)

нужно ли оно мне?

Это уже не мне решать ))))) Замерь и посмотри что по твоим критериям выдаст наиболее приемлимый результат...

Автор: Borland_Delphi_6 9.2.2006, 16:39
Блин, что-то я никак врубится не могу. Как мне с помощью LIKE сделать такую фигню: поиск ищет точное слово, но оно может встречатся в любом месте в тексте? Т.е. есть текст:

Цитата(Текст№1)

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


Цитата(Текст№2)

Сок - это очень вкусно.


Мне нужно найти слово "сочный". Если сделать "%сочный%", то помимо страницы с текстом №1, выдаст также и страницу №2. Если сделать просто WHERE smth = 'сочный', то не найден ни того, ни другого smile Нужно, чтобы искал точное совпадение...

Автор: AntonioBanderaz 9.2.2006, 16:52
RLIKE попробуй либо регульками в базе, а я бы сделал полнотекстовым!!!

Автор: Borland_Delphi_6 9.2.2006, 20:25
Вот что у меня получилось:

Код


//Разбиваем на логическое "ИЛИ" и обрабатываем запросы

$sqlsearch = "Some text to search";

        $logic_or = explode("^", $sqlsearch); 
        $logic_or_count = count($logic_or);
        if ($logic_or_count > 5)
                {
                $plugin_body .= "<p align=\"center\" style=\"color: red; font-weight: bold;\">Слишком много вложенных запросов логического \"ИЛИ\".<br />Максимально допустимое количество запросов - 4.</p>";
                $a = '';
                $continue_searching = 0;
                }

        //Создаем будущий SQL запрос
        if ($logic_or_count == 1) { $future_sql_query = "SELECT * FROM $db_news WHERE (news_title RLIKE '$logic_or[0]' OR news_text RLIKE '$logic_or[0]' OR news_title RLIKE '$logic_or[1]' OR news_text RLIKE '$logic_or[1]') AND news_minlevel<='".$usr['level']."' AND news_date<'".$sys['now_offset']."' ORDER by news_date "; }
        if ($logic_or_count == 2) { $future_sql_query = "SELECT * FROM $db_news WHERE (news_title RLIKE '$logic_or[0]' OR news_text RLIKE '$logic_or[0]' OR news_title RLIKE '$logic_or[1]' OR news_text RLIKE '$logic_or[1] OR news_title RLIKE '$logic_or[2]' OR news_text RLIKE '$logic_or[2]') AND news_minlevel<='".$usr['level']."' AND news_date<'".$sys['now_offset']."' ORDER by news_date "; }
        }
        //Создали... :)


Как видно, далеко не самый удачный вариант. Как бы эти длинные условия ("news_title RLIKE '$logic_or[0]' OR news_text RLIKE '$logic_or[0]' OR news_title RLIKE '$logic_or[1]' OR news_text RLIKE '$logic_or[1] OR news_title RLIKE '$logic_or[2]' OR news_text RLIKE '$logic_or[2]'") заменить регулярными выражениями? Насколько я понимаю, это ведь можно сделать?

Автор: -=Ustas=- 10.2.2006, 09:32
Borland_Delphi_6, тебе же уже советовали полнотекстовый поиск делать.... И выход этот правильный, т.к. через созданный индекс можно производить не физический, а логический поиск. Правда это если мускул не менее 4-ой версии.
Цитата

В MySQL 4.0.1 возможен полнотекстовый поиск также и в логическом режиме с использованием модификатора IN BOOLEAN.
Данный запрос вывел все строки, содержащие слово ``MySQL'' (заметьте, 50-процентная пороговая величина здесь не используется), но эти строки не содержат слова ``YourSQL''. Следует отметить, что логический режим поиска не сортирует автоматически строки в порядке уменьшения релевантности. Это видно по результату предыдущего запроса, где строка с наиболее высокой релевантностью (содержащая слово ``MySQL'' дважды) помещена последней, а не первой. Логический режим полнотекстового поиска может работать даже без индекса FULLTEXT, хотя и очень медленно.

В логическом режиме полнотекстового поиска поддерживаются следующие операторы:

+

Предшествующий слову знак ``плюс'' показывает, что это слово должно присутствовать в каждой возвращенной строке.

-

Предшествующий слову знак ``минус'' означает, что это слово не должно присутствовать в какой-либо возвращенной строке.

По умолчанию (если ни плюс, ни минус не указаны) данное слово является не обязательным, но содержащие его строки будут оцениваться более высоко. Это имитирует поведение команды MATCH() ... AGAINST() без модификатора IN BOOLEAN MODE.

< >

Эти два оператора используются для того, чтобы изменить вклад слова в величину релевантности, которое приписывается строке. Оператор < уменьшает этот вклад, а оператор > - увеличивает его. См. пример ниже.

( )

Круглые скобки группируют слова в подвыражения.

~

Предшествующий слову знак ``тильда'' воздействует как оператор отрицания, обуславливая негативный вклад данного слова в релевантность строки. Им отмечают нежелательные слова. Строка, содержащая такое слово, будет оценена ниже других, но не будет исключена совершенно, как в случае оператора - ``минус''.

*

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


И так далее, дальше можешь сам почитать smile http://dev.mysql.com/doc/refman/4.0/ru/fulltext-search.html

Автор: Borland_Delphi_6 10.2.2006, 17:05
-=Ustas=-
Да я пытаюсь, но полнотекстовый поиск не могу наладить для поля news_title, а для news_text все сделать удалось. Можно увидеть, какой запрос необходимо выяснить, чтобы разрешить полнотекстовый поиск для опр. поля?

Автор: -=Ustas=- 10.2.2006, 17:21
Borland_Delphi_6, тонкую настройку "http://dev.mysql.com/doc/refman/4.0/ru/fulltext-fine-tuning.html" смотрел? Может тебе переменный надо настроить.

Автор: Borland_Delphi_6 10.2.2006, 17:54
-=Ustas=-
Мне честно говоря этот полнотекстовый поиск не очень симпатизирует, из-за того, что он сам решает, на какие слова реагировать, а на какие нет, а также из-за своей нагрузки на БД... Но попробую чего-нибудь сделать. Мой вопрос по поводу оптимизации запроса в силе. Если непонятно, что я пытался спросить - дайте знать smile

Автор: Borland_Delphi_6 10.2.2006, 18:38
Короче, делаю два запроса на создание индексов поиска:

Код

ALTER  TABLE  `ldu_news`  ADD  FULLTEXT (  `news_text`  );
ALTER  TABLE  `ldu_news`  ADD  FULLTEXT (  `news_title`  );


Индексы вроде создаются. Дальше пытаюсь искать таким вот запросом:

Код

$sql = ldu_mysql_query("SELECT *, MATCH (news_title,news_text) AGAINST
                ('$sqlsearch') AS score
                FROM $db_news WHERE MATCH (news_title,news_text) AGAINST
                ('$sqlsearch')");


И получаю ошибку:
Цитата

MySQL error : Can't find FULLTEXT index matching the column list


Причем если искать по одному полю, то все ок. В чем трабл?

Автор: AntonioBanderaz 10.2.2006, 19:16
Код

<?php    
/*    
Class MysqlSearch    
Anton Velichko 10.02.2006    
*/    
class MysqlSearch {    
    var $host = ''; //Сервер подключения Mysql    
    var $user = 'root'; //Пользователь    
    var $password = ''; //Пароль    
    var $database = ''; //База данных    
     
    var $__activeLink = '';    
    var $__connectKey = '';    
    var $__searchMass = array();    
     
    var $where = array(); //Массив типа таблица=>row    
    var $what = ''; //Строка поиска    
    var $resultMass = array();    
     
     
     
    function MysqlSearch($host = '',$user = '',$password = '',$database = '') {    
            $this->host = $host;    
            $this->user = $user;    
            $this->password = $password;    
            $this->database = $database;    
             
            $this->__connectKey = mysql_connect($this->host,$this->user,$this->password);     
            mysql_select_db($this->database,$this->__connectKey);    
    }    
     
    function newSearch($what,$where) {    
        if(is_array($where) && isset($what)) {     
            $this->what = $what;    
            $this->where = $where;    
            $this->splitWhat();    
            $this->createQuery();    
        }     
    }    
     
    function getResults() {    
        return $this->resultMass;    
    }    
     
    function createQuery() {    
        foreach($this->where as $key => $value) {    
            $query = "select * from $key where ";    
            for($i=0;$i<count($this->__searchMass);$i++) {    
              foreach($value as $val)
                $query.='( '.$val." rlike '".$this->__searchMass[$i]."' ) or ";    
            }    
            $query = substr($query,0,strlen($query)-4);    
            //echo $query;    
            $this->__activeLink = mysql_query($query,$this->__connectKey);    
            while($row = mysql_fetch_assoc($this->__activeLink)) array_push($this->resultMass,$row);    
        }    
             
    }    
     
    function splitWhat() {    
        $separators = " ,.!?-";    
        $wordArray = array();    
        $total = 0;    
        for($token = strtok($this->what,$separators);$token !== false;$token = strtok($separators)) {    
            if($token != "" ) {    
                if(!isset($word_count[strtolower($token)])) {    
                     $word_count[strtolower($token)] = 1;    
                     array_push($wordArray,strtolower($token));    
                }    
                else $word_count[strtolower($token)]++;    
                $total++;    
            }     
        }    
        for($i=0;$i<count($wordArray);$i++) {    
            $this->createResultMass($i,count($wordArray),$wordArray);    
        }    
    }    
     
    function createResultMass($start,$end,$mass) {    
        if($end > $start) {    
            $val = '';    
            for($i=$start;$i<$end;$i++) $val .= ' '.$mass[$i];     
            array_push($this->__searchMass,$val);    
            $this->createResultMass($start,$end-1,$mass);    
        }    
             
    }    
}    
/* Пример использования    
$search = new MysqlSearch('host','user','password','db');    
$search->newSearch('search string',array("table"=>"массив полей"));    
print_r($search->getResults());    
*/    
?>


Ну как-то так короче...
Добавлено @ 19:18
Код

ALTER  TABLE  `ldu_news`  ADD  FULLTEXT (  `news_text`, `news_title`  );

А так?

Автор: -=Ustas=- 11.2.2006, 08:56
Цитата(Borland_Delphi_6 @ 10.2.2006, 18:38 Найти цитируемый пост)
ALTER  TABLE  `ldu_news`  ADD  FULLTEXT (  `news_text`  );
ALTER  TABLE  `ldu_news`  ADD  FULLTEXT (  `news_title`  );

Цитата(Borland_Delphi_6 @ 10.2.2006, 18:38 Найти цитируемый пост)
Причем если искать по одному полю, то все ок. В чем трабл?

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

Автор: Borland_Delphi_6 11.2.2006, 16:51
Цитата(AntonioBanderaz @ 10.2.2006, 20:16 Найти цитируемый пост)

Ну как-то так короче...

Спасибо конечно, но я поиск под движок пишу, так что мне все эти классы ни к чему smile

Цитата(AntonioBanderaz @ 10.2.2006, 20:16 Найти цитируемый пост)

ALTER  TABLE  `ldu_news`  ADD  FULLTEXT (  `news_text`, `news_title`  );


Ну я и тормоз smile Я до этого пытался индексы создать запросом

Код

ALTER  TABLE  `ldu_news`  ADD  FULLTEXT (  [b]`news_text,news_title`[/b]  );


smile Так вроде бы заработало, но... Есть ли возможность использовать в этом варианте логические "И" / "ИЛИ"? Если да, то как? Поддерживается ли это в MySQL напрямую, или необходимо самому придумывать? Примерные наброски того, как это можно было бы сделать с LIKE я приводил выше, но тут я что-то не знаю smile

Автор: AntonioBanderaz 11.2.2006, 21:26
Цитата(Borland_Delphi_6 @ 11.2.2006, 16:51 Найти цитируемый пост)
но я поиск под движок пишу, так что мне все эти классы ни к чему

а какая разница, он наиболее универсален...

просто
Код

$search = new MysqlSearch('host','user','password','db');     
$search->newSearch('ТО ЧТО ТЫ ХОЧЕШЬ НАЙТИ',array("ldu_news"=>Array('news_text', 'news_title')));    
print_r($search->getResults()); 


И он у тебя будет искать в таблице ldu_news в полях news_text и news_title...
по умолчанию любой разделитель является логическим OR, только эскейпь строку перед подачей...

Чтобы твою логику добавить просто нужно поменять ф-ию
Код

    function splitWhat() {     
        $separators = " ,.!?-";     
        $wordArray = array();     
        $total = 0;     
        for($token = strtok($this->what,$separators);$token !== false;$token = strtok($separators)) {     
            if($token != "" ) {     
                if(!isset($word_count[strtolower($token)])) {     
                     $word_count[strtolower($token)] = 1;     
                     array_push($wordArray,strtolower($token));     
                }     
                else $word_count[strtolower($token)]++;     
                $total++;     
            }      
        }     
        for($i=0;$i<count($wordArray);$i++) {     
            $this->createResultMass($i,count($wordArray),$wordArray);     
        }     
    }
она и генирит запрос к базе... она также разбивает на группы слов...

Автор: IZ@TOP 12.2.2006, 00:58
Borland_Delphi_6, а с чего ты взял что fulltext поиск будет тормозить? Вообще-то насколько я знаю, он работает на треть быстрее, нежели тот же like. А других способов поиска по тексту, кроме двух описанных, я не знаю! Просвятите дуралея!

Автор: Borland_Delphi_6 12.2.2006, 11:51
IZ@TOP,

Вроде бы в доках написано, что в fulltext таблицы данные будут добавлятся медленнее...


AntonioBanderaz,

Это я к тому, что мне нет особого смысла использовать твой класс целиком, т.к. поиск у меня будет в одном файле и мне не трудно вручную написать запрос smile Но разобраться в работе приведенного выше кода попытаюсь, т.к. он мне может очень пригодится. О результатах сообщу.
Спасибо!
Добавлено @ 11:55
AntonioBanderaz,

Хотя я тут подумал (за 2 минуты-то! smile ), может быть нет смысла изобретать велосипед... Попробую использовать твой класс, посмотрим, что выйдет smile

Автор: Borland_Delphi_6 12.2.2006, 14:41
AntonioBanderaz,

Попытался я воткнуть в свой скрипт этот класс - пришел к выводу, что нет в этом особого смысла...
Цитата(AntonioBanderaz @ 11.2.2006, 22:26 Найти цитируемый пост)
по умолчанию любой разделитель является логическим OR, только эскейпь строку перед подачей...


Что касается OR: есть страница с заголовком вроде "Обзор Galaxy 6600GE". Если я хочу поискать "Обзор Galaxy" - нет проблем, результат верен, а если "Обзор 6600GE" - то тут уже ничего не выдается. Вопрос в силе - есть ли возможность использовать OR и AND без BOOLEAN MODE?

Автор: IZ@TOP 13.2.2006, 14:40
Borland_Delphi_6, так мы же играем на выборке?! Или нет?

Автор: Borland_Delphi_6 13.2.2006, 15:47
IZ@TOP,

Чего-то я тебя не понял... smile

Автор: IZ@TOP 14.2.2006, 11:34
Borland_Delphi_6, Ты говоришь о тормазах при вставке данных, но мы ведь поиск юзаем по документам при селекте, а вставки дай бог несколько раз в день делаем.

Автор: Borland_Delphi_6 14.2.2006, 15:55
IZ@TOP,
Отнюдь, обновлять-то сайт часто надо. Но не это главное, т.к. у меня сервер 4х AMD Opteron... smile

Автор: Borland_Delphi_6 15.2.2006, 21:33
Никак не возьму в толк: что нужно сделать, чтобы MySQL мне вернула строку лишь в том случае, если есть полное совпадение. Пример:

Цитата(Text1)

Даже и не знаю, какой текст сочинить, поэтому просто напишу здесь слово дом (без кавычек smile )


Цитата(Text2)

А у вас есть домашние животные?


По запросу "дом" текст 1 должен найтись, а текст 2 - нет. Как такое устроить? Обратите внимание, что искомое слово может содержаться в любом месте текста, поэтому банальное сравнение через "=" не подойдет...

Автор: CTAPbIuMABP 17.2.2006, 15:02
Цитата

Никак не возьму в толк: что нужно сделать, чтобы MySQL мне вернула строку лишь в том случае, если есть полное совпадение



Код

SELECT * FROM `table` WHERE field LIKE '% дом %' LIMIT 20


а конкретно для твоего примера

Код

SELECT *, MATCH (field) AGAINST ('дом') as rel FROM `table` WHERE `field` LIKE '% дом %' ORDER BY rel DESC LIMIT 20

Автор: AntonioBanderaz 18.2.2006, 19:36
не всё найдёт, допустим в строке

Код

Дом, который построил Джек...


Слово дом он не найдёт...
Добавлено @ 19:40
Код

 SELECT *, MATCH (field) AGAINST ('дом') as rel FROM `table` WHERE `field` LIKE '% дом %' OR `field` LIKE '% дом,%' OR `field` LIKE '% дом:%' OR `field` LIKE '% дом!%' OR ....  ORDER BY rel DESC 

Автор: Borland_Delphi_6 19.2.2006, 15:55
Спасибо за советы, но я уже решил остановится на другом варианте с использованием RLIKE, который вполне нормально находит точное совпадение, в то же время, если использовать его для поиска одного слова, то выдает больше результатов, чем FULLTEXT Search, т.е. для RLIKE более мягкие условия получаются что-ли... Результат моих (или даже наших? smile ) трудов можно понаблюдать здесь: http://code.pp.ru/search.php Это на случай, если кому-то вдруг будет интересно smile

Автор: IZ@TOP 19.2.2006, 16:15
Цитата(Borland_Delphi_6 @ 14.2.2006, 16:55 Найти цитируемый пост)
Отнюдь, обновлять-то сайт часто надо. Но не это главное, т.к. у меня сервер 4х AMD Opteron..

А, ну если у тебя сидит 20 авторов и каждый раз в минуту что-то постит... и посетителей в этот момент на сайте в течении часа в районе тысячи... даже в таком случае сомневаюсь что производительность упадет очень сильно... тем более при такой системе...

Автор: Borland_Delphi_6 19.2.2006, 17:50
IZ@TOP,
Да с этим уже разобрались smile

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)