AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • Início
  • system&network
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • Início
  • system&network
    • Recentes
    • Highest score
    • tags
  • Ubuntu
    • Recentes
    • Highest score
    • tags
  • Unix
    • Recentes
    • tags
  • DBA
    • Recentes
    • tags
  • Computer
    • Recentes
    • tags
  • Coding
    • Recentes
    • tags
Início / dba / Perguntas / 166117
Accepted
David N. Welton
David N. Welton
Asked: 2017-03-04 09:20:55 +0800 CST2017-03-04 09:20:55 +0800 CST 2017-03-04 09:20:55 +0800 CST

Colunas de mês e ano separadas ou data com dia sempre definido como 1?

  • 772

Estou construindo um banco de dados com Postgres onde haverá um monte de agrupamento de coisas por monthe year, mas nunca pelo date.

  • Eu poderia criar inteiros monthe yearcolunas e usá-los.
  • Ou eu poderia ter uma month_yearcoluna e sempre definir daycomo 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.

database-design postgresql
  • 3 3 respostas
  • 9522 Views

3 respostas

  • Voted
  1. Best Answer
    Evan Carroll
    2017-03-04T10:10:51+08:002017-03-04T10:10:51+08:00

    Pessoalmente, se é um encontro, ou pode ser um encontro, sugiro sempre armazená-lo como um. É apenas mais fácil de trabalhar como regra geral.

    • Uma data tem 4 bytes.
    • Um smallint tem 2 bytes (precisamos de dois)
      • ... 2 bytes: um smallint por ano
      • ... 2 bytes: um smallint por mês

    Você pode ter uma data que suportará o dia, se precisar, ou uma smallintpara 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.

    CREATE TABLE foo
    AS
      SELECT
        x,
        make_date(year,month,1)::date AS date,
        year::smallint,
        month::smallint
      FROM generate_series(1,1e6) AS gs(x)
      CROSS JOIN LATERAL CAST(trunc(random()*12+1+x-x) AS int) AS month
      CROSS JOIN LATERAL CAST(trunc(random()*200+1901+x-x) AS int) AS year
    ;
    CREATE INDEX ON foo(date);
    CREATE INDEX ON foo (year,month);
    VACUUM FULL ANALYZE foo;
    

    Teste

    SimplesWHERE

    Agora podemos testar essas teorias de não usar data. Executei cada uma delas algumas vezes para esquentar as coisas.

    EXPLAIN ANALYZE SELECT * FROM foo WHERE date = '2014-1-1'
                                                            QUERY PLAN                                                        
    --------------------------------------------------------------------------------------------------------------------------
     Bitmap Heap Scan on foo  (cost=11.56..1265.16 rows=405 width=14) (actual time=0.164..0.751 rows=454 loops=1)
       Recheck Cond: (date = '2014-04-01'::date)
       Heap Blocks: exact=439
       ->  Bitmap Index Scan on foo_date_idx  (cost=0.00..11.46 rows=405 width=0) (actual time=0.090..0.090 rows=454 loops=1)
             Index Cond: (date = '2014-04-01'::date)
     Planning time: 0.090 ms
     Execution time: 0.795 ms
    

    Agora, vamos tentar o outro método com eles separados

    EXPLAIN ANALYZE SELECT * FROM foo WHERE year = 2014 AND month = 1;
                                                               QUERY PLAN                                                           
    --------------------------------------------------------------------------------------------------------------------------------
     Bitmap Heap Scan on foo  (cost=12.75..1312.06 rows=422 width=14) (actual time=0.139..0.707 rows=379 loops=1)
       Recheck Cond: ((year = 2014) AND (month = 1))
       Heap Blocks: exact=362
       ->  Bitmap Index Scan on foo_year_month_idx  (cost=0.00..12.64 rows=422 width=0) (actual time=0.079..0.079 rows=379 loops=1)
             Index Cond: ((year = 2014) AND (month = 1))
     Planning time: 0.086 ms
     Execution time: 0.749 ms
    (7 rows)
    

    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).

    EXPLAIN ANALYZE
      SELECT *
      FROM foo
      WHERE date
        BETWEEN
          ('2014-1-1'::date - '1 month'::interval)::date 
          AND ('2014-1-1'::date + '1 month'::interval)::date;
                                                            QUERY PLAN                                                         
    ---------------------------------------------------------------------------------------------------------------------------
     Bitmap Heap Scan on foo  (cost=21.27..2310.97 rows=863 width=14) (actual time=0.384..1.644 rows=1226 loops=1)
       Recheck Cond: ((date >= '2013-12-01'::date) AND (date <= '2014-02-01'::date))
       Heap Blocks: exact=1083
       ->  Bitmap Index Scan on foo_date_idx  (cost=0.00..21.06 rows=863 width=0) (actual time=0.208..0.208 rows=1226 loops=1)
             Index Cond: ((date >= '2013-12-01'::date) AND (date <= '2014-02-01'::date))
     Planning time: 0.104 ms
     Execution time: 1.727 ms
    (7 rows)
    

    Compare isso com o método combinado

    EXPLAIN ANALYZE
      SELECT *
      FROM foo
      WHERE year = 2013 AND month = 12
        OR ( year = 2014 AND ( month = 1 OR month = 2) );
    
                                                                     QUERY PLAN                                                                 
    --------------------------------------------------------------------------------------------------------------------------------------------
     Bitmap Heap Scan on foo  (cost=38.79..2999.66 rows=1203 width=14) (actual time=0.664..2.291 rows=1226 loops=1)
       Recheck Cond: (((year = 2013) AND (month = 12)) OR (((year = 2014) AND (month = 1)) OR ((year = 2014) AND (month = 2))))
       Heap Blocks: exact=1083
       ->  BitmapOr  (cost=38.79..38.79 rows=1237 width=0) (actual time=0.479..0.479 rows=0 loops=1)
             ->  Bitmap Index Scan on foo_year_month_idx  (cost=0.00..12.64 rows=421 width=0) (actual time=0.112..0.112 rows=402 loops=1)
                   Index Cond: ((year = 2013) AND (month = 12))
             ->  BitmapOr  (cost=25.60..25.60 rows=816 width=0) (actual time=0.218..0.218 rows=0 loops=1)
                   ->  Bitmap Index Scan on foo_year_month_idx  (cost=0.00..12.62 rows=420 width=0) (actual time=0.108..0.108 rows=423 loops=1)
                         Index Cond: ((year = 2014) AND (month = 1))
                   ->  Bitmap Index Scan on foo_year_month_idx  (cost=0.00..12.38 rows=395 width=0) (actual time=0.108..0.108 rows=401 loops=1)
                         Index Cond: ((year = 2014) AND (month = 2))
     Planning time: 0.256 ms
     Execution time: 2.421 ms
    (13 rows)
    

    É mais lento e mais feio.

    GROUP BY/ORDER BY

    Método combinado,

    EXPLAIN ANALYZE
      SELECT date, count(*)
      FROM foo
      GROUP BY date
      ORDER BY date;
                                                            QUERY PLAN                                                        
    --------------------------------------------------------------------------------------------------------------------------
     Sort  (cost=20564.75..20570.75 rows=2400 width=4) (actual time=286.749..286.841 rows=2400 loops=1)
       Sort Key: date
       Sort Method: quicksort  Memory: 209kB
       ->  HashAggregate  (cost=20406.00..20430.00 rows=2400 width=4) (actual time=285.978..286.301 rows=2400 loops=1)
             Group Key: date
             ->  Seq Scan on foo  (cost=0.00..15406.00 rows=1000000 width=4) (actual time=0.012..70.582 rows=1000000 loops=1)
     Planning time: 0.094 ms
     Execution time: 286.971 ms
    (8 rows)
    

    E novamente com o método composto

    EXPLAIN ANALYZE
      SELECT year, month, count(*)
      FROM foo
      GROUP BY year, month
      ORDER BY year, month;
                                                            QUERY PLAN                                                        
    --------------------------------------------------------------------------------------------------------------------------
     Sort  (cost=23064.75..23070.75 rows=2400 width=4) (actual time=336.826..336.908 rows=2400 loops=1)
       Sort Key: year, month
       Sort Method: quicksort  Memory: 209kB
       ->  HashAggregate  (cost=22906.00..22930.00 rows=2400 width=4) (actual time=335.757..336.060 rows=2400 loops=1)
             Group Key: year, month
             ->  Seq Scan on foo  (cost=0.00..15406.00 rows=1000000 width=4) (actual time=0.010..70.468 rows=1000000 loops=1)
     Planning time: 0.098 ms
     Execution time: 337.027 ms
    (8 rows)
    

    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êsWHERE (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.

    EXPLAIN ANALYZE
      SELECT *
      FROM foo
      WHERE (year, month) between (2013, 12) and (2014,2);
    
                                                                  QUERY PLAN                                                              
    --------------------------------------------------------------------------------------------------------------------------------------
     Bitmap Heap Scan on foo  (cost=5287.16..15670.20 rows=248852 width=14) (actual time=0.753..2.157 rows=1226 loops=1)
       Recheck Cond: ((ROW(year, month) >= ROW(2013, 12)) AND (ROW(year, month) <= ROW(2014, 2)))
       Heap Blocks: exact=1083
       ->  Bitmap Index Scan on foo_year_month_idx  (cost=0.00..5224.95 rows=248852 width=0) (actual time=0.550..0.550 rows=1226 loops=1)
             Index Cond: ((ROW(year, month) >= ROW(2013, 12)) AND (ROW(year, month) <= ROW(2014, 2)))
     Planning time: 0.099 ms
     Execution time: 2.249 ms
    (7 rows)
    
    • 20
  2. joanolo
    2017-03-04T15:37:16+08:002017-03-04T15:37:16+08:00

    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_monthcoluna, do tipo INTEGER(4 bytes), computada como

     year_month = year * 100 + month
    

    Ou 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 pobreyear_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

    CHECK ((year_date % 100) BETWEEN 1 AND 12)   /*  % = modulus operator */
    

    Você pode ter uma WHEREcláusula parecida com:

    year_month BETWEEN 201610 and 201702 
    

    e funciona de forma eficiente (se a year_monthcoluna estiver indexada corretamente, é claro).

    Você pode agrupar year_monthda mesma forma que faria com uma data, e com a mesma eficiência (pelo menos).

    Se você precisar separar yeare month, o cálculo é simples:

    month = year_month % 100    -- % is modulus operator
    year  = year_month / 100    -- / is integer division 
    

    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):

    year_month + delta (months) = ...
    
        /* intermediate calculations */
        year = year_month/100 + delta/12    /* years we had + new years */
               + (year_month % 100 + delta%12) / 12  /* extra months make 1 more year? */
        month = ((year_month%10) + (delta%12) - 1) % 12 + 1
    
    /* final result */
    ... = year * 100 + month
    

    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_monthnã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_monthtipo e definir um operador year_month+ interval, e também outro year_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. A date- dateestá realmente escondendo algo semelhante.

    • 6
  3. Evan Carroll
    2017-03-16T22:42:31+08:002017-03-16T22:42:31+08:00

    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 int4no PostgreSQL é um inteiro com sinal, variando de -2147483648 a +2147483647

    Aqui está uma visão geral, da nossa estrutura.

                   bit                
    ----------------------------------
     YYYYYYYYYYYYYYYYYYYYYYYYYYYYMMMM
    

    Mês de armazenamento.

    • Um mês requer 12 opções pow(2,4)é 4 bits .
    • O resto dedicamos ao ano, 32-4 = 28 bits .

    Aqui está nosso mapa de bits de onde os meses são armazenados.

                   bit                
    ----------------------------------
     00000000000000000000000000001111
    

    Meses, 1º de janeiro a 12 de dezembro

                   bit                
    ----------------------------------
     00000000000000000000000000000001
                   bit                
    ----------------------------------
     00000000000000000000000000001100
    

    Anos. Os 28 bits restantes nos permitem armazenar nossas informações de ano

    SELECT (pow(2,28)-1)::int;
       int4    
    -----------
     268435455
    (1 row)
    

    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 BCque cobre praticamente todo o Mesozóico e tudo o que for útil no futuro.

    SELECT (pow(2,28)-1)::int4::bit(32) << 4;
                   year               
    ----------------------------------
     11111111111111111111111111110000
    

    E, agora, temos os rudimentos do nosso tipo, programados para expirar em 2.700 anos.

    Então vamos trabalhar para fazer algumas funções.

    CREATE DOMAIN year_month AS int4;
    
    CREATE OR REPLACE FUNCTION to_year_month (cstring text)
    RETURNS year_month
    AS $$
      SELECT (
        ( ((date[1]::int4 - 5000) * -1)::bit(32) << 4 )
        | date[2]::int4::bit(32)
      )::year_month
      FROM regexp_split_to_array(cstring,'-(?=\d{1,2}$)')
        AS t(date)
    $$
    LANGUAGE sql
    IMMUTABLE;
    
    CREATE OR REPLACE FUNCTION year_month_to_text (ym year_month)
    RETURNS text
    AS $$
      SELECT ((ym::bit(32) >>4)::int4 * -1 + 5000)::text ||
      '-' ||
      (ym::bit(32) <<28 >>28)::int4::text
    $$ LANGUAGE sql
    IMMUTABLE;
    

    Um teste rápido mostra isso funcionando ..

    SELECT year_month_to_text( to_year_month('2014-12') );
    SELECT year_month_to_text( to_year_month('-5000-10') );
    SELECT year_month_to_text( to_year_month('-8000-10') );
    SELECT year_month_to_text( to_year_month('-84398-10') );
    

    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.

    • 5

