SQL e Regex – Entenda quando e como usar em instruções SQL

SQL e Regex – Entenda quando e como usar em instruções SQL

23 de janeiro de 2019 0 Por Ramos de Souza Janones

Aprenda o conceito e uso de Regex em SQL, o MySQL e para as mais diversas linguagens de programação que possuem suas próprias engines de regex embutidas.

Neste artigo você aprenderá um pouco do conceito e uso de Regex em SQL, não apenas para o MySQL para para as mais diversas linguagens de programação que possuem suas próprias engines de regex embutidas. 

Imagine o seguinte cenário:

Você precisa selecionar registros que tenham no meio do texto números de telefone para excluir (moderação automática).

Exemplo de formatos: 9933459879 93345987(21)932324343(21)9 3232 4343, etc…

Evento gratuito de tecnologia: inscreva-se na 3ª edição do Dev Summit:

  1. Vem ai a 3ª edição do maior evento hands-on de desenvolvimento de software do Brasil. Vamos apresentar tecnologias, ferramentas e práticas em mais de 20 horas em sessões ao vivo com profissionais de referência em Full Stack, Flutter, Angular, React e muito mais.
  2.  



    Como você resolveria este problema?

    Resposta curta: O uso de uma query parecida com esta:

     

    select * from tabela where comentario REGEXP '.*[0-9]{2}9[0-9]{8}.*|.*[0-9]{2} 9 [0-9]{4} [0-9]{4}.*';
    

    Vamos entender mais sobre REGEX no SQL

    Para explicar vamos pegar o mesmo exemplo no inicio do artigo, explorando números de telefones e CPF:

    Se são os próprios usuários que colocam os telefones nos comentários, então não há muito controle sobre o formato.

    Claro que você pode considerar alguns formatos mais comuns. Pelos exemplos, podem ser os 9 dígitos do número do celular, todos juntos ou separados por espaços (9999999999 9999 9999 ou 99999 9999), sendo o DDD opcional.

    Na resposta curtra no inicio deste artigo sugerimos uma regex bem complexa para contemplar estes (e muitos outros) casos, mas a sintaxe de regex suportada pelo MySQL infelizmente é um pouco limitada e não vai suportar todos os recursos propostos, como \d para representar dígitos ou os lookaheads e lookbehinds (os trechos que começam com (?=(?! e (?<!).

    Sendo assim, segue uma alternativa que verifica alguns formatos:

    select * from tabela where comentario REGEXP '(^|[^0-9])(\\(?0?[0-9]{2}\\)?)?9 ?[0-9]{4} ?[0-9]{4}([^0-9]|$)';

    (^|[^0-9]):| significa “ou”. Portanto, este trecho significa “início da string” (^ou “qualquer coisa que não seja número” (o [^ significa que não quero o que vem depois, ou seja, não quero 0-9 – nenhum dígito de zero a 9).

    Isso garante que até aqui eu posso estar no início da string (vai que o telefone já está no início), ou tem qualquer caractere que não seja um número (evitando que pegue casos como 3393333333333333333).

    Em seguida temos (\\(?0?[0-9]{2}\\)?)?. Vamos por partes, de dentro para fora:

    • 0?[0-9]{2} – um zero opcional (0? – o ? indica “zero ou uma ocorrência“, o que é o mesmo que dizer “opcional”), seguido de 2 dígitos ([0-9] é qualquer dígito de 0 a 9, e {2} que dizer “duas ocorrências“), pois o DDD pode ser escrito como 11 ou 011
    • \\(? e \\)? – os parênteses podem ser opcionais. Fiz assim porque somente os parênteses () têm significado especial em regex, pois servem para agrupar sub-expressões. Por isso temos que escapá-los com \\.
    • por fim, todo este trecho está entre parênteses (ou seja, agrupado em uma única sub-expressão), e o ? no final faz todo este trecho ser opcional.

    Ou seja, o DDD é opcional.

    Depois temos 9 ?, que é o número 9 seguido de um espaço opcional (repare que há um espaço antes do ?, ou seja, o espaço que é opcional, não o 9). Aqui estou assumindo que serão somente números de celular que começam com 9 – lembrando que futuramente poderemos ter celulares que começam com 8, 7, etc, então fica a seu critério deixar sempre 9 ou trocar para [0-9] (ou [7-9] se quiser que comece somente com 7, 8 ou 9, etc).

    Depois temos [0-9]{4} (4 dígitos), seguido de um espaço opcional, mais 4 dígitos.

    LEIA TAMBÉM: 

    E por fim, temos ([^0-9]|$): qualquer caractere que não seja número ou o final da string ($). Isso também garante que não vai pegar mais dígitos que o necessário, evitando que pegue por exemplo 3393333333333333333.

    Neste SQLfiddle você pode ver esta query funcionando.


    Se quiser incrementar, pode colocar o separador como hífen ou espaço, por exemplo, assim seriam aceitos números como 9 9123-4567 ou 99123-4567. Basta trocar os espaços opcionais por [ \\-]?(um espaço ou um hífen, opcional). A regex ficaria assim:

    select * from tabela where comentario REGEXP
    '(^|[^0-9])(\\(?0?[0-9]{2}\\)?)?9[ \\-]?[0-9]{4}[ \\-]?[0-9]{4}([^0-9]|$)';

    Veja aqui ela funcionando.

    Também é possível adicionar um espaço opcional depois do DDD:

    (^|[^0-9])(\\(??[0-9]{2}\\)?)? ?9 ?[0-9]{4} ?[0-9]{4}([^0-9]|$)
                                   

    Sem esse espaço, o DDD é ignorado para casos como (11) 9 9123 4567 – somente o número de telefone é capturado pela regex, mas o DDD não, veja aqui um exemplo. Já colocando o espaço opcional, o DDD também é capturado, veja aqui a diferença.

    Outro detalhe é que estamos considerando apenas números de celular. Mas ainda existem números de telefone com 8 dígitos (em residências nem tanto, mas em empresas, ainda é bem comum). Se também quiser considerar estes números, basta colocar o 9 como opcional:

    (^|[^0-9])(\\(??[0-9]{2}\\)?)? ?9? ?[0-9]{4} ?[0-9]{4}([^0-9]|$)
                                     ^^

    Só lembrando que, como são os usuários que digitam seus números, sempre poderá haver algum formato estranho que você não previu. E quanto mais possibilidades, mais complexa vai ficando a regex.

    Por exemplo, a regex que sugeri só considera um espaço em branco opcional. Mas se quiser que tenha mais de um espaço, pode trocar o ? por * (zero ou mais ocorrências), ou limitar a quantidade com chaves (por exemplo, {0,3} limita entre 0 a 3 ocorrências).

    Há também a possibilidade de um CPF ser confundido com um telefone, já que ambos podem ser escritos sem nenhum separador (43912341222 pode ser tanto um CPF quanto um DDD + telefone – mesmo que as pessoas costumem escrever o CPF como 439.123.412-22, quem garante que não vai ter um caso assim? De qualquer forma, veja se isso se aplica aos seus casos). Enfim, regex não é um negócio tão “mágico” assim, e cabe a você avaliar se vai confiar tanto nela ao ponto de remover automaticamente qualquer coisa que ela pegar…

    LEIA TAMBÉM: 

    Conclusão

    A maioria das linguagens de programação possui engines de regex mais modernas, o que permite escrever expressões como essa, por exemplo (que usa \b para delimitar os telefones, sem precisar usar o “truque” que fiz acima com (^|[^0-9]) e ([^0-9]|$), além de usar \d como um atalho para [0-9] e \s para espaços).

    Outros tutoriais e dicas sobre PHP:

    Leia mais sobre Banco de Dados

     

    votes
    Article Rating

    LEIA TAMBÉM:  MySQLi vs PDO - qual o mais recomendado para usar?

    E-Zine Ramos da Informática

    * indicates required

    View previous campaigns.