Estou construindo um banco de dados com Postgres onde haverá um monte de agrupamento de coisas por month
e year
, mas nunca pelo date
.
- Eu poderia criar inteiros
month
eyear
colunas e usá-los. - Ou eu poderia ter uma
month_year
coluna e sempre definirday
como 1.
O primeiro parece um pouco mais simples e claro se alguém estiver analisando os dados, mas o último é bom porque usa um tipo adequado.
Pessoalmente, se é um encontro, ou pode ser um encontro, sugiro sempre armazená-lo como um. É apenas mais fácil de trabalhar como regra geral.
Você pode ter uma data que suportará o dia, se precisar, ou uma
smallint
para ano e mês que nunca suportará a precisão extra.Dados de amostra
Vamos ver um exemplo agora. Vamos criar 1 milhão de datas para nossa amostra. Isso é aproximadamente 5.000 linhas por 200 anos entre 1901 e 2100. Todo ano deve ter algo para cada mês.
Teste
Simples
WHERE
Agora podemos testar essas teorias de não usar data. Executei cada uma delas algumas vezes para esquentar as coisas.
Agora, vamos tentar o outro método com eles separados
Para ser justo, nem todos são 0,749... alguns são um pouco mais ou menos, mas não importa. São todos relativamente iguais. Simplesmente não é necessário.
Dentro de um mês
Agora, vamos nos divertir com isso. Digamos que você queira encontrar todos os intervalos dentro de 1 mês de janeiro de 2014 (o mesmo mês que usamos acima).
Compare isso com o método combinado
É mais lento e mais feio.
GROUP BY
/ORDER BY
Método combinado,
E novamente com o método composto
Conclusão
Geralmente, deixe as pessoas inteligentes fazerem o trabalho duro. Datemath é difícil, meus clientes não me pagam o suficiente. Eu costumava fazer esses testes. Eu estava duramente pressionado para concluir que eu poderia obter melhores resultados do que
date
. Eu parei de tentar.ATUALIZAÇÕES
@a_horse_with_no_name sugerido para meu teste de um mês
WHERE (year, month) between (2013, 12) and (2014,2)
. Na minha opinião, embora legal, essa é uma consulta mais complexa e prefiro evitá-la, a menos que haja um ganho. Infelizmente, ainda foi mais lento, embora esteja perto - o que é mais importante para este teste. Simplesmente não importa muito.Como alternativa ao método proposto por Evan Carroll, que considero provavelmente a melhor opção, usei em algumas ocasiões (e não especialmente no PostgreSQL) apenas uma
year_month
coluna, do tipoINTEGER
(4 bytes), computada comoOu seja, você codifica o mês nos dois dígitos decimais mais à direita (dígito 0 e dígito 1) do número inteiro e o ano nos dígitos de 2 a 5 (ou mais, se necessário).
Esta é, até certo ponto, a alternativa de um homem pobre
year_month
para construir seu próprio tipo e operadores. Tem algumas vantagens, principalmente "clareza de intenção", e alguma economia de espaço (não no PostgreSQL, eu acho), e também alguns inconvenientes, por ter duas colunas separadas.Você pode garantir que os valores sejam válidos apenas adicionando um
Você pode ter uma
WHERE
cláusula parecida com:e funciona de forma eficiente (se a
year_month
coluna estiver indexada corretamente, é claro).Você pode agrupar
year_month
da mesma forma que faria com uma data, e com a mesma eficiência (pelo menos).Se você precisar separar
year
emonth
, o cálculo é simples:O que é inconveniente : se você quiser adicionar 15 meses a um
year_month
, você deve calcular (se eu não tiver cometido um erro ou descuido):Se você não for cuidadoso, isso pode ser propenso a erros.
Se você deseja obter o número de meses entre dois anos_meses, você precisa fazer alguns cálculos semelhantes. Isso é (com muitas simplificações) o que realmente acontece nos bastidores com a aritmética de datas, que felizmente está escondida de nós através de funções e operadores já definidos.
Se você precisar de muitas dessas operações, usar
year_month
não é muito prático. Se você não fizer isso, é uma maneira muito clara de tornar sua intenção clara.Como alternativa, você pode definir um
year_month
tipo e definir um operadoryear_month
+interval
, e também outroyear_month
-year_month
... e ocultar os cálculos. Na verdade, nunca fiz um uso tão pesado a ponto de sentir a necessidade na prática. Adate
-date
está realmente escondendo algo semelhante.Como alternativa ao método do joanolo =) (desculpe, estava ocupado, mas queria escrever isso)
POUCO DE ALEGRIA
Vamos fazer a mesma coisa, mas com bits. Um
int4
no PostgreSQL é um inteiro com sinal, variando de -2147483648 a +2147483647Aqui está uma visão geral, da nossa estrutura.
Mês de armazenamento.
pow(2,4)
é 4 bits .Aqui está nosso mapa de bits de onde os meses são armazenados.
Meses, 1º de janeiro a 12 de dezembro
Anos. Os 28 bits restantes nos permitem armazenar nossas informações de ano
Neste ponto, precisamos decidir como queremos fazer isso. Para nossos propósitos, poderíamos usar um deslocamento estático, se precisarmos cobrir apenas 5.000 dC, poderíamos voltar para o
268,430,455 BC
que cobre praticamente todo o Mesozóico e tudo o que for útil no futuro.E, agora, temos os rudimentos do nosso tipo, programados para expirar em 2.700 anos.
Então vamos trabalhar para fazer algumas funções.
Um teste rápido mostra isso funcionando ..
Agora temos funções que podemos usar em nossos tipos binários.
Poderíamos ter cortado mais um bit da parte assinada, armazenado o ano como positivo e, em seguida, classificá-lo naturalmente como um inteiro assinado. Se a velocidade fosse uma prioridade mais alta do que o espaço de armazenamento, esse seria o caminho que seguiríamos. Mas, por enquanto, temos uma data que funciona com o Mesozóico.
Posso atualizar mais tarde com isso, apenas por diversão.