Как сопоставить ключевое слово htmlenified и ключевое слово non htmlenified одновременно в полнотекстовом поиске MySQL - PullRequest
1 голос
/ 15 июля 2011

Меня научили хранить мои данные в MySQL как можно более сырыми.Вот пример того, как я храню контент в моей базе данных MySQL:

title (VARCHAR, 255) => Références
content (TEXT) => <p>A paragraph about r&eacute;f&eacute;rences...</p>

Когда я выводю его на страницу, я использую htmlentities() на title, но, конечно, не на контенте.Я чувствую, что это правильный способ хранения, поскольку title хранит только текст, а content хранит HTML.

Однако теперь я вижу ограничение на это: когда я выполняю полнотекстовый поиск всоответствовать определенному ключевому слову (например, ссылки), мне нужно найти оба références И r&eacute;f&eacute;rences, чтобы получить все результаты.

А теперь я думаю ... Как правильно решить эту проблему?

  • Просмотрите базу данных и сохраните все с htmlentities?(Не хочу!)
  • Есть два поиска, один для ключевого слова без htmlentities и один для другого с?(Мне не кажется оптимальным ...)

Просто для записи, вот мой огромный запрос MySQL, который ищет в page, page_content, article, download, member и event, так что у вас есть небольшая картина того, с чем я имею дело.

Заранее спасибо за ваши усилия.

$keyword = utf8_decode(mysql_real_escape_string($_POST['keyword']));

SELECT 
    *, 
    sum(score) AS total_score
FROM 
    (
        SELECT 
            "page" as db_table,
            lid,
            sid as page_sid,
            sid,
            hook,
            title,
            meta_keywords,
            meta_description,
            NULL as content,
            NULL as location,
            NULL as company,
            MATCH(title, meta_keywords, meta_description) AGAINST("'.$keyword.'*" IN BOOLEAN MODE) AS score 
        FROM page
        WHERE MATCH(title, meta_keywords, meta_description) AGAINST("'.$keyword.'*" IN BOOLEAN MODE)
    UNION
        SELECT 
            "page_content" as db_table,
            p.lid as lid,
            pc.page_sid as page_sid,
            NULL as sid,
            NULL as hook,
            p.title as title,
            NULL as meta_keywords,
            NULL as meta_description,
            pc.content as content, 
            NULL as location,
            NULL as company,
            MATCH(content) AGAINST("'.$keyword.'*" IN BOOLEAN MODE) AS score 
        FROM page_content pc, page p
        WHERE MATCH(content) AGAINST("'.$keyword.'*" IN BOOLEAN MODE)
        AND p.sid = pc.page_sid     
    UNION
        SELECT 
            "article" as db_table,
            lid,
            NULL as page_sid,
            sid,
            NULL as hook,
            title,
            meta_keywords,
            meta_description,
            content,
            NULL as location,
            NULL as company,
            MATCH(meta_keywords, meta_description, title, content) AGAINST("'.$keyword.'*" IN BOOLEAN MODE) AS score 
        FROM article 
        WHERE MATCH(meta_keywords, meta_description, title, content) AGAINST("'.$keyword.'*" IN BOOLEAN MODE)   
    UNION
        SELECT 
            "download" as db_table,
            lid,
            NULL as page_sid,
            NULL as sid,
            NULL as hook,
            title,
            NULL as meta_keywords,
            NULL as meta_description,
            content,
            NULL as location,
            NULL as company,
            MATCH(title, content) AGAINST("'.$keyword.'*" IN BOOLEAN MODE) AS score 
        FROM download
        WHERE MATCH(title, content) AGAINST("'.$keyword.'*" IN BOOLEAN MODE)
    UNION
        SELECT
            "event" as db_table,
            lid,
            NULL as page_sid,
            NULL as sid,
            NULL as hook, 
            title,
            NULL as meta_keywords,
            NULL as meta_description,
            content,
            location,
            NULL as company,
            MATCH(title, content, location) AGAINST("'.$keyword.'*" IN BOOLEAN MODE) AS score 
        FROM event
        WHERE MATCH(title, content, location) AGAINST("'.$keyword.'*" IN BOOLEAN MODE)  
    UNION
        SELECT
            "member" as db_table, 
            NULL as lid,
            NULL as page_sid,
            NULL as sid,
            NULL as hook, 
            NULL as title,
            NULL as meta_keywords,
            NULL as meta_description,
            NULL as content,
            NULL as location,
            company,
            MATCH(company) AGAINST("'.$keyword.'*" IN BOOLEAN MODE) AS score 
        FROM member
        WHERE MATCH(company) AGAINST("'.$keyword.'*" IN BOOLEAN MODE)   
    ) AS sub_query
WHERE 1=1
GROUP BY page_sid
ORDER BY total_score DESC

Ответы [ 3 ]

1 голос
/ 15 июля 2011

При полнотекстовом поиске IN BOOLEAN MODE Я полагаю, вы можете использовать оператор OR в своем выражении.См. MySQL документацию для более подробной информации.Вы можете использовать это, чтобы сделать это в одном запросе.

Вышеупомянутое будет примерно таким в вашем коде:

$keyword = utf8_decode(mysql_real_escape_string($_POST['keyword'])); 
$keyword = '(' . $keyword . '*) OR (' . htmlentities($keyword) . '*)';

(немного грязно, но я думаю, что вы можетеочистить это;)

Я не знаю, может ли переменная $keyword содержать более одного слова в вашем случае.Поэтому я добавил скобки, чтобы прояснить это.

На другом примечании: я не уверен, что булевский полнотекстовый поиск будет видеть что-то вроде r&eacute;f&eacute;rences как одно слово.Но я думаю, стоит попробовать;)

