Boas práticas – Três dicas a respeito da função GETDATE()

Sim, você não entendeu errado, estou dedicando este artigo ás boas práticas da utilização (e não utilização) da função GETDATE.

Neste artigo, tratarei de três práticas que considero importantes quando requer a utilização desta função, mas se alguém quiser comentar sobre outras práticas ou soluções que considere melhores, fique a vontade.

A primeira dica esta relacionada à divergência da data do servidor/cliente da aplicação em comparação ao servidor do banco de dados, pois é comum aparecer problemas em regras de negócios que envolvem a utilização da função GETDATE() e outras funções com finalidades semelhantes pela aplicação (ex.: VB.NET/C# – System.DateTime.Now()) quando a data/hora dos servidores/clientes estão diferentes (horário de verão que o diga). Desta forma, procure criar políticas para manter as datas/horas dos servidores sincronizadas, e utilizar somente a data/hora de um dos servidores (normalmente o servidor de banco de dados) nas regras de negócio.

Outra dica é referente ao conflito de fuso horário (ou timezone), pois quando você possui servidores em dois ou mais lugares distantes no mundo, a função GETDATE() pode gerar um pouco de dor de cabeça. Assim, utilizar a função SYSDATETIMEOFFSET ao invés de GETDATE, e o tipo DATETIMEOFFSET ao invés de DATETIME, permitirá solucionar estes conflitos e também dará ‘transparência’ em relação aos fusos horários nas consultas.

A função GETUTCDATE também pode ser útil para fusos horários distintos, mas não oferece a mesma transparência em relação ás datas entre os diversos fusos, por utilizar o horário UTC.

Para ‘conversões’ entre os fusos horários, utilize a função SWITCHOFFSET.

A terceira dica é referente ao conflito com os valores obtidos pela função GETDATE() em um mesmo script. Sim, isso existe! Ou estava pensando que o valor da função GETDATE() é obtido só uma vez durante todos os comandos de um script, veja o exemplo:

A fim de solucionar estas divergências, opte pela utilização de variáveis para identificar o valor da função GETDATE() durante os comandos de um script, exemplo:

Espero que tenham gostado destas dicas.

Ainda tenho outras dicas a respeito da precisão das datas/horas, mas as deixo para uma próxima oportunidade.

Trabalhando com datas – Scripts para trabalhar com os dias de um mês

E ai pessoas,

Nestes dias, dando uma olhada em alguns rascunhos do blog, encontrei quatro scripts que considero interessante compartilhar, principalmente pelo fato deles evoluírem de um para outro de forma bem clara.

O primeiro script recupera o último dia do mês:

DECLARE @Mes INT = 2, @Ano INT = 2011

DECLARE @DtaFinal DATETIME

SET @DtaFinal = DATEADD(YEAR, @Ano - 1900, DATEADD(MONTH, @Mes, 0)) - 1

SELECT CONVERT(VARCHAR, @DtaFinal, 103) AS [Último dia de um determinado mês]

GO

Este segundo, usa a função DATEPART para recuperar a quantidade de dias do mês:

DECLARE @Mes INT = 2, @Ano INT = 2011

DECLARE @DtaFinal DATETIME

SET @DtaFinal = DATEADD(YEAR, @Ano - 1900, DATEADD(MONTH, @Mes, 0)) - 1

SELECT DATEPART(DAY, @DtaFinal) AS [Número de dias de um determinado mês]

GO

Este terceiro script, usa uma CTE para relacionar os dias de um determinado mês:

DECLARE @Mes INT = 2, @Ano INT = 2011

DECLARE @Num INT
DECLARE @DtaInicio DATETIME
DECLARE @DtaFinal DATETIME

SET @DtaInicio = DATEADD(YEAR, @Ano - 1900, DATEADD(MONTH, @Mes - 1, 0))
SET @DtaFinal = DATEADD(YEAR, @Ano - 1900, DATEADD(MONTH, @Mes, 0)) - 1

;WITH CTE (Dta) AS (
	SELECT @DtaInicio
	UNION ALL
	SELECT Dta + 1
	FROM CTE
	WHERE Dta < @DtaFinal
)

SELECT Dta AS [Dia] FROM CTE

GO

E este ultimo script, usa um CASE em combinação de um COUNT para contar a quantidade de sábados e domingos de um determinado mês:

DECLARE @Mes INT = 2, @Ano INT = 2011

DECLARE @Num INT
DECLARE @DtaInicio DATETIME
DECLARE @DtaFinal DATETIME

SET @DtaInicio = DATEADD(YEAR, @Ano - 1900, DATEADD(MONTH, @Mes - 1, 0))
SET @DtaFinal = DATEADD(YEAR, @Ano - 1900, DATEADD(MONTH, @Mes, 0)) - 1

;WITH CTE (Dta) AS (
	SELECT @DtaInicio
	UNION ALL
	SELECT Dta + 1
	FROM CTE
	WHERE Dta < @DtaFinal
)

--DATEPART(W, Dta) = 1 // Domingo
--DATEPART(W, Dta) = 7 // Sábado
SELECT @Num = COUNT(CASE WHEN DATEPART(W, Dta) IN (1, 7) THEN 1 END) FROM CTE

SELECT @Num AS [Número de sábado e domingos de um determinado mês]

GO

Artigos relacionados:

Descobrindo o primeiro dia e o número de semanas de um mês

Lista de meses e dias da semana

Recuperando informações de dia, mes, ano, hora, minuto, segundo de datas

Trabalhando com formatação de datas

Criando um calendário pelo SQL

Trabalhando com datas – Cálculos com meses

É muito comum aparecer no dia a dia, muitas necessidades estranhas para trabalhar com SQL, principalmente quando se trabalha com os campos de data. Mas a sensação de superar obstáculos somente pode ter sentido com estes pequenos e mirabolantes desafios aparecem, e a vontade de dar um jeitinho no SQL começa aparecer.

Para enterrar o tópico “Trabalhando com datas”, segue dois desafios legais que já encontrei.

Retornando o primeiro dia da mês:

DECLARE @DATA DATETIME
SET @DATA = GETDATE()
SELECT CONVERT(VARCHAR, @DATA - DAY(@DATA) + 1, 103) as PrimeiroDiaDoMes
Ou
SELECT CONVERT(VARCHAR, @DATA - DATEPART(DAY, @DATA) + 1, 103) as PrimeiroDiaDoMes

Retornando o número da semana do mês:

DECLARE @DATA DATETIME
SET @DATA = GETDATE()
SELECT DATEPART(WK, @DATA) - DATEPART(WK, @DATA - DAY(@DATA) + 1) + 1 as SemanaDoMes
Ou
SELECT DATEPART(WEEK, @DATA) - DATEPART(WEEK, @DATA - DATEPART(DAY, @DATA) + 1) + 1 as SemanaDoMes

Valeu Maikel pelo desafio e por liberar que fosse postado!

Trabalhando com datas – Lista de meses e dias da semana

Pessoal, no último post relacionei funções para trabalhar com campos do tipo data, acabei recebendo um bom feedback e algumas perguntas sobre como relacionar todos os meses ou dias da semana por meio de query. Para responder estas perguntas, relacionei formas de gerar listas com os meses e dias da semana por meio de CTE.

Nestas duas primeiras queries, faço uso de um CTE recursivo básico, com as funções DATENAME e DATEADD e a instrução SET LANGUAGE para definir o idioma ‘Brasileiro’. Estas queries são bem mais simples e tornam mais fácil trazer novos campos. 

Tips:
1: Os campos de data quando somados a tipos inteiros, adicionam o valor dos inteiros em dias.
2: A função DATEADD simplifica a adição de outros quantitativos como meses, horas e anos.
3: O inteiro ‘0’ (zero) quando convertido para data se torna ‘1900-01-01 00:00:00.000’, uma segunda-feira.
4: O inteiro ‘-1’ quando convertido para data se torna ‘1899-12-31 00:00:00.000’, um domingo.

Gerando uma lista de meses:

SET LANGUAGE 'Brazilian'
;WITH Meses AS
(
  SELECT 1 AS IdMes, DATENAME(MONTH, 0) AS NomeMes
  UNION ALL
  SELECT IdMes + 1, DATENAME(MONTH, DATEADD(MONTH, IdMes, 0))
  FROM Meses
  WHERE IdMes < 12
)
SELECT * FROM Meses

Gerando uma lista de dias da semana começando pelo domingo:

SET LANGUAGE 'Brazilian'
;WITH DiaSemana AS
(
  SELECT 1 AS idDSe, DATENAME(WEEKDAY, -1) AS nomeDSe
  UNION ALL
  SELECT idDSe + 1, DATENAME(WEEKDAY, idDSe -1)
  FROM DiaSemana
  WHERE idDSe < 7
)
SELECT idDSe, nomeDSe FROM DiaSemana

Ou pela Segunda-Feira:

SET LANGUAGE 'Brazilian'
;WITH DiaSemana AS
(
  SELECT 1 AS idDSe, DATENAME(WEEKDAY, 0) AS nomeDSe
  UNION ALL
  SELECT idDSe + 1, DATENAME(WEEKDAY, idDSe)
  FROM DiaSemana
  WHERE idDSe < 7
)
SELECT idDSe, nomeDSe FROM DiaSemana

Nas duas próximas queries, avanço um pouco a complexidade das CTEs, utilizando a view syslanguage e uma CTE de Split que modifiquei do site do Zavaschi. Elas realmente se diferenciam pelo campo utilizado da syslanguages facilitando alterações neste sentido.

Gerando uma lista dos meses:

DECLARE @Texto VARCHAR(8000),
        @Delimitador CHAR
SET @Texto = (SELECT months FROM sys.syslanguages WHERE alias = 'Brazilian')
SET @Delimitador = ','
;WITH _SPLIT(ID, _INDEX, _LENGTH) AS
(
  SELECT
    1,
    1,
    CHARINDEX(@Delimitador, @Texto + @Delimitador)
UNION ALL
  SELECT
    ID + 1,
    _LENGTH + 1,
    CHARINDEX(@Delimitador, @Texto + @Delimitador, _LENGTH + 1)
  FROM _SPLIT
  WHERE CHARINDEX(@Delimitador, @Texto + @Delimitador, _LENGTH + 1) <> 0
)
, Meses (idMes, nomeMes) AS
(
  SELECT
    ID,
    SUBSTRING(@Texto, _INDEX, _LENGTH - _INDEX)
  FROM _SPLIT
)
SELECT idMes, nomeMes FROM Meses

Gerando uma lista de dias da semana:

DECLARE @Texto VARCHAR(8000),
        @Delimitador CHAR
SET @Texto = (SELECT days FROM sys.syslanguages WHERE alias = 'Brazilian')
SET @Delimitador = ','
;WITH _SPLIT(ID, _INDEX, _LENGTH) AS
(
  SELECT
    1,
    1,
    CHARINDEX(@Delimitador, @Texto + @Delimitador)
UNION ALL
  SELECT
    ID + 1,
    _LENGTH + 1,
    CHARINDEX(@Delimitador, @Texto + @Delimitador, _LENGTH + 1)
  FROM _SPLIT
  WHERE CHARINDEX(@Delimitador, @Texto + @Delimitador, _LENGTH + 1) <> 0
)
, DiaSemana (idDSe, nomeDSe) AS
(
  SELECT
    ID,
    SUBSTRING(@Texto, _INDEX, _LENGTH - _INDEX)
  FROM _SPLIT
)
SELECT idDSe, nomeDSe FROM DiaSemana

Eu espero que elas respondam as necessidades que venham a aparecer!

Não se esqueçam de comentar!

Trabalhando com datas – Extraindo informações

Boa noite pessoal!

Continuando o tópico “trabalhando com datas”, continuarei a relacionar algumas dificuldades com extrair informações de tipos DATETIME.

Função responsável por obter a data e hora atual:

GETDATE()
Ou
CURRENT_TIMESTAMP

Outras funções utilizadas para obter de pequenos fragmentos de datas como ano, hora, dia:

Função Exemplo
DAY SELECT DAY(GETDATE())
MONTH SELECT MONTH(GETDATE())
YEAR SELECT YEAR(GETDATE())

Obs.: As três funções acima não funcionam em SQL CE.

Para obter informações sobre dia de semana, hora, minuto, segundo e outras, não é possível pelas três funções acima, sendo necessário utilizar a função DATEPART, exemplo:

SELECT DATEPART(YEAR, GETDATE())
SELECT DATEPART(MONTH, GETDATE())
SELECT DATEPART(WEEKDAY, GETDATE())
SELECT DATEPART(DAY, GETDATE())
SELECT DATEPART(HOUR, GETDATE())
SELECT DATEPART(MINUTE, GETDATE())
SELECT DATEPART(SECOND, GETDATE())

Na biblioteca do MSDN, também existe uma relação de todas as dateparts possíveis:
http://msdn.microsoft.com/en-us/library/ms174420.aspx

Também existe a função DATENAME, que retorna, quando possível, o nome do mês ou do dia da semana, exemplo:

SELECT DATENAME(MONTH, GETDATE())
SELECT DATENAME(WEEKDAY, GETDATE())

Pequeno detalhe, se o servidor estiver no idioma inglês será necessário utilizar a seguinte instrução:

SET LANGUAGE 'Brazilian'
Ou
SET LANGUAGE 'Portuguese'

Obs.: No SQL Server, o idioma Brasileiro existe e funciona melhor que o português, exemplo:

SET LANGUAGE 'Portuguese'
SELECT DATENAME(MONTH, GETDATE()), DATENAME(WEEKDAY, GETDATE())
Ou
SET LANGUAGE 'Brazilian'
SELECT DATENAME(MONTH, GETDATE()), DATENAME(WEEKDAY, GETDATE())

A relação dos idiomas possíveis pode ser conseguida pela view:

SELECT * FROM syslanguages

Somente para completar o post anterior, abaixo as funções para pegar dia/mês/ano e hora:minuto:segundo:

SELECT CONVERT(VARCHAR(8),GETDATE(),103)
SELECT CONVERT(VARCHAR(8),GETDATE(),108)

E o diferencial de trabalhar com SQL Server 2008, onde existem os tipos DATE e TIME, sendo possível obter estas informações até mesmo por um simples cast: 

SELECT CAST(GETDATE() AS DATE)
SELECT CAST(GETDATE() AS TIME)

(at. http://blog.sqlauthority.com/2009/08/06/sql-server-get-time-in-hourminute-format-from-a-datetime-get-date-part-only-from-datetime/ )

Pessoal, então finalizando este post, agradeço as visitas nestes quatro primeiros meses de blog que têm me animado bastante a continuar escrevendo, principalmente os emails e comentários que tenho recebido.

Até o próximo post!

Trabalhando com datas – dia/mes/ano

Durante todos os dias me deparo com muitas queries trabalhando com datas, principalmente em T-SQL, algumas bem complexas e outras simples, que muita gente ainda insiste em complicar. O MVP Gustavo Maia Aguiar trabalhou alguns tópicos neste sentido no seu blog, mas o meu objetivo é trabalhar do mais simples (formato da data) até passear um pouco em cálculos complexos com datas nos próximos tópicos.

Neste primeiro post demonstrarei como formatar campos e variáveis do tipo DATETIME para dia/mês/ano, utilizando a função Convert. O motivo de trabalhar isso é pelo fato de encontrar muitas vezes, algo assim:

Cabeçalho para demonstrações:

DECLARE @data DATETIME
SET @data = GETDATE()

Não faça isso:

SELECT CAST(DAY(@data), VARCHAR) + '/' + CAST(MONTH(@data), VARCHAR) + '/' + CAST(YEAR(@data), VARCHAR)

E até mesmo algumas coisas assim:

Também não faça isso:

SELECT
  (CASE WHEN DAY(@data) > 9
    THEN ''
    ELSE '0'
   END) +
CAST(DATE(@data) AS VARCHAR) +
  (CASE WHEN MONTH(@data) > 9
    THEN '/'
    ELSE '/0'
   END) +
CAST(MONTH(@data) AS VARCHAR) +
  '/' +
CAST(YEAR(@data) AS VARCHAR)

Se o objetivo era demonstrar o conhecimento em lógica, realmente os dois exemplos acima já me impressionam, pois não é qualquer um que chega a este raciocínio. Mas vamos para a versão que chamo de forma elegante:

SELECT CONVERT(VARCHAR, @data, 103)

Gostou da simplicidade, dê uma olhada no tópico de CAST e CONVERT da biblioteca do MSDN, que possui uma relação gigante de formatos possíveis.
http://msdn.microsoft.com/en-us/library/ms187928.aspx