Clique no banner para conhecer e adquirir o meu treinamento de Bancos de Dados no Azure

SQL Server 2025 – Finalmente temos expressão regular (regex) nativamente

Visualizações: 43 views
Tempo de Leitura: 20 minutos

Fala pessoal! Tudo bem com vocês?
Hoje o assunto será dos mais interessantes: Expressões regulares!

Introdução

Quem me acompanha há mais tempo sabe que eu sempre bati na tecla de que o suporte a expressões regulares no SQL Server era uma lacuna imensa. Em 2018, eu escrevi o artigo SQL Server – Como utilizar expressões regulares (RegExp) no seu banco de dados, detalhando como contornar essa limitação usando o LIKE do WHERE (funciona, mas é limitado), SQLCLR, criando uma DLL em C# e embarcando dentro do banco de dados para termos o poder do Regex ou utilizando OLE Automation.

Funcionou por anos, mas sempre com aquele “overhead” de contexto entre o motor do SQL e o CLR, além das restrições de segurança (PERMISSION_SET) que muitas vezes barravam a implementação em ambientes mais restritos, como o Azure SQL Database.

Com a chegada do SQL Server 2025 e as atualizações de maio de 2024 no Azure SQL Database, a Microsoft finalmente ouviu a comunidade e trouxe o suporte nativo para expressões regulares. Estamos falando de funções integradas diretamente no motor, otimizadas e prontas para o uso sem a necessidade de configurações externas.

Neste post, vou demonstrar essas novas funções, entender a sintaxe e, claro, comparar com o nosso antigo método via SQLCLR para ver o que realmente muda na vida do DBA e do Desenvolvedor.

As Novas Funções de Expressão Regular

Diferente do antigo LIKE (que é extremamente limitado), as novas funções utilizam o padrão RE2, desenvolvido pelo Google, permitindo buscas complexas, extrações e substituições de strings de forma muito mais performática.

As funções introduzidas, conforme podemos observar na documentação oficial, são:

Funções Escalares:

  • REGEXP_LIKE: Valida se a string bate com o padrão (Retorna BIT).
  • REGEXP_COUNT: Conta as ocorrências do padrão.
  • REGEXP_INSTR: Localiza a posição de um padrão.
  • REGEXP_REPLACE: Substitui padrões por novos textos.
  • REGEXP_SUBSTR: Extrai uma parte da string.

Funções de Tabela (Rowsets):

  • REGEXP_MATCHES: Extrai todas as ocorrências de um padrão e as retorna como linhas de uma tabela, inclusive separando os grupos de captura.
  • REGEXP_SPLIT_TO_TABLE: Divide uma string em várias linhas usando um padrão Regex como delimitador (o STRING_SPLIT dos sonhos).

Tabela de Comparação de Funcionalidades

Abaixo, preparei uma tabela comparativa para facilitar a visualização de como cada função se comporta em relação aos parâmetros comuns:

Função Tipo Objetivo Principal Retorno Principal
REGEXP_LIKE Escalar Validar se um texto segue um padrão (Email, CPF, etc.) Boolean (1 ou 0)
REGEXP_COUNT Escalar Contar quantas vezes um padrão ocorre em um texto Integer
REGEXP_INSTR Escalar Localizar a posição inicial/final de um padrão Integer (Posição)
REGEXP_REPLACE Escalar Substituir ocorrências de um padrão por um novo texto VARCHAR / NVARCHAR
REGEXP_SUBSTR Escalar Extrair uma parte específica (substring) baseada no padrão VARCHAR / NVARCHAR
REGEXP_MATCHES Tabular Extrair todas as ocorrências e seus grupos de captura Table (Rowset)
REGEXP_SPLIT_TO_TABLE Tabular Dividir uma string em várias linhas usando Regex como delimitador Table (Rowset)
Observação: As funções tabulares (REGEXP_MATCHES e REGEXP_SPLIT_TO_TABLE) são processadas via CROSS APPLY. Isso permite que o SQL Server utilize paralelismo de forma muito mais eficiente do que as antigas funções de split baseadas em loops ou recursividade utilizando T-SQL puro, como a que eu compartilhei no artigo SQL Server – Quebrando strings em sub-strings utilizando separador (Split string).
IMPORTANTE: O suporte nativo exige que o nível de compatibilidade do banco de dados (Compatibility Level) esteja configurado para 160 (SQL Server 2022) ou superior. Especificamente para utilizar a função REGEXP_LIKE, o nível de compatibilidade deve ser o 170 (SQL Server 2025). Verifique sempre antes de testar em produção!

