Tempo de Leitura: 13 minutosOlá pessoal,
Tudo bem com vocês ??
Neste post, que será o número 200 do blog, eu gostaria de falar sobre dois assuntos que eu particularmente gosto muito no SQL Server, que é a criação de rotinas .NET (CLR) dentro do banco de dados SQL Server e performance tuning.
Coincidência ou não, meu post de número 100 foi o Introdução ao SQL CLR (Common Language Runtime) no SQL Server, que foi o meu primeiro post no blog sobre o CLR.
Meu intuito nesse post, é demonstrar o ganho de performance que pode ser obtido ao utilizar Scalar functions do CLR ao invés da Scalar function escrita em T-SQL (UDF – User Defined Function). Nos testes realizados, a grande maioria das funções T-SQL que foram migradas para funções CLR tiverem um grande ganho de performance simplesmente por conta das otimizações do Microsoft .NET Framework frente ao motor do SQL Server.
Em muitas situações, vemos que os índices estão criados corretamente, estatísticas atualizadas e, sem precisar reescrever a query, pode-se conseguir um grande ganho de performance simplesmente alterando a linguagem de programação de funções escalares.
Função no CLR sempre vai ser melhor?
Há quem possa ter algumas dúvidas sobre a performance de funções escalares do CLR, as quais vou esclarecer:
– As funções escalares do CLR sempre vão ter uma performance melhor que funções ?
Com certeza não! Já fiz vários testes comparando diversas funções, de diversas finalidades diferentes e pela minha experiência, as funções scalares escritas em C# no CLR, GERALMENTE apresentam sim, uma melhor performance, mas já vi alguns casos em que mesmo otimizando o código ao máximo, a função T-SQL apresenta um desempenho melhor.
– E se compararmos a performance de funções nativas com as funções do CLR ?
Funções, de uma maneira geral, sempre prejudicam a performance se uma consulta ao banco de dados. Entretanto, por mais que as funções escalares do CLR sejam bem performáticas, GERALMENTE as funções nativas do SQL Server (Ex: CONVERT, CAST, DATEADD, etc) apresentam uma melhor performance quando comparadas.
Comparando a performance na prática
Depois de fazer essa introdução sobre o assunto, é hora de realmente demonstrar o que foi falado aqui. E nada melhor que realizar os testes de performance na prática para convencer você, de que o CLR pode, em muitos casos, prover um grande ganho de performance nas suas consultas ao SQL Server.
Para gerar a massa de dados de testes, utilizei esse script:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | IF (OBJECT_ID('dirceuresende.dbo.Teste_Performance') IS NOT NULL) DROP TABLE dirceuresende.dbo.Teste_Performance CREATE TABLE dirceuresende.dbo.Teste_Performance ( Id INT IDENTITY(1, 1), Nome VARCHAR(200), Numero INT, [Data] DATETIME, Observacao VARCHAR(MAX), CPF VARCHAR(11) ) INSERT INTO dirceuresende.dbo.Teste_Performance ( Nome, Numero, [Data], Observacao, CPF ) SELECT CAST(NEWID() AS VARCHAR(50)), dbo.fncRand(99999999), DATEADD(SECOND, (CAST(RAND() * 31536000 AS INT)), '2017-01-01') AS Dt_Venda, CAST(NEWID() AS VARCHAR(50)) + ';' + CAST(NEWID() AS VARCHAR(50)) + ';' + CAST(NEWID() AS VARCHAR(50)), RIGHT(REPLICATE('0', 11) + CAST(dbo.fncRand(99999999999) AS VARCHAR(11)), 11) AS CPF DECLARE @Contador INT = 1, @Total INT = 20 WHILE(@Contador <= @Total) BEGIN INSERT INTO dirceuresende.dbo.Teste_Performance ( Nome, Numero, [Data], Observacao, CPF ) SELECT CAST(NEWID() AS VARCHAR(50)), dbo.fncRand(99999999), DATEADD(SECOND, (CAST(RAND() * 31536000 AS INT)), '2017-01-01') AS Dt_Venda, CAST(NEWID() AS VARCHAR(50)) + ';' + CAST(NEWID() AS VARCHAR(50)) + ';' + CAST(NEWID() AS VARCHAR(50)), RIGHT(REPLICATE('0', 11) + CAST(dbo.fncRand(99999999999) AS VARCHAR(11)), 11) AS CPF FROM dirceuresende.dbo.Teste_Performance SET @Contador += 1 END |
O código-fonte da função fncRand(), bem como a explicação de porque utilizo essa função ao invés da RAND(), você encontra no post SQL Server – Msg 443 Invalid use of a side-effecting operator ‘rand’ within a function.
fncPrimeiroDiaMes
Visualizar conteúdo Código-fonte T-SQL: | CREATE FUNCTION [dbo].[fncPrimeiroDiaMes](@Dt_Referencia DATETIME) RETURNS DATETIME AS BEGIN RETURN DATEADD(DAY,-(DAY(@Dt_Referencia)-1), CAST(FLOOR(CAST(@Dt_Referencia AS FLOAT)) AS DATETIME)) END |
Código-fonte CLR:
| using System; using System.Data.SqlTypes; public partial class UserDefinedFunctions { [Microsoft.SqlServer.Server.SqlFunction] public static SqlDateTime fncPrimeiro_Dia_Mes(SqlDateTime Dt_Referencia) { if (Dt_Referencia.IsNull) return SqlDateTime.Null; var data = Dt_Referencia.Value; return new DateTime(data.Year, data.Month, 1); } } |
Saída:

Comparação de performance no SELECT

Comparação de performance no WHERE

fncUltimoDiaMes
Visualizar conteúdo Código-fonte T-SQL: | create function [dbo].[fncUltimoDiaMes](@Dt_Referencia datetime) RETURNS datetime as begin return dateadd(day,-1,dateadd(month,+1,DATEADD(DAY,-(DAY(@Dt_Referencia)-1), CAST(FLOOR(CAST(@Dt_Referencia AS FLOAT)) AS DATETIME)))) end |
Código-fonte CLR:
| using System; public partial class UserDefinedFunctions { [Microsoft.SqlServer.Server.SqlFunction] public static DateTime fncUltimo_Dia_Mes(DateTime data) { return new DateTime(data.Year, data.Month, DateTime.DaysInMonth(data.Year, data.Month)); } } |
Saída:

Comparação de performance no SELECT

Comparação de performance no WHERE

fncSplit
Visualizar conteúdo Código-fonte T-SQL: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | CREATE FUNCTION [dbo].[fncSplit] ( @String varchar(8000), @Separador varchar(8000), @PosBusca int ) RETURNS varchar(8000) AS BEGIN DECLARE @Index int, @Max int, @Retorno varchar(8000) DECLARE @Partes as TABLE ( Id_Parte int identity(1,1), Texto varchar(8000) ) SET @Index = charIndex(@Separador,@String) WHILE (@Index > 0) BEGIN INSERT INTO @Partes SELECT SubString(@String,1,@Index-1) SET @String = Rtrim(Ltrim(SubString(@String,@Index+Len(@Separador),Len(@String)))) SET @Index = charIndex(@Separador,@String) END IF (@String != '') INSERT INTO @Partes SELECT @String SELECT @Max = Count(*) FROM @Partes IF (@PosBusca = 0) SET @Retorno = Cast(@Max as varchar(5)) IF (@PosBusca < 0) SET @PosBusca = @Max + 1 + @PosBusca IF (@PosBusca > 0) SELECT @Retorno = Texto FROM @Partes WHERE Id_Parte = @PosBusca RETURN Rtrim(Ltrim(@Retorno)) END |
Código-fonte CLR:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | using System; using System.Data.SqlTypes; public partial class UserDefinedFunctions { [Microsoft.SqlServer.Server.SqlFunction] public static SqlString fncSplit(SqlString Ds_Palavra, SqlString Ds_Delimitador, SqlInt32 Qt_Indice) { if (Ds_Palavra.IsNull || Qt_Indice.IsNull) return SqlString.Null; if (Ds_Delimitador.IsNull) return Ds_Palavra.Value; var indice = (Qt_Indice.Value == 0) ? 1 : Qt_Indice.Value; var palavra = Ds_Palavra.Value.Split(Ds_Delimitador.Value.ToCharArray()); if (Qt_Indice.Value == 0) return palavra.Length.ToString(); if (palavra.Length < Qt_Indice.Value) return SqlString.Null; return indice > 0 ? palavra[indice - 1] : palavra[palavra.Length - Math.Abs(indice)]; } } |
Saída:

Comparação de performance no SELECT
Reparem que, após esperar 5 mins, acabei cancelando e iniciando os testes novamente, mas com apenas 10.000 registros para a função T-SQL, comparando com a função do CLR rodando em 100.000 registros. Mas mesmo a função do CLR sendo executada 10x mais, vejam o que aconteceu nos resultados…

Comparação de performance no WHERE

fncBase64_Encode
Visualizar conteúdo Código-fonte T-SQL: | CREATE FUNCTION [dbo].[fncBase64_Encode] ( @string VARCHAR(MAX) ) RETURNS VARCHAR(MAX) AS BEGIN DECLARE @source VARBINARY(MAX), @encoded VARCHAR(MAX) set @source = convert(varbinary(max), @string) SET @encoded = CAST('' AS XML).value('xs:base64Binary(sql:variable("@source"))', 'varchar(max)') RETURN @encoded END |
Código-fonte CLR:
| using System; using System.Data.SqlTypes; using System.Text; public partial class UserDefinedFunctions { [Microsoft.SqlServer.Server.SqlFunction] public static SqlString fncBase64_Encode(SqlString Ds_Texto) { return Ds_Texto.IsNull ? null : Convert.ToBase64String(Encoding.UTF8.GetBytes(Ds_Texto.Value)); } } |
Saída:

Comparação de performance no SELECT

fncBase64_Decode
Visualizar conteúdo Código-fonte T-SQL: | CREATE FUNCTION [dbo].[fncBase64_Decode] ( @string VARCHAR(MAX) ) RETURNS VARCHAR(MAX) AS BEGIN DECLARE @decoded VARCHAR(MAX) SET @decoded = CAST('' AS XML).value('xs:base64Binary(sql:variable("@string"))', 'varbinary(max)') RETURN convert(varchar(max), @decoded) END |
Código-fonte CLR:
| using System; using System.Data.SqlTypes; using System.Text; public partial class UserDefinedFunctions { [Microsoft.SqlServer.Server.SqlFunction] public static SqlString fncBase64_Decode(SqlString Ds_Texto) { return Ds_Texto.IsNull ? null : Encoding.UTF8.GetString(Convert.FromBase64String(Ds_Texto.Value)); } } |
Saída:

Comparação de performance no SELECT