relate perguntas

  • Práticas recomendadas para executar a replicação atrasada do deslocamento de tempo

  • Os procedimentos armazenados impedem a injeção de SQL?

  • Quais são algumas maneiras de implementar um relacionamento muitos-para-muitos em um data warehouse?

  • Sequências Biológicas do UniProt no PostgreSQL

  • Qual é a diferença entre a replicação do PostgreSQL 9.0 e o Slony-I?

Sidebar

Stats

  • Perguntas 205573
  • respostas 270741
  • best respostas 135370
  • utilizador 68524
  • Highest score
  • respostas
  • Marko Smith

    conectar ao servidor PostgreSQL: FATAL: nenhuma entrada pg_hba.conf para o host

    • 12 respostas
  • Marko Smith

    Como fazer a saída do sqlplus aparecer em uma linha?

    • 3 respostas
  • Marko Smith

    Selecione qual tem data máxima ou data mais recente

    • 3 respostas
  • Marko Smith

    Como faço para listar todos os esquemas no PostgreSQL?

    • 4 respostas
  • Marko Smith

    Listar todas as colunas de uma tabela especificada

    • 5 respostas
  • Marko Smith

    Como usar o sqlplus para se conectar a um banco de dados Oracle localizado em outro host sem modificar meu próprio tnsnames.ora

    • 4 respostas
  • Marko Smith

    Como você mysqldump tabela (s) específica (s)?

    • 4 respostas
  • Marko Smith

    Listar os privilégios do banco de dados usando o psql

    • 10 respostas
  • Marko Smith

    Como inserir valores em uma tabela de uma consulta de seleção no PostgreSQL?

    • 4 respostas
  • Marko Smith

    Como faço para listar todos os bancos de dados e tabelas usando o psql?

    • 7 respostas
  • Martin Hope
    Jin conectar ao servidor PostgreSQL: FATAL: nenhuma entrada pg_hba.conf para o host 2014-12-02 02:54:58 +0800 CST
  • Martin Hope
    Stéphane Como faço para listar todos os esquemas no PostgreSQL? 2013-04-16 11:19:16 +0800 CST
  • Martin Hope
    Mike Walsh Por que o log de transações continua crescendo ou fica sem espaço? 2012-12-05 18:11:22 +0800 CST
  • Martin Hope
    Stephane Rolland Listar todas as colunas de uma tabela especificada 2012-08-14 04:44:44 +0800 CST
  • Martin Hope
    haxney O MySQL pode realizar consultas razoavelmente em bilhões de linhas? 2012-07-03 11:36:13 +0800 CST
  • Martin Hope
    qazwsx Como posso monitorar o andamento de uma importação de um arquivo .sql grande? 2012-05-03 08:54:41 +0800 CST
  • Martin Hope
    markdorison Como você mysqldump tabela (s) específica (s)? 2011-12-17 12:39:37 +0800 CST
  • Martin Hope
    Jonas Como posso cronometrar consultas SQL usando psql? 2011-06-04 02:22:54 +0800 CST
  • Martin Hope
    Jonas Como inserir valores em uma tabela de uma consulta de seleção no PostgreSQL? 2011-05-28 00:33:05 +0800 CST
  • Martin Hope
    Jonas Como faço para listar todos os bancos de dados e tabelas usando o psql? 2011-02-18 00:45:49 +0800 CST

Hot tag

sql-server mysql postgresql sql-server-2014 sql-server-2016 oracle sql-server-2008 database-design query-performance sql-server-2017

Explore

  • Início
  • Perguntas
    • Recentes
    • Highest score
  • tag
  • help

Footer

AskOverflow.Dev

About Us

  • About Us
  • Contact Us

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve