Fonética + SQL Server
Autor: Eduardo Spaki | Publicado: 2011-07-21T22:36:00-03:00

     fonética       (francês phonétique)    s. f.    1. [Linguística]&#160; Parte da linguística que trata dos sons articulados, considerados do ponto de vista físico, acústico e articulatório, como elementos dos vocábulos.   Essa é a definição do dicionário, mas basicamente a fonética é um &quot;ramo&quot; que estuda a pronuncia de uma palavra, visto que há diferenças entre a escrita e a pronuncia. Por exemplo, quantas maneiras podemos escrever o nome Wilham?  Wilian, Willian, Uyliam… Ainda mais no Brasil, e por falar nisso aqui há uma &quot;limitação&quot;, a fonética é fortemente vinculada a língua. No brasil o som da letra &quot;i&quot; é um, nos EUA é outro… Ai!  Voltando aos nomes, no exemplo citado o som pode ser &quot;fielmente&quot; definido como &quot;uiliam&quot;, então, na teoria, teríamos que bolar algum algoritmo para gerar o fonema da palavra, e nesse caso, independente da maneira como o nome for escrito, o resultado fonético seria o mesmo.  Complicado não!? Mas a boa notícia é que existem inúmeros estudos para gerar essa solução. Um deles é o Soundex. Criado em 1918, por Robert C. Russell e Margaret K. Odell. Esse algoritmo gera uma chave baseada na primeira letra e três números, &quot;calculados&quot; pelas letras na sequencia. Nesse algoritmo, o nosso Willian, viraria W450, e suas variações também.  O SQL Server já implementa nativamente o Soundex. Veja os exemplos a seguir:    Mas como &quot;alegria de pobre dura pouco&quot;… Há alguns poréns, primeiramente o Soundex é um algoritmo que funciona muito bem com a língua inglesa. Como disse anteriormente a fonética é extremamente ligada a sua língua. Palavras compostas e alguns casos da língua portuguesa não se dão muito bem com essa implementação do Soundex. Veja:    Vamos superar esse #mimimi!? Primeiramente vamos implementar o Soundex em C# e depois implementar adaptações necessárias para a nossa língua.  A base para isso foi tirada da revista Mundo .NET número 26, Casos de sucesso – Pesquisa Fonética em português; Tribunal de Justiça de Mato Grosso do Sul. Nesse caso o tribunal tinha problemas em localizar pessoas no sistema pelo nome. Antes da solução era preciso de informar o nome exato para o sistema buscar as informações.   Vamos definir alguns métodos para a implementação básica do Soundex:         // -&gt; Obtem o soudex de uma string    private static string GetSoundex(string value)    {        value = value.ToUpper(); // -&gt; Normaliza letras em Maiuscula        var result = new StringBuilder();        foreach (char item in value)            if (char.IsLetter(item)) // -&gt; Limpa String                AddCharacter(result, item); // -&gt; Adiciona um char Soudex        // -&gt; Limpa as exceções geradas        result.Replace(&quot;.&quot;, string.Empty);        // -&gt; Arruma o tamanho no padrão soudex         //      4 caracteres.        FixLength(result);        return result.ToString();    }    // -&gt; Preenche com 0 nos casos menor q quatro,    //      ou pega os 4 primeiros caracteres.    private static void FixLength(StringBuilder result)    {        var length = result.Length;        if (length &lt; 4)            result.Append(new string('0', 4 - length));        else            result.Length = 4;    }    // -&gt; Adiciona o caractere soudex, cuidando para não repetir.    private static void AddCharacter(StringBuilder result, char item)    {        if (result.Length == 0)        {            result.Append(item);        }        else        {            var code = GetSoundexDigit(item);            if (code != result[result.Length - 1].ToString())                result.Append(code);        }    }    // -&gt; Algoritmo de substituição padrão Soudex.    private static string GetSoundexDigit(char item)    {        var charString = item.ToString();        if (&quot;BFPV&quot;.Contains(charString))            return &quot;1&quot;;        else if (&quot;CGJKQSXZ&quot;.Contains(charString))            return &quot;2&quot;;        else if (&quot;DT&quot;.Contains(charString))            return &quot;3&quot;;        else if (&quot;L&quot;.Contains(charString))            return &quot;4&quot;;        else if (&quot;MN&quot;.Contains(charString))            return &quot;5&quot;;        else if (&quot;R&quot;.Contains(charString))            return &quot;6&quot;;        else            return &quot;.&quot;;    }.csharpcode, .csharpcode pre{	font-size: small;	color: black;	font-family: consolas, "Courier New", courier, monospace;	background-color: #ffffff;	/*white-space: pre;*/}.csharpcode pre { margin: 0em; }.csharpcode .rem { color: #008000; }.csharpcode .kwrd { color: #0000ff; }.csharpcode .str { color: #006080; }.csharpcode .op { color: #0000c0; }.csharpcode .preproc { color: #cc6633; }.csharpcode .asp { background-color: #ffff00; }.csharpcode .html { color: #800000; }.csharpcode .attr { color: #ff0000; }.csharpcode .alt {	background-color: #f4f4f4;	width: 100%;	margin: 0em;}.csharpcode .lnum { color: #606060; }Como a língua portuguesa dispões de muita sujeira e quinquilharia acentos e símbolos, temos de tratá-las com as funções a seguir:      // -&gt; Recebe várias letras, separadas por virgula, para serem substituídas por uma letra específica.    private static string ReplaceMultiplo(string value, string from, string to)    {        var fromPattern = string.Format(&quot;({0})&quot;, string.Join(&quot;)|(&quot;, from.Split(','))).Replace(&quot;.&quot;, @&quot;\.&quot;);        return Regex.Replace(value, fromPattern, to);    }    private static string GetSoundexBR(string value)    {        value = value.ToUpper();        value = ReplaceMultiplo(value, &quot;Á,À,Ã,Â,Ä&quot;, &quot;A&quot;);        value = ReplaceMultiplo(value, &quot;É,È,Ê,Ë&quot;, &quot;E&quot;);        value = ReplaceMultiplo(value, &quot;Í,Ì,Î,Ï&quot;, &quot;I&quot;);        value = ReplaceMultiplo(value, &quot;Ó,Ò,Ö,Õ,Ô&quot;, &quot;O&quot;);        value = ReplaceMultiplo(value, &quot;Ú,Ù,Û,Ü&quot;, &quot;U&quot;);        value = ReplaceMultiplo(value, &quot;.,-,&quot;, string.Empty);        value = ReplaceMultiplo(value, &quot;0,1,2,3,4,5,6,7,8,9,0&quot;, string.Empty);        value = ReplaceMultiplo(value, &quot;Y&quot;, &quot;I&quot;);        value = ReplaceMultiplo(value, &quot;PH&quot;, &quot;F&quot;);        value = ReplaceMultiplo(value, &quot;GE&quot;, &quot;JE&quot;);        value = ReplaceMultiplo(value, &quot;GI&quot;, &quot;JI&quot;);        value = ReplaceMultiplo(value, &quot;CA&quot;, &quot;KA&quot;);        value = ReplaceMultiplo(value, &quot;CE&quot;, &quot;SE&quot;);        value = ReplaceMultiplo(value, &quot;CI&quot;, &quot;SI&quot;);        value = ReplaceMultiplo(value, &quot;CO&quot;, &quot;KO&quot;);        value = ReplaceMultiplo(value, &quot;CU&quot;, &quot;KU&quot;);        value = ReplaceMultiplo(value, &quot;Ç&quot;, &quot;S&quot;);        value = ReplaceMultiplo(value, &quot;WAS&quot;, &quot;WS&quot;);        value = ReplaceMultiplo(value, &quot;WA&quot;, &quot;VA&quot;);        value = ReplaceMultiplo(value, &quot;WO&quot;, &quot;VO&quot;);        value = ReplaceMultiplo(value, &quot;WU&quot;, &quot;VU&quot;);        value = ReplaceMultiplo(value, &quot;WI&quot;, &quot;UI&quot;);        // -&gt; Depois de tratar a lingua portuguesa        //      Geramos o soudex padrão.        value = GetSoundex(value);         return value;    }.csharpcode, .csharpcode pre{	font-size: small;	color: black;	font-family: consolas, "Courier New", courier, monospace;	background-color: #ffffff;	/*white-space: pre;*/}.csharpcode pre { margin: 0em; }.csharpcode .rem { color: #008000; }.csharpcode .kwrd { color: #0000ff; }.csharpcode .str { color: #006080; }.csharpcode .op { color: #0000c0; }.csharpcode .preproc { color: #cc6633; }.csharpcode .asp { background-color: #ffff00; }.csharpcode .html { color: #800000; }.csharpcode .attr { color: #ff0000; }.csharpcode .alt {	background-color: #f4f4f4;	width: 100%;	margin: 0em;}.csharpcode .lnum { color: #606060; }.csharpcode, .csharpcode pre{	font-size: small;	color: black;	font-family: consolas, "Courier New", courier, monospace;	background-color: #ffffff;	/*white-space: pre;*/}.csharpcode pre { margin: 0em; }.csharpcode .rem { color: #008000; }.csharpcode .kwrd { color: #0000ff; }.csharpcode .str { color: #006080; }.csharpcode .op { color: #0000c0; }.csharpcode .preproc { color: #cc6633; }.csharpcode .asp { background-color: #ffff00; }.csharpcode .html { color: #800000; }.csharpcode .attr { color: #ff0000; }.csharpcode .alt {	background-color: #f4f4f4;	width: 100%;	margin: 0em;}.csharpcode .lnum { color: #606060; }Agora vem uma parte legal, vamos implementar isso diretamente no SQL Server, usando a CLR do SQL Server, ou seja, a vantagem do SQL Server utilizar o .NET Framework.Para tal, vamos habilitar a execução da CLR executando os comando a seguir no SQL Server:  sp_configure 'clr', 1goRECONFIGUREgo     .csharpcode, .csharpcode pre{	font-size: small;	color: black;	font-family: consolas, "Courier New", courier, monospace;	background-color: #ffffff;	/*white-space: pre;*/}.csharpcode pre { margin: 0em; }.csharpcode .rem { color: #008000; }.csharpcode .kwrd { color: #0000ff; }.csharpcode .str { color: #006080; }.csharpcode .op { color: #0000c0; }.csharpcode .preproc { color: #cc6633; }.csharpcode .asp { background-color: #ffff00; }.csharpcode .html { color: #800000; }.csharpcode .attr { color: #ff0000; }.csharpcode .alt {	background-color: #f4f4f4;	width: 100%;	margin: 0em;}.csharpcode .lnum { color: #606060; }Em seguida crie no Visual Studio um projeto para o SQL Server:Após isso o Visual Studio pede para se conectar a base onde a CLR será implantada:O projeto já vem estruturado para o SQL Server e com exemplos de teste:Nesse projeto existem várias opções prontas para implementar muitas das funcionalidades do banco de dados. Aqui usaremos uma Function:Cada método que deverá ser função tem que ser decorado com o atributo &quot;[Microsoft.SqlServer.Server.SqlFunction]&quot;. Outro ponto importante é que é possível trabalhar com os tipos do SQL Server. Eles são fundamentais para receber entradas e devolver as saídas.Resta fazer os métodos que serão reconhecidas pelo SQL Server, ou seja, decoradas com o atributo citado acima. A classe final fica assim:  using System.Data.SqlTypes;using System.Text.RegularExpressions;using System.Text;using System.Linq;public partial class UserDefinedFunctions{    /*     para ligar o clr no sql server:        sp_configure 'clr', 1        go        RECONFIGURE        go          */    [Microsoft.SqlServer.Server.SqlFunction]    public static SqlString SoundexBR(SqlString value)    {        StringBuilder palavras = new StringBuilder();        value = NormalizarEspacos(value);                var valorNormalizado = value.ToString();        // -&gt; Gera o Soundex de cada palavra da string        valorNormalizado.Split(' ').ToList().ForEach(palavra =&gt; palavras.Append(string.Format(&quot;{0} &quot;, GetSoundexBR(palavra))));        // -&gt; Retorna o tipo string do SQL Server        return new SqlString(palavras.ToString().Trim());    }    // -&gt; Retira espaços excedentes.    [Microsoft.SqlServer.Server.SqlFunction]    public static SqlString NormalizarEspacos(SqlString value)    {        var result = value.ToString().Trim();        while (result.Contains(&quot;  &quot;))            result = result.Replace(&quot;  &quot;, &quot; &quot;);        return new SqlString(result);    }    // -&gt; Recebe várias letras, separadas por virgula, para serem substituídas por uma letra específica.    private static string ReplaceMultiplo(string value, string from, string to)    {        var fromPattern = string.Format(&quot;({0})&quot;, string.Join(&quot;)|(&quot;, from.Split(','))).Replace(&quot;.&quot;, @&quot;\.&quot;);        return Regex.Replace(value, fromPattern, to);    }    private static string GetSoundexBR(string value)    {        value = value.ToUpper();        value = ReplaceMultiplo(value, &quot;Á,À,Ã,Â,Ä&quot;, &quot;A&quot;);        value = ReplaceMultiplo(value, &quot;É,È,Ê,Ë&quot;, &quot;E&quot;);        value = ReplaceMultiplo(value, &quot;Í,Ì,Î,Ï&quot;, &quot;I&quot;);        value = ReplaceMultiplo(value, &quot;Ó,Ò,Ö,Õ,Ô&quot;, &quot;O&quot;);        value = ReplaceMultiplo(value, &quot;Ú,Ù,Û,Ü&quot;, &quot;U&quot;);        value = ReplaceMultiplo(value, &quot;.,-,&quot;, string.Empty);        value = ReplaceMultiplo(value, &quot;0,1,2,3,4,5,6,7,8,9,0&quot;, string.Empty);        value = ReplaceMultiplo(value, &quot;Y&quot;, &quot;I&quot;);        value = ReplaceMultiplo(value, &quot;PH&quot;, &quot;F&quot;);        value = ReplaceMultiplo(value, &quot;GE&quot;, &quot;JE&quot;);        value = ReplaceMultiplo(value, &quot;GI&quot;, &quot;JI&quot;);        value = ReplaceMultiplo(value, &quot;CA&quot;, &quot;KA&quot;);        value = ReplaceMultiplo(value, &quot;CE&quot;, &quot;SE&quot;);        value = ReplaceMultiplo(value, &quot;CI&quot;, &quot;SI&quot;);        value = ReplaceMultiplo(value, &quot;CO&quot;, &quot;KO&quot;);        value = ReplaceMultiplo(value, &quot;CU&quot;, &quot;KU&quot;);        value = ReplaceMultiplo(value, &quot;Ç&quot;, &quot;S&quot;);        value = ReplaceMultiplo(value, &quot;WAS&quot;, &quot;WS&quot;);        value = ReplaceMultiplo(value, &quot;WA&quot;, &quot;VA&quot;);        value = ReplaceMultiplo(value, &quot;WO&quot;, &quot;VO&quot;);        value = ReplaceMultiplo(value, &quot;WU&quot;, &quot;VU&quot;);        value = ReplaceMultiplo(value, &quot;WI&quot;, &quot;UI&quot;);        // -&gt; Depois de tratar a lingua portuguesa        //      Geramos o soudex padrão.        value = GetSoundex(value);         return value;    }    // -&gt; Obtem o soudex de uma string    private static string GetSoundex(string value)    {        value = value.ToUpper(); // -&gt; Normaliza letras em Maiuscula        var result = new StringBuilder();        foreach (char item in value)            if (char.IsLetter(item)) // -&gt; Limpa String                AddCharacter(result, item); // -&gt; Adiciona um char Soudex        // -&gt; Limpa as exceções geradas        result.Replace(&quot;.&quot;, string.Empty);        // -&gt; Arruma o tamanho no padrão soudex         //      4 caracteres.        FixLength(result);        return result.ToString();    }    // -&gt; Preenche com 0 nos casos menor q quatro,    //      ou pega os 4 primeiros caracteres.    private static void FixLength(StringBuilder result)    {        var length = result.Length;        if (length &lt; 4)            result.Append(new string('0', 4 - length));        else            result.Length = 4;    }    // -&gt; Adiciona o caractere soudex, cuidando para não repetir.    private static void AddCharacter(StringBuilder result, char item)    {        if (result.Length == 0)        {            result.Append(item);        }        else        {            var code = GetSoundexDigit(item);            if (code != result[result.Length - 1].ToString())                result.Append(code);        }    }    // -&gt; Algoritmo de substituição padrão Soudex.    private static string GetSoundexDigit(char item)    {        var charString = item.ToString();        if (&quot;BFPV&quot;.Contains(charString))            return &quot;1&quot;;        else if (&quot;CGJKQSXZ&quot;.Contains(charString))            return &quot;2&quot;;        else if (&quot;DT&quot;.Contains(charString))            return &quot;3&quot;;        else if (&quot;L&quot;.Contains(charString))            return &quot;4&quot;;        else if (&quot;MN&quot;.Contains(charString))            return &quot;5&quot;;        else if (&quot;R&quot;.Contains(charString))            return &quot;6&quot;;        else            return &quot;.&quot;;    }};.csharpcode, .csharpcode pre{	font-size: small;	color: black;	font-family: consolas, "Courier New", courier, monospace;	background-color: #ffffff;	/*white-space: pre;*/}.csharpcode pre { margin: 0em; }.csharpcode .rem { color: #008000; }.csharpcode .kwrd { color: #0000ff; }.csharpcode .str { color: #006080; }.csharpcode .op { color: #0000c0; }.csharpcode .preproc { color: #cc6633; }.csharpcode .asp { background-color: #ffff00; }.csharpcode .html { color: #800000; }.csharpcode .attr { color: #ff0000; }.csharpcode .alt {	background-color: #f4f4f4;	width: 100%;	margin: 0em;}.csharpcode .lnum { color: #606060; }Pronto. Para testar coloque a chamada SQL no arquivo &quot;Test.sql&quot;, veja:&quot;Dá o play macaco&quot; #Cruj… E começa o deploy no banco de dados, veja o resultado:Como podemos observar na imagem acima, o fonema é apresentado na janela de output, e como a dll já está instalada no SQL Server podemos testar diretamente nele:Podemos aplicá-la em tabelas&#160; e views normalmente! O desempenho é muito superior comparada a uma implementação onde os dados são recuperados e processados por uma aplicação externa. Códigos .NET rodando diretamente na CLR do SQL Server são muito eficazes.O Soundex é uma dos estudos disponíveis. Existe também o algoritmo BuscaBR, mas esse fica para uma outra oportunidade Obrigado, abraços.