Trabalhando com datas no SQL Server
- Amanda Nascimento
- 29 de mai.
- 9 min de leitura
Atualizado: 22 de jul.
O tratamento de datas é uma parte essencial do dia a dia de quem trabalha com banco de dados no SQL Server. Seja para filtrar registros por período, calcular diferenças entre datas ou formatar resultados, entender como lidar com tipos de dados de data é fundamental.
Boas práticas com datas no SQL Server
✅ Armazene datas no formato ISO (yyyy-MM-dd) para evitar ambiguidades.
✅ Sempre use parâmetros em procedimentos armazenados para evitar problemas com formatação de data.
✅ Não use FORMAT() em filtros (piora a performance).
✅ Para intervalos, use >= e < em vez de BETWEEN com DATETIME.
Filtrar registros por datas
-- Registros de um único dia
SELECT * FROM pedidos
WHERE data_pedido = '2024-05-29';
-- Registros de um intervalo de datas
SELECT * FROM pedidos
WHERE data_pedido BETWEEN '2024-05-01' AND '2024-05-31';
Cuidado com o uso de BETWEEN em campos DATETIME, pois ele considera horas! Para incluir o dia final por completo, use:
-- Incluir o dia 2024-05-31 inteiro
WHERE data_pedido >= '2024-05-01' AND data_pedido < '2024-06-01'
Comando SET format dmy
SET DATEFORMAT dmy;
GO
Quando você usa o comando SET DATEFORMAT dmy; no início do script, você está configurando o SQL Server para interpretar as datas no formato dia/mês/ano durante toda a sessão (ou até que outro SET DATEFORMAT seja aplicado).
Como isso funciona no seu script:
SET DATEFORMAT dmy; define que as strings de data devem ser lidas como DD/MM/YYYY.
Isso evita erros de interpretação quando você insere datas como '25/12/2023' (25 de dezembro), que em um formato mdy (padrão em alguns sistemas) seria interpretado erroneamente como 12 de dezembro (ou até causar um erro).
✅ Vantagens:
Você não precisa ficar convertendo datas manualmente (CONVERT(DATE, ... , 103)).
Padroniza a interpretação de datas em todo o script.
⚠️ Cuidados:
Essa configuração só vale para a sessão atual (se desconectar e reconectar, volta ao padrão do servidor).
Se o script for executado em um ambiente com configuração diferente (mdy ou ymd), pode causar inconsistências.
Em alguns casos (como em bancos internacionais), é melhor usar formato explícito ISO (YYYYMMDD) ou CONVERT()/PARSE() para evitar ambiguidades.
Retornar data e hora
FORMAT(GETDATE(), 'dd/MM/yyyy HH:mm:ss') AS 'DATA_RELATORIO'
FORMAT(GETDATE(), 'HH:mm:ss') AS 'HORA_ATUAL'
Formatando datas para exibição
A função CONVERT() tem a seguinte sintaxe:
CONVERT(tipo_de_dado, expressão, estilo)
Para datas, usaremos: CONVERT(VARCHAR, GETDATE(), estilo)
SELECT
GETDATE() AS data_original,
CONVERT(VARCHAR, GETDATE(), 105) AS formato_italiano,
CONVERT(VARCHAR, GETDATE(), 103) AS formato_brasileiro,
CONVERT(VARCHAR, GETDATE(), 120) AS formato_iso
O FORMAT() também formata datas com flexibilidade, mas é mais lento do que CONVERT() e deve ser evitado em consultas com muitas linhas, em grandes volumes de dados.
Exemplo:
DATE(), 'dd/MM/yyyy')
-- Converte a data para string em formato brasileiro
Formatando data
SELECT FORMAT(GETDATE(), 'dd/MM/yyyy') AS DataBR;
SELECT FORMAT(GETDATE(), 'dddd', 'pt-BR') AS DiaSemana;
Formatando data e hora
SELECT FORMAT(GETDATE(), 'dd/MM/yyyy HH:mm') AS DataHora;
-- Resultado: '01/06/2025 14:45'
Para exportações ou comparações com outros sistemas, os estilos 120 ou 121 (ISO 8601) são os mais recomendados.
Estilo | Resultado | Descrição |
0 | May 29 2025 3:45PM | Sem segundos |
1 | 05/29/25 | EUA (2 dígitos) |
2 | 25.05.29 | ANSI padrão |
3 | ######## | Europa (DD/MM/YY) |
4 | 29.05.25 | Europa com ponto |
5 | ######## | Europa com hífen |
6 | ######## | Abrev. mês em texto |
7 | May 29, 25 | EUA com vírgula |
8 | 15:45:12 | Apenas hora |
9 | May 29 2025 3:45:12:123PM | Inglês detalhado |
10 | 05-29-25 | EUA com hífen |
11 | ######## | Invertido |
12 | 250529 | Compacto |
13 | 29 May 2025 15:45:12:123 | Europa com hora |
14 | 15:45:12:123 | Hora com milissegundos |
20 | ######## | ISO 8601 padrão |
21 | 2025-05-29 15:45:12.123 | ISO com milissegundos |
22 | 05/29/25 3:45:12 PM | EUA com hora |
23 | ######## | ISO (somente data) |
24 | 15:45:12 | Somente hora (24h) |
25 | 2025-05-29 15:45:12.123 | ISO completo |
100 | May 29 2025 3:45PM | Igual ao estilo 0 |
101 | 05/29/2025 | EUA completo |
102 | 20250529 | Compacto ISO |
103 | ######## | Brasil / Reino Unido |
104 | 29.05.2025 | Alemanha |
105 | ######## | Itália |
106 | 29 May 2025 | Abreviado |
107 | May 29, 2025 | EUA com vírgula |
108 | 15:45:12 | Apenas hora |
109 | May 29 2025 3:45:12:123PM | Inglês com hora |
110 | 05-29-2025 | EUA com hífen |
111 | ######## | Japão |
112 | 20250529 | Compacto sem separadores |
113 | 29 May 2025 15:45:12:123 | Europa com hora |
114 | 15:45:12:123 | Hora completa |
120 | ######## | ISO sem milissegundos |
121 | 2025-05-29 15:45:12.123 | ISO com milissegundos |
126 | 2025-05-29T15:45:12 | ISO 8601 (T separador) |
127 | 2025-05-29T15:45:12.123 | ISO com milissegundos |
Funções para extrair partes da data
SELECT
YEAR(data_pedido) AS Ano,
MONTH(data_pedido) AS Mes,
DAY(data_pedido) AS Dia,
DATEPART(HOUR, data_pedido) AS Hora,
DATEPART(MINUTE, data_pedido) AS Minuto
FROM pedidos
Diferença entre datas
--- Diferença em dias
SELECT DATEDIFF(DAY, '2024-01-01', GETDATE()) AS DiasDesdeInicioDoAno;
-- Diferença em minutos
SELECT DATEDIFF(MINUTE, horario_entrada, horario_saida) AS TempoTrabalhado
-- Contagem de dias entre duas datas
CASE
WHEN PFUNC.DATADEMISSAO IS NULL THEN DATEDIFF(DAY, PFUNC.DATAADMISSAO, GETDATE())
WHEN PFUNC.DATADEMISSAO IS NOT NULL THEN DATEDIFF(DAY, PFUNC.DATAADMISSAO, PFUNC.DATADEMISSAO)
END AS 'QNT_DIAS'
Adicionando/Subtraindo datas
-- Soma de 30 dias
SELECT DATEADD(DAY, 30, GETDATE()) AS DataFutura;
-- Subtrai 2 horas
SELECT DATEADD(HOUR, -2, GETDATE()) AS DuasHorasAtras;
Alterar dia para o último dia do mês
FORMAT(EOMONTH(PFUNC.DATADEMISSAO), 'dd/MM/yyyy') AS DATA_DEMISSAO,
Alterar data para o mês anterior
FORMAT(DATEADD(MONTH, -1, CONVERT(DATE, datCredito, 103)), 'dd/MM/yyyy') AS MES_ANT,
-- Data do mês anterior
format(DATEADD(MONTH, -1, GETDATE()), 'dd/MM/yyyy')
-- Data do mês anterior considerando o último dia do mês anterior
FORMAT(EOMONTH(DATEADD(MONTH, -1, GETDATE())), 'dd/MM/yyyy')
-- Buscar registros até o último dia do mês anterior
WHERE
((AABONFUN.HORAFIM - AABONFUN.HORAINICIO) > 0)
WHERE ((AABONFUN.HORAFIM-AABONFUN.HORAINICIO)>0) AND AABONFUN.DATA>='01/01/2022' AND AABONFUN.DATA <= DATEADD(DAY, -1, EOMONTH(GETDATE(), -1))
-- Retornar as demissões do mês anterior + quadro de funcionários que estão ativos
AND (MONTH(PFUNC.DATADEMISSAO) >= (MONTH(GETDATE())-1) AND YEAR(PFUNC.DATADEMISSAO) = (YEAR(GETDATE())))
OR (PFUNC.DATADEMISSAO IS NULL)
Convertendo textos em datas
-- Convertendo string em data
SELECT CONVERT(DATE, '2024-05-29', 103); -- Formato BR (dd/mm/yyyy)
SELECT CAST('2024-05-29 14:00' AS DATETIME) AS DataHora;
-- Convertendo a data 20240506003506 06/05/2024 00:35:06
porem sem os segundos:
-- Data e hora do saldo (formato AAAAMMDDHHMMSS → smalldatetime)
CASE WHEN LEN([DtHoraSaldo]) = 14 THEN
TRY_CAST(
STUFF(STUFF(STUFF([DtHoraSaldo], 13, 0, ':'), 11, 0, ':'), 9, 0, ' ') AS smalldatetime
) ELSE NULL END AS [DtHoraSaldo],
Calculando a idade de uma pessoa
Um erro comum ao calcular a idade de uma pessoa é usar apenas o DATEDIFF(YEAR, data_nascimento, GETDATE()), pois ele considera apenas os anos completos entre as duas datas, sem se importar com o mês ou o dia.
▶ A forma mais precisa é comparar os anos e verificar se a data de aniversário já passou.
DATEDIFF(YEAR, nascimento, hoje) dá a diferença bruta em anos.
DATEADD(...) soma esse número de anos à data de nascimento — ou seja, calcula o aniversário da pessoa neste ano.
Se esse aniversário ainda não chegou, subtrai 1 da idade.
SELECT
FORMAT(DTNASCIMENTO, 'dd/MM/yyyy') AS DATA_NASCIMENTO,
DATEDIFF(YEAR, DTNASCIMENTO, GETDATE())
CASE
WHEN DATEADD(YEAR, DATEDIFF(YEAR, DTNASCIMENTO, GETDATE()), DTNASCIMENTO) > GETDATE()
THEN 1
ELSE 0
END AS IDADE_CORRETA
FROM PPESSOA;
▶ Também podemos calcular a idade com maior precisão usando uma combinação de FLOOR e o número de dias entre as datas. Essa abordagem é uma aproximação que considera anos bissextos (~365,25 dias), útil em relatórios rápidos, mas não tão precisa quanto a comparação direta de datas.
SELECT
FLOOR(DATEDIFF(DAY, DTNASCIMENTO, GETDATE()) / 365.25) AS IDADE_ESTIMADA
FROM PPESSOA;
▶ Uma forma precisa e didática:
Ele calcula a idade com DATEDIFF(YEAR, nascimento, hoje) e depois verifica se a pessoa já fez aniversário neste ano:
Se o mês de nascimento ainda não chegou → subtrai 1 da idade
Se o mês é o mesmo, mas o dia ainda não chegou → subtrai 1 da idade
Caso contrário, a idade está correta
CASE
WHEN MONTH(PFDEPEND.DTNASCIMENTO) > MONTH(GETDATE())
THEN DATEDIFF(YEAR, PFDEPEND.DTNASCIMENTO, GETDATE()) - 1
WHEN MONTH(PFDEPEND.DTNASCIMENTO) = MONTH(GETDATE()) AND DAY(PFDEPEND.DTNASCIMENTO) > DAY(GETDATE())
THEN DATEDIFF(YEAR, PFDEPEND.DTNASCIMENTO, GETDATE()) - 1
ELSE DATEDIFF(YEAR, PFDEPEND.DTNASCIMENTO, GETDATE())
END AS Idade
Calculando o tempo de empresa através da data de demissão, admissão ou data atual
CONCAT(AVG(
CASE
WHEN PFUNC.DATADEMISSAO IS NULL THEN DATEDIFF(DAY, PFUNC.DATAADMISSAO, GETDATE())
WHEN PFUNC.DATADEMISSAO IS NOT NULL THEN DATEDIFF(DAY, PFUNC.DATAADMISSAO, PFUNC.DATADEMISSAO)
END
) / 365,' anos e ',
CASE
WHEN FORMAT(CONCAT('0.',SUBSTRING((CAST(ROUND(AVG(
CASE
WHEN PFUNC.DATADEMISSAO IS NULL THEN DATEDIFF(DAY, PFUNC.DATAADMISSAO, GETDATE())
WHEN PFUNC.DATADEMISSAO IS NOT NULL THEN DATEDIFF(DAY, PFUNC.DATAADMISSAO, PFUNC.DATADEMISSAO)
END
) / 365.0, 2) AS varchar(38))), 3, 2)) * CAST(365 AS DECIMAL(18, 2)) / 30, '0') <= 1 THEN '1 mês'
ELSE CONCAT(FORMAT(CONCAT('0.',SUBSTRING((CAST(ROUND(AVG(
CASE
WHEN PFUNC.DATADEMISSAO IS NULL THEN DATEDIFF(DAY, PFUNC.DATAADMISSAO, GETDATE())
WHEN PFUNC.DATADEMISSAO IS NOT NULL THEN DATEDIFF(DAY, PFUNC.DATAADMISSAO, PFUNC.DATADEMISSAO)
END
) / 365.0, 2) AS varchar(38))), 3, 2)) * CAST(365 AS DECIMAL(18, 2)) / 30, '0'), ' meses')
END) AS ANOS_E_MESES
Relatórios que precisam de períodos mensais dinâmicos baseados na data atual
Se hoje for dia 15 ou depois: Pega o primeiro dia do mês atual
Se hoje for antes do dia 15: Pega o primeiro dia do mês anterior
SET @DataInicial =
CASE WHEN DAY(getdate())<15 THEN CONVERT(date, '01' + RIGHT(CONVERT(varchar(10), DATEADD(month, -1, getdate()), 105), 8), 105)
ELSE CONVERT(date, '01' + RIGHT(CONVERT(varchar(10), getdate(), 105), 8), 105)
END;
SET @DataFinal = CONVERT(date, getdate());
--Opção 2
SET @DataInicial = DATEADD(DAY, 1-DAY(getdate()),
CASE WHEN DAY(getdate()) < 15 THEN DATEADD(MONTH, -1, getdate())
ELSE getdate() END);
O estilo 105 (dd-mm-yyyy) garante que a formatação seja consistente independente das configurações regionais do SQL Server.
Data Atual: 2024-02-10 (dia 10)
CASE WHEN: 10 < 15? SIM → 2024-01-10
DATEADD: 1-10 = -9 → 10/01 - 9 dias = 01/01 Resultado: 2024-01-01
Data Atual: 2024-02-20 (dia 20)
CASE WHEN: 20 < 15? NÃO → 2024-02-20
DATEADD: 1-20 = -19 → 20/02 - 19 dias = 01/02 Resultado: 2024-02-01