Trabalhando com SQL CLR: Table-value User Defined Functions

Neste artigo estaremos conhecendo um pouco sobre as funções tabulares, que consistem basicamente de funções que podem ser “selecionáveis” como as tabelas e views e ainda podem receber parâmetros.

Mas para que iremos utilizar uma função que retorne um resultado em formato de tabela? Não se preocupe, pois em algum momento você encontrará uma utilidade! Por enquanto vamos conhecer a estrutura de nossa função tabular.

A primeira característica é o a utilização de dois métodos para defini-la, sendo um método principal que será responsável por retornar um enumerador de elementos de qualquer tipo e um método auxiliar que terá o papel de traduzir estes elementos para o SQL Sever.

O método principal definirá pelo atributo “TableDefinition” a estrutura básica da “tabela de retorno” e pelo atributo “FillRowMethodName” o método responsável por transformar cada elemento do enumerador resultante do método principal como “linhas” da tabela!!!

Parece um pouco complexo? Para diminuir o susto, vamos a um exemplo simples…

Com o método principal estarei retornando um array de strings e com o método auxiliar digo que cada string que retornar será a coluna “nome” do meu resultado.

using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
using System.Collections;

public partial class UserDefinedFunctions
{
    [SqlFunction(Name = "UDF_Segundo"
        , FillRowMethodName = "Fill"
        , TableDefinition = "Nome VARCHAR(10)")]
    public static IEnumerable Segundo()
    {
        return new[] { "Paulo", "Roberto", "Pereira", "Junior" };
    }

    public static void Fill(object obj, out SqlString nome)
    {
        nome = (string)obj;
    }
};
SELECT * FROM UDF_Segundo()

O método auxiliar terá como parâmetro de entrada um objeto que representará um dos elementos resultantes do método principal, e mais um parâmetro de saída para cada coluna do resultado tabular.

Para deixar um pouco mais complexo, elaborei um segundo exemplo trabalhando com dicionário e mais de uma coluna.

using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
using System.Collections;
using System.Collections.Generic;

public partial class UserDefinedFunctions
{
    [SqlFunction(Name = "UDF_Segundo"
        , FillRowMethodName = "Fill"
        , TableDefinition = "Codigo INT, Nome NVARCHAR(40)")]
    public static IEnumerable Segundo()
    {
        var dic = new Dictionary<int, string>
        {
            {1, "Paulo"},
            {2, "Thiago"},
            {3, "João"},
            {4, "Lucas"}
        };

        return dic;
    }

    public static void Fill(object obj
        , out SqlInt32 codigo
        , out SqlString nome )
    {
        var row = (KeyValuePair<int, string>)obj;
        codigo = row.Key;
        nome = row.Value;
    }
};
SELECT * FROM UDF_Segundo()

O método auxiliar pode ser utilizado por um ou vários métodos principais, o que ajuda e muito na hora de tratar funções parecidas.

Para um exemplo muito útil de funções tabulares, elaborei uma função de “Split”, que basicamente dividi um texto de acordo com o delimitador, demonstrando assim a utilização de parâmetros na função principal, além da possibilidade de utilizar “yield” (que poderá ser útil para soluções mais complexas):

using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
using System.Collections;

public partial class UserDefinedFunctions
{
    [SqlFunction(FillRowMethodName = "UDF_Split_Fill"
        , TableDefinition = "Texto NVARCHAR(MAX)")]
    public static IEnumerable UDF_Split(
        [SqlFacet(MaxSize = -1)]SqlString texto,
        [SqlFacet(MaxSize = 1)]SqlString delimitador)
    {
        var lim = delimitador.Value[0];

        var parts = texto.Value;

        foreach (var item in parts.Split(lim))
        {
            var part = item.Trim();
            if (part != string.Empty)
                yield return part;
        }
    }

    public static void UDF_Split_Fill(
        object obj
        , out SqlString texto)
    {
        texto = (string)obj;
    }
};
SELECT * FROM UDF_Split('1;1.10;1.12;2;2.3;', ';')

E para a utilização de uma função de split, um cenário onde temos uma tabela de “contratos” e nesta mesma tabela os serviços de cada contrato em um campo separado por vírgulas, no qual podemos utilizar CROSS APPLY com a função de split para obter uma relação dos contratos e seus serviços:

DECLARE @Contrato TABLE(
	Id INT IDENTITY,
	Contrato VARCHAR(250),
	Serviços VARCHAR(100)
)

INSERT INTO @Contrato
VALUES('Curitiba', '1,2,4,3')

INSERT INTO @Contrato
VALUES('Florianópolis', '3,2,8,1,4,6')

INSERT INTO @Contrato
VALUES('Campo Largo', '1,4')


SELECT S.Texto AS Serviço, C.Contrato
FROM @Contrato C
CROSS APPLY UDF_Split(C.Serviços, ',') AS S

Com este artigo, abrimos mais um pouco o leque de possibilidade do suporte CLR do SQL Server, espero que tenham gostado. No próximo artigo sobre SQL CLR, estaremos explorando funções de agregação.

Trabalhando com SQL CLR: User Defined Function Scalar

Boa noite pessoas!

Dando seqüência aos artigos relacionados a CLR no SQL Server, partiremos para as funções (ou user defined fuction), cuja estrutura é semelhante as stored procedures, mas diferente das stored procedures, as funções podem ser utilizadas em combinação com consultas em T-SQL.

Neste primeiro artigo, veremos a forma mais simples das funções desenvolvidas em CLR, as funções escalares, que possuem como retorno “um valor”, como demonstrado a seguir:

using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;

public partial class UserDefinedFunctions
{
    [SqlFunction]
    public static SqlString Segundo()
    {
        return new SqlString("SQL From Hell!!!");
    }
};
SELECT dbo.Segundo() AS Valor

Da mesma forma que nomeamos procedimentos armazenados anteriormente, também podemos nomear funções desenvolvidas em CLR, assim como trabalhar com parâmetros:

using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;

public partial class UserDefinedFunctions
{
    [SqlFunction(Name = "UDF_Segundo")]
    public static SqlString Segundo(SqlString texto)
    {
        return texto;
    }
};
SELECT dbo.UDF_Segundo('SQL From Hell!!!') AS Valor

E estas características podem ser manipuladas com o atributo SqlFacet.

using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;

public partial class UserDefinedFunctions
{
    [SqlFunction(Name = "UDF_Segundo")]
    public static SqlString Segundo
        ([SqlFacet(MaxSize=40)]SqlString texto)
    {
        return texto;
    }
};
SELECT dbo.UDF_Segundo('SQL From Hell!!!') AS Valor

Até mesmos os resultados provenientes destas funções podem ser manipulados, como demonstrado no script gerado automaticamente pelo Visual Studio no deploy.

using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;

public partial class UserDefinedFunctions
{
    [SqlFunction(Name = "UDF_Segundo")]
    [return: SqlFacet(MaxSize=40)]
    public static SqlString Segundo
        ([SqlFacet(MaxSize=40)]SqlString texto)
    {
        return texto;
    }
};
SELECT dbo.UDF_Segundo('SQL From Hell!!!') AS Valor
--Script gerado automaticamente pelo Visual Studio no deploy
CREATE FUNCTION [dbo].[UDF_Segundo](@texto [nvarchar](40))
RETURNS [nvarchar](40) WITH EXECUTE AS CALLER
AS
EXTERNAL NAME [Modelo].[UserDefinedFunctions].[Segundo]
GO

Como exemplo do que podemos desenvolver com funções escalares, demonstro abaixo uma função de validação de CPF desenvolvida em CLR, que pode ser utilizada para verificar numa tabela “pessoas físicas” quais são os registros possuem CPF inválido.

using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
using System.Collections.Generic;

public partial class UserDefinedFunctions
{
    [SqlFunction]
    public static SqlBoolean UDF_ValidarCPF(SqlString numero)
    {
        if (numero.IsNull)
            return SqlBoolean.False;

        var cpf = new List<int>(11);

        foreach (var c in numero.Value)
            if (char.IsNumber(c))
                cpf.Add((int)char.GetNumericValue(c));

        if (cpf.Count != 11)
            return SqlBoolean.False;

        var v = new int[2];

        //Nota: Calcula o primeiro dígito de verificação.
        v[0] = 10 * cpf[0] + 9 * cpf[1] + 8 * cpf[2];
        v[0] += 7 * cpf[3] + 6 * cpf[4] + 5 * cpf[5];
        v[0] += 4 * cpf[6] + 3 * cpf[7] + 2 * cpf[8];
        v[0] = 11 - v[0] % 11;
        v[0] = v[0] >= 10 ? 0 : v[0];

        //Nota: Calcula o segundo dígito de verificação.
        v[1] = 11 * cpf[0] + 10 * cpf[1] + 9 * cpf[2];
        v[1] += 8 * cpf[3] + 7 * cpf[4] + 6 * cpf[5];
        v[1] += 5 * cpf[6] + 4 * cpf[7] + 3 * cpf[8];
        v[1] += 2 * v[0];
        v[1] = 11 - v[1] % 11;
        v[1] = v[1] >= 10 ? 0 : v[1];

        //Nota: Verdadeiro se os dígitos de verificação são os esperados.
        return new SqlBoolean(v[0] == cpf[9] && v[1] == cpf[10]);
    }
};
SELECT *
FROM PessoaFisica
WHERE dbo.UDF_ValidarCPF(CPF) = 0

E uma validação semelhante para CNPJ:

using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
using System.Collections.Generic;

public partial class UserDefinedFunctions
{
    [SqlFunction]
    public static SqlBoolean UDF_ValidarCNPJ(SqlString numero)
    {
        if (numero.IsNull)
            return SqlBoolean.False;

        var cnpj = new List<int>(14);

        //Nota: Removendo não numéricos
        foreach (var c in numero.Value)
            if (char.IsNumber(c))
                cnpj.Add((int)char.GetNumericValue(c));

        if (cnpj.Count != 14)
            return SqlBoolean.False;

        //Nota: Dígitos de verificação.
        var v = new int[2];

        //Nota: Calcula o primeiro dígito de verificação.
        v[0] = 5 * cnpj[0] + 4 * cnpj[1] + 3 * cnpj[2] + 2 * cnpj[3];
        v[0] += 9 * cnpj[4] + 8 * cnpj[5] + 7 * cnpj[6] + 6 * cnpj[7];
        v[0] += 5 * cnpj[8] + 4 * cnpj[9] + 3 * cnpj[10] + 2 * cnpj[11];
        v[0] = 11 - v[0] % 11;
        v[0] = v[0] >= 10 ? 0 : v[0];

        //Nota: Calcula o segundo dígito de verificação.
        v[1] = 6 * cnpj[0] + 5 * cnpj[1] + 4 * cnpj[2] + 3 * cnpj[3];
        v[1] += 2 * cnpj[4] + 9 * cnpj[5] + 8 * cnpj[6] + 7 * cnpj[7];
        v[1] += 6 * cnpj[8] + 5 * cnpj[9] + 4 * cnpj[10] + 3 * cnpj[11];
        v[1] += 2 * v[0];
        v[1] = 11 - v[1] % 11;
        v[1] = v[1] >= 10 ? 0 : v[1];

        //Nota: Verdadeiro se os dígitos de verificação são os esperados.
        return new SqlBoolean(v[0] == cnpj[12] && v[1] == cnpj[13]);
    }
};
SELECT *
FROM PessoaJuridica
WHERE dbo.UDF_ValidarCNPJ(CNPJ) = 0

Assim finalizamos mais um artigo, espero que tenham gostado de conhecer um pouco mais sobre as funções “escalares”, principalmente pelos exemplos com validação de CPF e CNPJ. Lembrando também que é possível integrar estas funções com WebServices para validar e recuperar dados relacionados a CEP, telefone e muitos outros.

Agradecimentos aos artigos de CPF e CNPJ da Wikipédia pelos algoritmos de validação que adaptei em C# para as demonstrações.

Nas próximas semanas, darei uma pausa nesta série de artigos sobre SQL CLR, para atender alguns pedidos e tratar os tópicos mais votados da enquete (http://wp.me/sv9ju-enquete).

(DRAFT) Trabalhando com SQL CLR: Validação de CNPJ e CPF

Validação de CPF:

using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
using System.Collections.Generic;

public partial class UserDefinedFunctions
{
    [SqlFunction]
    public static SqlBoolean UDF_ValidarCPF(SqlString numero)
    {
        if (numero.IsNull)
            return SqlBoolean.False;

        var cpf = new List<int>(11);

        foreach (var c in numero.Value)
            if (char.IsNumber(c))
                cpf.Add((int)char.GetNumericValue(c));

        if (cpf.Count != 11)
            return SqlBoolean.False;

        var v = new int[2];

        //Nota: Calcula o primeiro dígito de verificação.
        v[0] = 10 * cpf[0] + 9 * cpf[1] + 8 * cpf[2];
        v[0] += 7 * cpf[3] + 6 * cpf[4] + 5 * cpf[5];
        v[0] += 4 * cpf[6] + 3 * cpf[7] + 2 * cpf[8];
        v[0] = 11 - v[0] % 11;
        v[0] = v[0] >= 10 ? 0 : v[0];

        //Nota: Calcula o segundo dígito de verificação.
        v[1] = 11 * cpf[0] + 10 * cpf[1] + 9 * cpf[2];
        v[1] += 8 * cpf[3] + 7 * cpf[4] + 6 * cpf[5];
        v[1] += 5 * cpf[6] + 4 * cpf[7] + 3 * cpf[8];
        v[1] += 2 * v[0];
        v[1] = 11 - v[1] % 11;
        v[1] = v[1] >= 10 ? 0 : v[1];

        //Nota: Verdadeiro se os dígitos de verificação são os esperados.
        return new SqlBoolean(v[0] == cpf[9] && v[1] == cpf[10]);
    }
};
SELECT *
FROM PessoaFisica
WHERE dbo.UDF_ValidarCPF(CPF) = 0

Validação de CNPJ:

using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
using System.Collections.Generic;

public partial class UserDefinedFunctions
{
    [SqlFunction]
    public static SqlBoolean UDF_ValidarCNPJ(SqlString numero)
    {
        if (numero.IsNull)
            return SqlBoolean.False;

        var cnpj = new List<int>(14);

        //Nota: Removendo não numéricos
        foreach (var c in numero.Value)
            if (char.IsNumber(c))
                cnpj.Add((int)char.GetNumericValue(c));

        if (cnpj.Count != 14)
            return SqlBoolean.False;

        //Nota: Dígitos de verificação.
        var v = new int[2];

        //Nota: Calcula o primeiro dígito de verificação.
        v[0] = 5 * cnpj[0] + 4 * cnpj[1] + 3 * cnpj[2] + 2 * cnpj[3];
        v[0] += 9 * cnpj[4] + 8 * cnpj[5] + 7 * cnpj[6] + 6 * cnpj[7];
        v[0] += 5 * cnpj[8] + 4 * cnpj[9] + 3 * cnpj[10] + 2 * cnpj[11];
        v[0] = 11 - v[0] % 11;
        v[0] = v[0] >= 10 ? 0 : v[0];

        //Nota: Calcula o segundo dígito de verificação.
        v[1] = 6 * cnpj[0] + 5 * cnpj[1] + 4 * cnpj[2] + 3 * cnpj[3];
        v[1] += 2 * cnpj[4] + 9 * cnpj[5] + 8 * cnpj[6] + 7 * cnpj[7];
        v[1] += 6 * cnpj[8] + 5 * cnpj[9] + 4 * cnpj[10] + 3 * cnpj[11];
        v[1] += 2 * v[0];
        v[1] = 11 - v[1] % 11;
        v[1] = v[1] >= 10 ? 0 : v[1];

        //Nota: Verdadeiro se os dígitos de verificação são os esperados.
        return new SqlBoolean(v[0] == cnpj[12] && v[1] == cnpj[13]);
    }
};
SELECT *
FROM PessoaJuridica
WHERE dbo.UDF_ValidarCNPJ(CNPJ) = 0

Artigos relacionados:

Validação de CNPJ e CPF (somente com SQL)
https://sqlfromhell.wordpress.com/2009/11/19/validacao-de-cnpj-e-cpf/

Referências:

Wikipédia – CPF
http://pt.wikipedia.org/wiki/Cpf

Wikipédia – CNPJ
http://pt.wikipedia.org/wiki/Cnpj