Pesquisas não sensíveis ao caso e acento no MongoDB e PHP

Vindo do MySQL, programadores ficam preguiçosos a respeito de pesquisar strings. MySQL faz todo o serviço sujo por você: pesquise joao e encontre joao, joão, João, JOÃO e qualquer outra variante.

Mas… quando você começa a utilizar o MongoDB, mesmo com suas características poderosas, você se perde. Vamos encontrar uma solução.

Primeiro de tudo, geralmente você não quer pesquisar uma string exata, mas parte dela. Para isto você utiliza a clásula LIKE no WHERE, como abaixo:

SELECT * FROM people WHERE name LIKE "%joão%";

E você encontra “joão”, com qualquer caso (maiúscula/minúsculas), com ou sem acento, em qualquer parte do campo “name”.

No DB você não possui a clásula “LIKE”, mas, muito mais poderoso, você pode utilizar expressões regulares. Então você pode fazer:

db.my_collection.find({ name: /.*joão.*/}

e encontrará “joão” em qualquer parte do campo “name”, mas SENSÍVEL ao caso. Para pesquisas não-sensíveis ao caso, adicionar “/i” faz o trabalho muito bem:

db.my_collection.find({ name: /.*joão.*/i}

Agora realmetne nós temos um problema. Nós podemos fazer algo como /.*jo[aãáâàAÃÁÂÁ]o.*/i e resolver parte do problema… porque isto encontra joão, joao, joÃo, JOÃO e por aí vai, mas não jóão (veja o o primeiro “o” com acento).

Este é o problema em pequena abrangência, pense no seu usuário utilizando formas muito estranhas de escrever seus nomes, o ainda pior, pense em nomes estranhos em outras línguas com acento (eu sei português, mas não usamos, por exemplo, “n” ou “Ñ” como o espanhol).

Como eu utilizo o MongoDB com o PHP, e o PHP sabe lidar com expressões regulares, vamos utilizá-lo para pegar todas as variações de joão com acentos.

<?php
/**
 * Description of StringUtil
 *
 * @author  Rafael Goulart
 */

class StringUtil {

    const ACCENT_STRINGS = 'ŠŒŽšœžŸ¥µÀÁÂÃÄÅÆÇÈÉÊËẼÌÍÎÏĨÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæçèéêëẽìíîïĩðñòóôõöøùúûüýÿ';
    const NO_ACCENT_STRINGS = 'SOZsozYYuAAAAAAACEEEEEIIIIIDNOOOOOOUUUUYsaaaaaaaceeeeeiiiiionoooooouuuuyy';

    /**
     * Returns a string with accent to REGEX expression to find any combinations
     * in accent insentive way
     *
     * @param string $text The text.
     * @return string The REGEX text.
     */
    static public function accentToRegex($text)
    {

        $from = str_split(utf8_decode(self::ACCENT_STRINGS));
        $to   = str_split(strtolower(self::NO_ACCENT_STRINGS));
        $text = utf8_decode($text);
        $regex = array();
        foreach ($to as $key => $value) {
            if (isset($regex[$value])) {
                $regex[$value] .= $from[$key];
            } else {
                $regex[$value] = $value;
            }
        }
        foreach ($regex as $rg_key => $rg) {
            $text = preg_replace("/[$rg]/", "_{$rg_key}_", $text);
        }
        foreach ($regex as $rg_key => $rg) {
            $text = preg_replace("/_{$rg_key}_/", "[$rg]", $text);
        }

        return utf8_encode($text);
    }
}

Utilizando esta função, joão (ou qualquer forma similar) será transformado em:

j[oÒÓÔÕÖØðòóôõöø][aÁÂÃÄÅÆàáâãäåæ][oÒÓÔÕÖØðòóôõöø]

E então completamos a pesquisa utilizando o Driver do MongoDB para PHP como segue:

<?php
// Connection
$mongo = new Mongo("localhost:27017", array("persist" => "x"));

// Selecting database
$db = $mongo->my_db;

// Selecting collection
$collection = $db->my_collection;

// Creating the Query
$search = StringUtil::accentToRegex('joão');
$query =  array('name' =>new MongoRegex("/.*{$search}.*/i"));

// Running and returning results
$cursor = $collection->find(query);

E está feito! Claro que você pode pesquisar vários campos simultaneamente, mas isto é com você…

  • Rafael

    OLá chará! Estou trabalhando com java e estudando mongodb para aplicar em uma solução onde trabalho, no entanto, estou tendo dificuldades acredito eu bobas até, preciso realizar uma consulta por um trecho de uma string, populei uma coleção com 500 mil documentos onde existem somente um atributo: usuario.

    O formato das string na coleção estão assim:
    usuario1@host.com.br
    usuario2@host.com.br

    Acontece que eu tento tanto no console do mongo quanto no código em java executar o código assim:

    -console:
    db.logs.find({“usuario” : “/.*usuario.*/”})

    E não resulta em nada, o que há de errado?

    Obrigado!

  • Muito obrigado pela solução!

    Muito embora acredito que não tenha a melhor performace do mundo, ela funciona muitíssimo bem!

    Valeu!

  • Pingback: how to remove permanent marker()

  • cara valeu!! me ajudou muito!!!

  • ” I haven’t jumped for joy over with these yet – however are pretty nice pens. When using promotional gifts as gifts it’s smart to use items which are unrelated. Any number of business presents although give an effect in your advertising process but in addition impressed your audience promotional mugs: an excellent business promotion product promotional mugs have many space to show the message and logo.

  • Pingback: A PHP script for MongoDB accent search needs to translate into JavaScript | BlogoSfera()

  • Make sure you continue track of all of the
    expenses associated with your marketing promotions, such as the indirect costs like shipping,
    handling and storage costs. Exporters should therefore devise an important and most appropriate strategy for exporting in place.
    You are able to use promotional merchandising to capture the minds in
    the people and reach your desired goal, which is to cause them to become aware about your organization’s existence.

  • You are awesome! Cheers!

  • save my life! Thanks man…

  • It was hard to find your blog in google search results.
    I found it on 19 place, you should build a lot of quality backlinks
    , it will help you to get more visitors. I know how to
    help you, just search in google – k2 seo tricks

  • Alan Ribeiro

    Rafael,
    Sua biblioteca funcionou muito bem, contudo, se faço o inverso, ou seja, pesquiso por ‘joao’ sem acento, a pesquisa não encontra os nomes com ‘JOÃO’.

    • Alan Ribeiro

      Para resolver o problema inclui, no final das constantes o seguinte:
      const ACCENT_STRINGS =
      ‘ŠŒŽšœžŸ¥µÀÁÂÃÄÅÆÇÈÉÊËẼÌÍÎÏĨÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæçèéêëẽìíîïĩðñòóôõöøùúûüýÿaeiouçAEIOUÇ’;
      const NO_ACCENT_STRINGS =
      ‘SOZsozYYuAAAAAAACEEEEEIIIIIDNOOOOOOUUUUYsaaaaaaaceeeeeiiiiionoooooouuuuyyaeioucAEIOUÇ’;

  • Tahir Ata Barry

    Although MondoDB does not offer it, but you can very easily do it in PHP or any other language. Here is how to do it in PHP.

    $cn = new MongoClient($dbHost);
    $db = $cn->selectDB($dbName);
    $col = new MongoCollection($db, $collectionName);

    $cursor = $col->find();
    $cursor = iterator_to_array($cursor);

    foreach ($cursor as $key => $row) {
    $name[$key] = $row[‘name’];
    $email[$key] = $row[‘email’];
    }
    //$name is the field to sort on, taken from the above loop
    //You can use SORT_ASC or SORT_DESC

    array_multisort($name, SORT_ASC, $cursor);

    foreach ($cursor as $doc) {
    echo $doc[‘name’].’-‘.$doc[‘email’].”;
    }

  • Obrigado pelo código Rafael!!!
    eu estava errando em não usar o decode UTF8 XD

    Abraço Equipe Mestre Search

  • Eduardo Alvarado D

    THANK YOU, YOUR CODE IS AMAZING!

    Obrigado!, Gracias!

  • Kinjal Fdgdfd Patel

    THANK YOU, It really work for me. You save my time