Редактировать: Я только что обнаружил, что проблему с вышеупомянутым можно решить с помощью двойных кавычек вокруг ключевого слова, например: "r&eacute;f&eacute;rences".То есть, если есть проблема, конечно;)

1 голос
/ 15 июля 2011

Вы также можете оставить "référence" неэкранированным в content, так как он остается правильным HTML.

(Пока в заголовке HTML кодировка будет такой же, как в базе данных, предположительно UTF-8:

<meta http-equiv="Content-Type" content="text/html;charset=utf-8" >

)

0 голосов
/ 08 июля 2014

Как французский разработчик, работающий на французском сайте, у меня тоже была такая же проблема, и я нашел способ ее решить, все еще используя мощную функцию MATCH AGAINST, которая, как мне показалось, могла бы пригодиться.Вы извините возможные английские ошибки, я сказал, что я француз!: -)

Проблема, с которой я столкнулся, заключалась в том, что слово htmlenified не распознается как одно слово.Например, << édité >>, когда «htmlenified» появляется в моей базе данных как << édité >>, а функция MATCH AGAINST рассматривает его как << 'eacute' 'dit' 'eacute' >> (три разных слова).

Но, как было сказано выше, слово "htmlenified" между кавычками (") рассматривается как выражение само по себе, а <<" édité ">> рассматривается как <<" édité ">> (одно выражение).

Таким образом, решение оказалось таким, что мне нужно было сохранять выражения между кавычками в том виде, в каком они есть, и использовать регулярное выражение, чтобы помещать слова htmlenified между кавычками.чтобы решить проблему, пока кто-то не исправит ошибку в MySQL.

    //At start, my variable is "htmlentified", so I need to replace <<&quot;>> with real double quotes <<">>
$Search_Text=str_replace("&quot;","\"",$Search_Text);

//I used the "ENT_COMPAT" option for html_entities, so I need to escape the single quotes
$Search_Text=str_replace("'","\\'",$Search_Text);

//I search if there are text between double quotes using "preg_match_all" with "PREG_OFFSET_CAPTURE" option to have starting offset for each of them
//Note that at the start of the regex I keep the eventual "+" or "-" or "(" or ")" just before or after quotes
if(preg_match_all('#([-|\+|(]{0,})(\"(.+)\")([)]{0,})#isU',$Search_Text,$Quoted_Blocks,PREG_OFFSET_CAPTURE))
{
    //I Initialise an array that will contain each distinct words and quoted expressions from the search text
    $List_Search_Terms=Array();

    //If the offset of the first quoted expression is not 0, it means that there is some words before that I will put in the array
    if($Quoted_Blocks[0][0][1]>0)
    {
        $Starting_Offset=0;
        $Length=$Quoted_Blocks[0][0][1];

        foreach(explode(" ",trim(substr($Search_Text,$Starting_Offset,$Length))) as $Index=>$Valeur)
        {
            array_push($List_Search_Terms,$Valeur);
        }
    }

    //Then I treat all quoted expressions and words between them that I will put in the array
    for($Index_Bloc=0;$Index_Bloc<count($Quoted_Blocks[0]);$Index_Bloc++)
    {
        //I put the quoted expression unmodified in my array
        array_push($List_Search_Terms,$Quoted_Blocks[0][$Index_Bloc][0]);

        //I verify if there's a following quoted expression for I can treat the words in between
        if(isset($Quoted_Blocks[0][$Index_Bloc+1]))
        {
            $Starting_Offset=$Quoted_Blocks[0][$Index_Bloc][1]+strlen($Quoted_Blocks[0][$Index_Bloc][0]);
            $Length=$Quoted_Blocks[0][$Index_Bloc+1][1]-$Starting_Offset;

            //I put the words in between in the array
            foreach(explode(" ",trim(substr($Search_Text,$Starting_Offset,$Length))) as $Index=>$Valeur)
            {
                array_push($List_Search_Terms,$Valeur);
            }
        }
    }

    //After treating all quoted expressions, I put in the array the words that can remain after the last quoted expression
    $Starting_Offset=$Quoted_Blocks[0][count($Quoted_Blocks[0])-1][1]+strlen($Quoted_Blocks[0][count($Quoted_Blocks[0])-1][0]);

    if($Starting_Offset<strlen($Search_Text))
    {
        foreach(explode(" ",trim(substr($Search_Text,$Starting_Offset))) as $Index=>$Valeur)
        {
            array_push($List_Search_Terms,$Valeur);
        }
    }
}
else
{
    //If there's no quoted expression in the search, I put all words in the array
    $List_Search_Terms=explode(" ",$Search_Text);
}

//Once I have all words and quoted expressions in the array, I can run through it to put "htmlentified" words (not expressions !) between double quotes
foreach($List_Search_Terms as $Index=>$Value)
{
    //I control that the line of the array doesn't contain double-quotes (which I won't touch) and if there are "&" and ";" characters that means an "htmlentified" word
    if(!preg_match('#"#',$Value) && preg_match('#&(.+)\;#isU',$Value))
    {
        //if both conditions match, I put quotes at the beginning and end
        //Note that as abovre, the regex will keep the eventual "+" or "-" or "(" or ")" just before or after the word
        $List_Search_Terms[$Index]=preg_replace('#^([-|\+|(]{0,})(.+)([)]{0,})$#is',"$1\"$2\"$3",$Value);
    }
}

//After that treatment, the array is converted in one single text that can be included in the mysql AGAINST condition
$Final_Search_Text=implode(" ",$List_Search_Terms);

Возможно, есть некоторые оптимизации, которые можно внести в него, но в действительности это делает свою работу!

...