Para verificar qual o nível de compatibilidade das suas bases de dados, você pode utilizar o script T-SQL abaixo:

Caso você queira alterar o nível de compatibilidade para 170 (SQL Server 2025) e você possa utilizar as funções de expressão regular, utilize o comando abaixo:

ATENÇÃO: As funções de expressão regular ainda tem limitações em relação aos tipos VARCHAR(MAX) e NVARCHAR(MAX). Se você tentar utilizá-las, poderá receber uma mensagem de erro como essa:
Msg 19304, Level 16, State 5, Line 35
Currently, ‘REGEXP_MATCHES’ function does not support NVARCHAR(max)/VARCHAR(max) inputs.

Por que RE2 e não PCRE ou POSIX puro?

A maioria das bibliotecas de Regex (como a PCRE usada no PHP/Python ou a própria biblioteca do .NET que usamos no SQLCLR) utiliza um algoritmo de Backtracking.

Isso é poderoso, mas perigoso para um banco de dados. Se um desenvolvedor escreve uma expressão regular mal formatada, ele pode causar o que chamamos de ReDoS (Regular Expression Denial of Service). O motor de busca entra em um loop exponencial de tentativas (catastrophic backtracking), trava um núcleo de CPU em 100% e pode derrubar a performance da instância inteira.

O RE2 foi projetado para ser seguro:

  • Tempo Linear: O RE2 garante que o tempo de execução seja linear em relação ao tamanho da string de entrada ($O(n)$).
  • Previsibilidade: Ele não permite retrocessos (backtracking), o que significa que ele nunca vai “travar” seu servidor, não importa quão complexa seja a expressão.
  • Segurança de Memória: O consumo de memória é controlado e finito, ideal para quem gerencia Buffer Pool e Memory Clerks.

Quando olhamos para os Wait Types, o uso do RE2 nativo tende a concentrar o esforço puramente em SOS_SCHEDULER_YIELD (se a consulta for muito longa) em vez de travar recursos externos ou gerar eventos de WAIT de CLR_AUTO_EVENT.

Quer mais informações?

Sintaxe básica:

Metacaractere Descrição
. Corresponde a qualquer caractere único (muitas aplicações excluem quebras de linha, e exatamente quais caracteres são considerados quebras de linha é específico do “sabor” do regex, da codificação de caracteres e da plataforma, mas é seguro assumir que o caractere de avanço de linha (line feed) está incluído). Dentro de expressões de colchetes POSIX, o caractere ponto corresponde a um ponto literal. Por exemplo, a.c corresponde a “abc”, etc., mas [a.c] corresponde apenas a “a”, “.”, ou “c”.
[ ] Uma expressão de colchetes. Corresponde a um único caractere contido dentro dos colchetes. Por exemplo, [abc] corresponde a “a”, “b”, ou “c”, e [a-z] especifica um intervalo que corresponde a qualquer letra minúscula de “a” a “z”. Essas formas podem ser misturadas: [abcx-z] corresponde a “a”, “b”, “c”, “x”, “y”, ou “z”, assim como [a-cx-z].

O caractere é tratado como um caractere literal se for o último ou o primeiro caractere dentro dos colchetes: [abc-], [-abc]. O caractere ] pode ser incluído em uma expressão de colchetes se for o primeiro caractere: []abc]. A expressão de colchetes também pode conter classes de caracteres, classes de equivalência e caracteres de agrupamento (collating characters).

[^ ] Corresponde a um único caractere que não está contido dentro dos colchetes. Por exemplo, [^abc] corresponde a qualquer caractere que não seja “a”, “b”, ou “c”, e [^a-z] corresponde a qualquer caractere único que não seja uma letra minúscula de “a” a “z”. Essas formas podem ser misturadas: [^abcx-z] corresponde a qualquer caractere exceto “a”, “b”, “c”, “x”, “y”, ou “z”.