fncValida_CPF
Visualizar conteúdo Código-fonte T-SQL: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | CREATE FUNCTION [dbo].[fncValida_CPF](@Nr_Documento [varchar](11)) RETURNS [bit] AS BEGIN IF (ISNUMERIC(@Nr_Documento) = 0) RETURN 0 DECLARE @Contador_1 INT, @Contador_2 INT, @Digito_1 INT, @Digito_2 INT, @Nr_Documento_Aux VARCHAR(11) SET @Nr_Documento_Aux = LTRIM(RTRIM(@Nr_Documento)) SET @Digito_1 = 0 IF LEN(@Nr_Documento_Aux) <> 11 RETURN 0 ELSE BEGIN -- Cálculo do segundo dígito SET @Nr_Documento_Aux = SUBSTRING(@Nr_Documento_Aux, 1, 9) SET @Contador_1 = 2 WHILE @Contador_1 <= 10 BEGIN SET @Digito_1 = @Digito_1 + ( @Contador_1 * CAST(SUBSTRING(@Nr_Documento_Aux, 11 - @Contador_1, 1) AS INT) ) SET @Contador_1 = @Contador_1 + 1 END SET @Digito_1 = @Digito_1 - ( @Digito_1 / 11 ) * 11 IF @Digito_1 <= 1 SET @Digito_1 = 0 ELSE SET @Digito_1 = 11 - @Digito_1 SET @Nr_Documento_Aux = @Nr_Documento_Aux + CAST(@Digito_1 AS VARCHAR(1)) IF @Nr_Documento_Aux <> SUBSTRING(@Nr_Documento, 1, 10) RETURN 0 ELSE BEGIN -- Cálculo do segundo dígito SET @Digito_2 = 0 SET @Contador_2 = 2 WHILE (@Contador_2 <= 11) BEGIN SET @Digito_2 = @Digito_2 + ( @Contador_2 * CAST(SUBSTRING(@Nr_Documento_Aux, 12 - @Contador_2, 1) AS INT) ) SET @Contador_2 = @Contador_2 + 1 END SET @Digito_2 = @Digito_2 - ( @Digito_2 / 11 ) * 11 IF @Digito_2 < 2 SET @Digito_2 = 0 ELSE SET @Digito_2 = 11 - @Digito_2 SET @Nr_Documento_Aux = @Nr_Documento_Aux + CAST(@Digito_2 AS VARCHAR(1)) IF @Nr_Documento_Aux <> @Nr_Documento RETURN 0 END END RETURN 1 END |
Código-fonte CLR:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | using System.Data.SqlTypes; public partial class UserDefinedFunctions { [Microsoft.SqlServer.Server.SqlFunction] public static SqlBoolean fncValida_CPF(SqlString Ds_CPF) { if (Ds_CPF.IsNull) return false; var valor = Ds_CPF.Value.Replace(".", "").Replace("-", "").Replace(" ", ""); if (valor.Length != 11) return false; long resultadoConversaoInt; if (!long.TryParse(valor, out resultadoConversaoInt)) return false; var igual = true; for (var i = 1; i < 11 && igual; i++) if (valor[i] != valor[0]) igual = false; if (igual || valor == "12345678909") return false; var numeros = new int[11]; for (var i = 0; i < 11; i++) numeros[i] = int.Parse(valor[i].ToString()); var soma = 0; for (var i = 0; i < 9; i++) soma += (10 - i) * numeros[i]; var resultado = soma % 11; if (resultado == 1 || resultado == 0) { if (numeros[9] != 0) return false; } else if (numeros[9] != 11 - resultado) return false; soma = 0; for (var i = 0; i < 10; i++) soma += (11 - i) * numeros[i]; resultado = soma % 11; if (resultado == 1 || resultado == 0) { if (numeros[10] != 0) return false; } else if (numeros[10] != 11 - resultado) return false; return true; } } |
Saída:

Comparação de performance no SELECT

fncRecupera_Numeros
Visualizar conteúdo Código-fonte T-SQL: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | CREATE function [dbo].[fncRecupera_Numeros](@str varchar(500)) returns varchar(500) begin declare @startingIndex int set @startingIndex=0 while 1=1 begin set @startingIndex= patindex('%[^0-9]%',@str) if @startingIndex <> 0 begin set @str = replace(@str,substring(@str,@startingIndex,1),'') end else break; end return NULLIF(@str, '') end |
Código-fonte CLR:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | using System.Data.SqlTypes; public partial class UserDefinedFunctions { [Microsoft.SqlServer.Server.SqlFunction] public static SqlString fncRecupera_Numeros(SqlString Ds_Texto) { if (Ds_Texto.IsNull) return SqlString.Null; var retorno = ""; var palavra = Ds_Texto.Value; var len = palavra.Length; for (var i = 0; i < len; ++i) { var letra = palavra[i]; if (letra >= '0' && letra <= '9') retorno += letra; } return string.IsNullOrEmpty(retorno) ? SqlString.Null : retorno; } }; |
Saída:

Comparação de performance no SELECT

fncMes
Visualizar conteúdo Código-fonte T-SQL: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | -- @Fl_Tipo define como é o tipo de formatação -- 1: Janeiro Fevereiro Marco Abril... -- 2: JANEIRO FEVEVEIRO MARCO ABRIL -- 3: Jan Fev Mar Abr -- 4: JAN FEV MAR ABR -- 5: January, February ... (cubo) CREATE FUNCTION [dbo].[fncMes] (@Dt_Referencia DATETIME, @Fl_Tipo TINYINT, @Fl_Incluir_Ano BIT = 0, @Fl_Incluir_Dia BIT = 0) RETURNS VARCHAR(30) AS BEGIN DECLARE @Mes TINYINT SET @Mes = DATEPART(MONTH, @Dt_Referencia) DECLARE @Ds_Mes as varchar(30) SET @Ds_Mes = CASE WHEN @Mes = 1 THEN 'Janeiro' WHEN @Mes = 2 THEN 'Fevereiro' WHEN @Mes = 3 THEN 'Março' WHEN @Mes = 4 THEN 'Abril' WHEN @Mes = 5 THEN 'Maio' WHEN @Mes = 6 THEN 'Junho' WHEN @Mes = 7 THEN 'Julho' WHEN @Mes = 8 THEN 'Agosto' WHEN @Mes = 9 THEN 'Setembro' WHEN @Mes = 10 THEN 'Outubro' WHEN @Mes = 11 THEN 'Novembro' WHEN @Mes = 12 THEN 'Dezembro' ELSE NULL END IF (@Fl_Tipo IN (3,4)) SET @Ds_Mes = SubString(@Ds_Mes,1,3) IF (@Fl_Tipo IN (2,4)) SET @Ds_Mes = Upper(@Ds_Mes) IF (@Fl_Tipo = 5) BEGIN DECLARE @Date datetime SET @Date = '2001'+Right('0'+Cast(@Mes as varchar(2)),2)+'01' SET @Ds_Mes = DateName(Month,@Date) END IF (@Fl_Incluir_Ano = 1) SET @Ds_Mes = @Ds_Mes + ' ' + CAST(DATEPART(YEAR, @Dt_Referencia) AS VARCHAR(4)) IF (@Fl_Incluir_Dia = 1) SET @Ds_Mes = CAST(DATEPART(DAY, @Dt_Referencia) AS VARCHAR(4)) + '/' + @Ds_Mes RETURN @Ds_Mes END |
Código-fonte CLR:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 | using System.Data.SqlTypes; public partial class UserDefinedFunctions { [Microsoft.SqlServer.Server.SqlFunction] public static SqlString fncMes(SqlDateTime Dt_Referencia, SqlInt32 Fl_Tipo, SqlBoolean Fl_Incluir_Ano, SqlBoolean Fl_Incluir_Dia) { #region Validações if (Dt_Referencia.IsNull) return null; if (Fl_Tipo.IsNull) Fl_Tipo = 1; if (Fl_Incluir_Ano.IsNull) Fl_Incluir_Ano = false; if (Fl_Incluir_Dia.IsNull) Fl_Incluir_Dia = false; #endregion var mes = Dt_Referencia.Value.Month; string retorno; if (Fl_Tipo == 5) { switch (mes) { case 1: retorno = "January"; break; case 2: retorno = "February"; break; case 3: retorno = "March"; break; case 4: retorno = "April"; break; case 5: retorno = "May"; break; case 6: retorno = "June"; break; case 7: retorno = "July"; break; case 8: retorno = "August"; break; case 9: retorno = "September"; break; case 10: retorno = "October"; break; case 11: retorno = "November"; break; case 12: retorno = "December"; break; default: retorno = null; break; } } else { switch (mes) { case 1: retorno = "Janeiro"; break; case 2: retorno = "Fevereiro"; break; case 3: retorno = "Março"; break; case 4: retorno = "Abril"; break; case 5: retorno = "Maio"; break; case 6: retorno = "Junho"; break; case 7: retorno = "Julho"; break; case 8: retorno = "Agosto"; break; case 9: retorno = "Setembro"; break; case 10: retorno = "Outubro"; break; case 11: retorno = "Novembro"; break; case 12: retorno = "Dezembro"; break; default: retorno = null; break; } if (Fl_Tipo == 3 || Fl_Tipo == 4) retorno = retorno?.Substring(0, 3); if (Fl_Tipo == 2 || Fl_Tipo == 4) retorno = retorno?.ToUpper(); } if (Fl_Incluir_Ano.Value) retorno += " " + Dt_Referencia.Value.Year.ToString("0000"); if (Fl_Incluir_Dia.Value) retorno = Dt_Referencia.Value.Day.ToString("00") + "/" + retorno; return retorno; } } |
Saída:

Comparação de performance no SELECT

fncConverte_Em_Horas
Visualizar conteúdo Código-fonte T-SQL: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | CREATE FUNCTION [dbo].[fncConverte_Em_Horas] (@Qt_Tempo BIGINT, @Tp_Tempo VARCHAR(10)) RETURNS VARCHAR(MAX) BEGIN DECLARE @ResultadoNegativo TINYINT = 0 IF (@Qt_Tempo < 0) BEGIN SET @ResultadoNegativo = 1 SET @Qt_Tempo = @Qt_Tempo * (-1) END DECLARE @Diferenca BIGINT = @Qt_Tempo, @Segundos BIGINT = 0, @Minutos BIGINT = 0, @Horas BIGINT = 0 IF(@Tp_Tempo IN('ss','second')) BEGIN SET @Horas = @Diferenca / 3600 SET @Diferenca = @Diferenca - (@Horas * 3600) SET @Minutos = @Diferenca / 60 SET @Diferenca = @Diferenca - (@Minutos * 60) SET @Segundos = @Diferenca END IF(@Tp_Tempo IN('mm','minute')) BEGIN SET @Horas = @Diferenca / 60 SET @Diferenca = @Diferenca - (@Horas * 60) SET @Minutos = @Diferenca SET @Segundos = 0 END IF(@Tp_Tempo IN('hh','hour')) BEGIN SET @Horas = @Diferenca SET @Minutos = 0 SET @Segundos = 0 END RETURN (CASE WHEN @ResultadoNegativo = 1 THEN '-' ELSE '' END) + (CASE WHEN @Horas <= 9 THEN RIGHT('00' + CAST(@Horas AS VARCHAR(1)), 2) ELSE CAST(@Horas AS VARCHAR(MAX)) END + ':' + RIGHT('00' + CAST(@Minutos AS VARCHAR(2)), 2) + ':' + RIGHT('00' + CAST(@Segundos AS VARCHAR(2)), 2)) END |
Código-fonte CLR:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | using System.Data.SqlTypes; public partial class UserDefinedFunctions { [Microsoft.SqlServer.Server.SqlFunction] public static SqlString fncConverte_Em_Horas(SqlInt64 Qt_Tempo, SqlString Tp_Tempo) { if (Qt_Tempo.IsNull) return SqlString.Null; if (Tp_Tempo.IsNull) return SqlString.Null; var resultadoNegativo = false; var tempo = Qt_Tempo.Value; if (tempo < 0) { resultadoNegativo = true; tempo = tempo * -1; } var diferenca = tempo; long segundos = 0; long minutos = 0; long horas = 0; switch (Tp_Tempo.Value) { case "ss": case "second": horas = diferenca / 3600; diferenca -= (horas * 3600); minutos = diferenca / 60; diferenca -= (minutos * 60); segundos = diferenca; break; case "mm": case "minute": horas = diferenca / 60; diferenca -= (horas * 60); minutos = diferenca; break; case "hh": case "hour": horas = diferenca; break; } return ((resultadoNegativo) ? "-" : "") + horas.ToString("00") + ":" + minutos.ToString("00") + ":" + segundos.ToString("00"); } } |
Saída:

Comparação de performance no SELECT

fncFormata_Documento
Visualizar conteúdo