O caractere é tratado como um caractere literal se for o último caractere ou o primeiro caractere após o ^: [^abc-], [^-abc]. O caractere ] é tratado como um caractere literal se for o primeiro caractere após o ^: [^]abc]. A expressão também pode conter classes de caracteres, classes de equivalência e caracteres de agrupamento.

^ Corresponde à posição inicial dentro da string, se for o primeiro caractere da expressão regular.
$ Corresponde à posição final da string, se for o último caractere da expressão regular.
* Corresponde ao elemento anterior zero ou mais vezes. Por exemplo, ab*c corresponde a “ac”, “abc”, “abbbc”, etc. [xyz]* corresponde a “”, “x”, “y”, “z”, “zx”, “zyx”, “xyzzy”, e assim por diante.

Exemplos:

  • .at corresponde a qualquer string de três caracteres terminada em “at”, incluindo “hat”, “cat” e “bat”.
  • [hc]at corresponde a “hat” e “cat”.
  • [^b]at corresponde a todas as strings identificadas por .at, exceto “bat”.
  • ^[hc]at corresponde a “hat” e “cat”, mas apenas no início da string ou linha.
  • [hc]at$ corresponde a “hat” e “cat”, mas apenas no final da string ou linha.
  • \[.\] corresponde a qualquer caractere único cercado por “[” e “]” já que os colchetes estão escapados; por exemplo: “[a]” e “[b]”.

Classes POSIX:

Classe POSIX Similar a Significado
[:upper:] [A-Z] Letras maiúsculas
[:lower:] [a-z] Letras minúsculas
[:alpha:] [A-Za-z] Letras maiúsculas e minúsculas
[:digit:] [0-9] Dígitos
[:xdigit:] [0-9A-Fa-f] Dígitos hexadecimais
[:alnum:] [A-Za-z0-9] Dígitos, letras maiúsculas e minúsculas
[:punct:] Pontuação (todos os caracteres gráficos, exceto letras e dígitos)
[:blank:] [ \t] Espaço e Tabulação
[:space:] [ \t\n\r\f\v] Caracteres de espaço em branco (vazio)
[:cntrl:] Caracteres de controle
[:graph:] Caracteres gráficos (exclui espaços)
[:print:] [[:graph:] ] Caracteres imprimíveis (inclui espaços)
ALERTA DE LIMITAÇÃO TÉCNICA: Como o RE2 prioriza a segurança e a performance linear, ele NÃO SUPORTA algumas funcionalidades comuns em outros motores, como Backreferences (referências retroativas como \1) e Lookaround (lookahead/lookbehind). Se o seu script SQLCLR dependia disso, você precisará repensar a lógica ao migrar para o nativo.

Exemplos práticos de cada função para expressão regular

Para facilitar o entendimento, vou demonstrar alguns exemplos de cada função para entenderem algumas aplicações úteis no dia a dia.

REGEXP_LIKE

Valida se a string bate com o padrão (Retorna BIT).

A Sintaxe é REGEXP_LIKE ( string_expression, pattern_expression [ , flags ] ), onde as valores para as flags são:

  • i: Não diferencia maiúsculas de minúsculas (padrão false)
  • m: Modo de várias linhas: “^” e “$” corresponder à linha de início/término, além do texto de início/término (padrão false)
  • s: Permitir “.” correspondência “\n” (padrão false)
  • c: Diferencia maiúsculas de minúsculas (padrão true)

Documentação oficial: REGEXP_LIKE

Exemplo 1: Validando máscaras de CEP

Exemplo 2: Validando se a máscara do CEP corresponde ao valor testado

Exemplo 3: Validando CPF e CNPJ

Exemplo 4: Buscando palavras repetidas
Aqui encontramos uma barreira do dialeto RE2, que é a ausência de Backtracking. Com a função do SQLCLR, que utiliza o dialeto .NET, eu poderia utilizar expressões regulares para buscar palavras repetidas através da expressão \1, como no exemplo abaixo:

Se tentarmos adaptar esse código para expressão regular nativa, ficaria algo como:

E tentar fazer isso, vai gerar uma mensagem de erro como essa:

Msg 19300, Level 16, State 1, Line 210
An invalid Pattern ‘\b(\w+)\s+\1\b’ was provided. Error ‘invalid escape sequence: \1’ occurred during evaluation of the Pattern.

Utilizando o dialeto RE2, não é possível fazer a mesma coisa utilizanos apenas expressão regular. Nesse caso, esbarramos em uma limitação técnica do motor de processamento das expressões regulares do SQL Server 2025 e Azure SQL Database.

Uma alternativa para resolver esse problema, é utilizando o STRING_SPLIT:

Exemplo 5: Busca nomes que contenham pelo menos um espaço entre caracteres alfabéticos

Exemplo 6: Cidades que começam ou terminam com vogais

Exemplo 7: Pessoas cujo primeiro nome possuem acentos ou caracteres especiais

Exemplo 8: Case sensitive e insensitive
Por padrão, as expressões regulares são sempre case sensitive por padrão, mas você pode controlar esse comportamento usando flags.

Forçar consulta com case sensitive e pesquisa por “mountain”:

Forçar consulta com case INsensitive e pesquisa por “mountain”:

Vale lembrar que o REGEXP_LIKE pesquisa em qualquer trecho da string, não apenas no começo:

REGEXP_INSTR

Retorna a posição inicial ou final da subcadeia de caracteres correspondente, dependendo do valor do argumento return_option.

A sintaxe é REGEXP_INSTR ( string_expression, pattern_expression [ , start [ , occurrence [ , return_option [ , flags [ , group ] ] ] ] ] ).

Documentação oficial: REGEXP_INSTR

Exemplo 1: Recuperar trechos onde a palavra “Dirceu” aparece no texto

Exemplo 2: Localizar o número no final da string composto por 2 algarismos (Ex: “42”, “58”, “70”)

Exemplo 3: Posição de múltiplos sufixos em endereços

Exemplo 4: Recuperar o nome da rua e ignorar o número

Exemplo 5: Identificando apartamentos ou unidades

REGEXP_COUNT

Conta o número de vezes que um padrão de expressão regular é correspondido em uma cadeia de caracteres.

A sintaxe é REGEXP_COUNT ( string_expression, pattern_expression [ , start [ , flags ] ] ), onde as valores para as flags são:

  • i: Não diferencia maiúsculas de minúsculas (padrão false)
  • m: Modo de várias linhas: “^” e “$” corresponder à linha de início/término, além do texto de início/término (padrão false)
  • s: Permitir “.” correspondência “\n” (padrão false)
  • c: Diferencia maiúsculas de minúsculas (padrão true)

Documentação oficial: https://learn.microsoft.com/pt-br/sql/t-sql/functions/regexp-count-transact-sql

Exemplo 1: Conte quantas vezes a letra a aparece em cada nome do produto.

Exemplo 2: Retorna os produtos que terminam com “tire” ou “ube”, ignorando maiúsculo e minúsculo.

Exemplo 3: Contando quantidade de palavras e nomes com iniciais

REGEXP_REPLACE

Retorna uma string modificada, em que a ocorrência do padrão de expressão regular foi encontrada. Se nenhuma correspondência for encontrada, a função retornará a string original.

A sintaxe é REGEXP_REPLACE( string_expression, pattern_expression [ , string_replacement [ , start [ , occurrence [ , flags ] ] ] ] ), onde as valores para as flags são:

  • i: Não diferencia maiúsculas de minúsculas (padrão false)
  • m: Modo de várias linhas: “^” e “$” corresponder à linha de início/término, além do texto de início/término (padrão false)
  • s: Permitir “.” correspondência “\n” (padrão false)
  • c: Diferencia maiúsculas de minúsculas (padrão true)

Documentação oficial: https://learn.microsoft.com/pt-br/sql/t-sql/functions/regexp-replace-transact-sql

Exemplo 1: Mascarando telefones e emails

Exemplo 2: Retornar apenas os números de uma string

Exemplo 3: Substituindo várias expressões para normalizar um nome

Exemplo 4: Transformações diversas

Exemplo 5: Retornar texto sem as tags HTML

REGEXP_SUBSTR

Retorna uma ocorrência de uma string que corresponde ao padrão de expressão regular. Se nenhuma correspondência for encontrada, ela retornará NULL.

A sintaxe é REGEXP_SUBSTR( string_expression, pattern_expression [ , string_replacement [ , start [ , occurrence [ , flags ] ] ] ] ), onde as valores para as flags são:

  • i: Não diferencia maiúsculas de minúsculas (padrão false)
  • m: Modo de várias linhas: “^” e “$” corresponder à linha de início/término, além do texto de início/término (padrão false)
  • s: Permitir “.” correspondência “\n” (padrão false)
  • c: Diferencia maiúsculas de minúsculas (padrão true)

Documentação oficial: https://learn.microsoft.com/pt-br/sql/t-sql/functions/regexp-substr-transact-sql

Exemplo 1: Recuperar partes de uma string

Resultado:

Exemplo 2: Retornar um número formado por 2 algarismos na string

Exemplo 3: Extraindo apenas o nome do Usuário do Email

Minha recomendação ao usar o REGEXP_SUBSTR é: Sempre valide o NULL antes de processar.

Diferente do SUBSTRING tradicional, que pode retornar uma string vazia ou erro se os índices estiverem errados, o REGEXP_SUBSTR retornará NULL se o padrão não for encontrado. Use isso a seu favor em suas queries de limpeza: WHERE REGEXP_SUBSTR(coluna, padrao) IS NOT NULL

Isso garante que sua extração seja limpa e que você não tente realizar operações matemáticas ou de JOIN em valores inexistentes.

REGEXP_MATCHES

Retorna uma tabela de substrings capturadas que correspondem a um padrão de expressão regular a uma string. Se nenhuma correspondência for encontrada, a função não retornará nenhuma linha.

A sintaxe é REGEXP_MATCHES( string_expression, pattern_expression [ , flags ] ), onde as valores para as flags são:

  • i: Não diferencia maiúsculas de minúsculas (padrão false)
  • m: Modo de várias linhas: “^” e “$” corresponder à linha de início/término, além do texto de início/término (padrão false)
  • s: Permitir “.” correspondência “\n” (padrão false)
  • c: Diferencia maiúsculas de minúsculas (padrão true)

Documentação oficial: https://learn.microsoft.com/pt-br/sql/t-sql/functions/regexp-matches-transact-sql

Exemplo 1: Retornando uma tabela com as hashtags de uma frase.

Exemplo 2: Retornando uma tabela de protocolos na string

Exemplo 3: Recuperando todos os emails de uma string

Exemplo 4: Consultas variadas

Exemplo 5: Extrai as cores a partir de uma string

REGEXP_SPLIT_TO_TABLE

Retorna uma tabela de strings dividida, delimitada pelo padrão regex. Se não houver correspondência com o padrão, a função retornará a string.

A sintaxe é REGEXP_SPLIT_TO_TABLE( string_expression, pattern_expression [ , flags ] ), onde as valores para as flags são:

  • i: Não diferencia maiúsculas de minúsculas (padrão false)
  • m: Modo de várias linhas: “^” e “$” corresponder à linha de início/término, além do texto de início/término (padrão false)
  • s: Permitir “.” correspondência “\n” (padrão false)
  • c: Diferencia maiúsculas de minúsculas (padrão true)

Documentação oficial: https://learn.microsoft.com/pt-br/sql/t-sql/functions/regexp-split-to-table-transact-sql

Exemplo 1: Retornando uma tabela com cada palavra do texto como uma linha

Exemplo 2: Quebra o texto em várias linhas com múltiplos delimitadores

Essa função se parece bastante com a STRING_SPLIT:

Entretanto, ela tem uma diferença sutil, que é o fato da função STRING_SPLIT aceitar somente 1 caractere separador, enquanto a REGEXP_SPLIT_TO_TABLE não tem esse limite.

Consigo separar minha string utilizando 3 caracteres como separador (-=-):

Mas ao tentar fazer a mesma coisa utilizando o STRING_SPLIT, nos deparamos com essa mensagem de erro:

Msg 214, Level 16, State 11, Line 1
Procedure expects parameter ‘separator’ of type ‘nchar(1)/nvarchar(1)’.

Comparativo: Nativo vs SQLCLR

Agora chegamos no ponto que todos queriam saber: Vale a pena migrar o que já temos em SQLCLR para as funções nativas?

Sintaxe e Manutenção

No SQLCLR, cada função precisava ser mapeada para um método C#. Se você precisasse de um comportamento novo, precisava recompilar a DLL, fazer o deploy no servidor (muitas vezes dependendo de privilégios de sysadmin) e torcer para não haver problemas de versão do .NET Framework. Com as funções nativas, a sintaxe é padrão T-SQL. Qualquer desenvolvedor SQL consegue ler e entender o que está acontecendo sem precisar abrir um projeto no Visual Studio.

Performance e Resource Wait Types

Quando utilizamos SQLCLR, o SQL Server precisa gerenciar o AppDomain e realizar o “Marshaling” de dados entre o motor relacional e a CLR. Isso gera esperas de processamento que muitas vezes se traduzem em CLR_AUTO_EVENT ou CLR_MANUAL_EVENT.

Com as funções nativas:

  • CPU: O processamento ocorre dentro do próprio motor de execução do SQL, permitindo melhor paralelismo.
  • Memória: Não há necessidade de reservar memória adicional para o Garbage Collector da CLR.
  • IOPS: Embora o impacto direto em IO seja menor, a eficiência na leitura de colunas LOB (VARCHAR(MAX)) com Regex nativo é superior por não exigir a cópia do dado para o ambiente gerenciado.

Um ponto de atenção: Tanto o SQLCLR quanto o Regex Nativo não são SARGABLE. Ou seja, se você colocar um WHERE REGEXP_LIKE(…) em uma coluna, o SQL Server provavelmente fará um Index Scan ou Table Scan. O regex é processado linha a linha.

Além disso, ao usar REGEXP_MATCHES ou REGEXP_SPLIT_TO_TABLE, o SQL Server não precisa realizar o “Marshaling” (cópia de dados) para o ambiente .NET do SQLCLR e isso melhora significamente a performance da função nativa.

Comparação de performance entre SQLCLR e as funções nativas:

Se quiser criar as funções no seu ambiente, é só executar o script abaixo:

Para conhecer melhor essas funções e sua sintaxe, acessem o post SQL Server – Como utilizar expressões regulares (RegExp) no seu banco de dados.
DICA DE DBA: Para ganhar performance, use o Regex em conjunto com filtros simples.

Por exemplo: WHERE [Coluna] LIKE ‘192%’ AND REGEXP_LIKE([Coluna], ‘padrão_complexo’). O LIKE simples faz o primeiro filtro usando índice, e o Regex processa apenas o que sobrar.

Para demonstrar que o LIKE é mais rápido que REGEXP, mesmo utilizando LIKE ‘%texto%’, podemos utilizar o script abaixo:

Resultado:

Segurança

Este é o maior ganho. Muitas instâncias de Azure SQL Managed Instance ou ambientes On-Premise extremamente travados não permitem a execução de clr enabled. Com as funções nativas, essa barreira desaparece. Você tem o poder do Regex mantendo a instância em SURFACE AREA CONFIGURATION segura.

DICA DE PERFORMANCE: Mesmo sendo nativo, o Regex é custoso para a CPU. Evite utilizar funções de Regex em cláusulas WHERE de tabelas com milhões de linhas sem filtros prévios que utilizem índices. O Regex nativo não torna sua busca “Sargable” por mágica! O índice ainda é seu melhor amigo.

Conclusão

A introdução das funções de Regex nativas no SQL Server 2025 é um marco de maturidade para a plataforma. Elas eliminam a necessidade de “gambiarras” técnicas com SQLCLR para tarefas cotidianas de manipulação de strings e trazem uma performance muito mais previsível e integrada. Se você está começando um projeto novo ou planejando o upgrade para o SQL 2025, o uso dessas funções é o caminho oficial e recomendado.

Espero que tenham gostado dessa dica, um grande abraço e até a próxima!