Agrupe os resultados da consulta por mês e ano no postgresql

157

Eu tenho a seguinte tabela de banco de dados em um servidor Postgres:

id      date          Product Sales
1245    01/04/2013    Toys    1000     
1245    01/04/2013    Toys    2000
1231    01/02/2013    Bicycle 50000
456461  01/01/2014    Bananas 4546

Gostaria de criar uma consulta que forneça SUMa Salescoluna e agrupe os resultados por mês e ano da seguinte maneira:

Apr    2013    3000     Toys
Feb    2013    50000    Bicycle
Jan    2014    4546     Bananas

Existe uma maneira simples de fazer isso?

Frechi
fonte

Respostas:

219
select to_char(date,'Mon') as mon,
       extract(year from date) as yyyy,
       sum("Sales") as "Sales"
from yourtable
group by 1,2

A pedido de Radu, explicarei essa consulta:

to_char(date,'Mon') as mon, : converte o atributo "date" no formato definido da forma abreviada de mês.

extract(year from date) as yyyy : A função "extrair" do Postgresql é usada para extrair o ano AAAA do atributo "data".

sum("Sales") as "Sales" : A função SUM () adiciona todos os valores "Vendas" e fornece um alias com distinção entre maiúsculas e minúsculas, mantendo a diferenciação entre maiúsculas e minúsculas usando aspas duplas.

group by 1,2: A função GROUP BY deve conter todas as colunas da lista SELECT que não fazem parte do agregado (ou seja, todas as colunas que não estão nas funções SUM / AVG / MIN / MAX etc). Isso informa à consulta que o SUM () deve ser aplicado a cada combinação exclusiva de colunas, que neste caso são as colunas de mês e ano. A parte "1,2" é uma abreviação em vez de usar os aliases da coluna, embora provavelmente seja melhor usar as expressões completas "to_char (...)" e "extract (...)" para facilitar a leitura.

bma
fonte
5
Não acho que dar uma resposta sem uma explicação seja uma ideia muito boa, principalmente para iniciantes. Você deveria ter explicado a lógica por trás da sua resposta, talvez pelo menos um pouco (embora possa parecer simples e direto para o resto de nós).
Radu Gheorghiu
1
@BurakArslan Os resultados foram parecidos com o que o OP pediu especificamente?
BMA
2
@rogerdpack, a saída date_truncnão é exatamente o que o consulente queria: select date_trunc('month', timestamp '2001-02-16 20:38:40')::date=>2001-02-01
pisaruk
2
Eu gosto da ideia de usar date_truncna group bycláusula.
pisaruk
1
Possível "campo deve estar no grupo por cláusula" questões ... É melhor usar OVER (PARTITION BY).
Zon
318

Não acredito que a resposta aceita tenha tantos votos positivos - é um método horrível.

Aqui está a maneira correta de fazer isso, com date_trunc :

   SELECT date_trunc('month', txn_date) AS txn_month, sum(amount) as monthly_sum
     FROM yourtable
 GROUP BY txn_month

É uma prática ruim, mas você pode ser perdoado se usar

 GROUP BY 1

em uma consulta muito simples.

Você também pode usar

 GROUP BY date_trunc('month', txn_date)

se você não quiser selecionar a data.

Burak Arslan
fonte
6
infelizmente a saída de date_truncnão é o que o autor da pergunta esperava: select date_trunc('month', timestamp '2001-02-16 20:38:40')=> 2001-02-01 00:00:00.
pisaruk
4
Eu concordo que este método é melhor. Não tenho certeza, mas acho que é mais eficiente também, pois há apenas um agrupamento em vez de dois. Se você precisar reformatar a data, poderá fazê-lo posteriormente, usando os métodos descritos em outras respostas:to_char(date_trunc('month', txn_date), 'YY-Mon')
Paweł Sokołowski
1
Sim, o número de votos para a resposta aceita é incompreensível. date_truncfoi criado para esse fim exato. não há nenhuma razão para criar duas colunas
allenwlee
2
Muito agradável! Essa é uma resposta superior, especialmente porque você pode fazer o pedido também. Voto a favor!
bobmarksie
1
Outro exemplo em que a resposta mais votada deve aparecer antes da resposta aceita
Brian Risk
33

to_char na verdade, permite que você retire o ano e o mês de uma só vez!

select to_char(date('2014-05-10'),'Mon-YY') as year_month; --'May-14'
select to_char(date('2014-05-10'),'YYYY-MM') as year_month; --'2014-05'

ou no caso do exemplo do usuário acima:

select to_char(date,'YY-Mon') as year_month
       sum("Sales") as "Sales"
from some_table
group by 1;
mgoldwasser
fonte
6
Eu não recomendaria isso se você tiver uma quantidade decente de dados em sua tabela. Isso tem um desempenho muito pior do que o date_truncmétodo ao executar o grupo por. Experimentação a uma DB eu tenho à mão, em uma tabela com 270k linhas, o método date_trunc é mais de duas vezes a velocidade do TO_CHAR
Chris Clark
@ChrisClark, se o desempenho for uma preocupação, concordo que pode fazer sentido usar date_trunc, mas em alguns casos é preferível ter uma string de data formatada e, se você estiver usando um data warehouse com desempenho, o cálculo adicional pode não ser um diferencial. . Por exemplo, se você estiver executando um relatório de análise rápida usando o desvio para o vermelho, e geralmente leva 3 segundos, uma consulta de 6 segundos provavelmente é boa (embora, se você estiver executando relatórios, o cálculo adicional possa diminuir a velocidade em uma porcentagem menor, porque existe uma sobrecarga computacional maior)
mgoldwasser
1
você ainda pode fazer isso - basta fazer a formatação como uma etapa separada, 'agrupando' o grupo por consulta. Por exemplo, SELECT to_char (d, 'AAAA-DD') FROM (SELECT date_trunc ('mês', d) AS "d" FROM tbl) AS foo. Melhor dos dois mundos!
26417 Chris Clark
1
Esta solução é simples e elegante. Eu gosto e no meu caso é rápido o suficiente. Obrigado por esta resposta!
guettli
5

Existe outra maneira de obter o resultado usando a função date_part () no postgres.

 SELECT date_part('month', txn_date) AS txn_month, date_part('year', txn_date) AS txn_year, sum(amount) as monthly_sum
     FROM yourtable
 GROUP BY date_part('month', txn_date)

obrigado

Nayan
fonte
1

A resposta da bma é ótima! Eu o usei com o ActiveRecords, aqui está se alguém precisar no Rails:

Model.find_by_sql(
  "SELECT TO_CHAR(created_at, 'Mon') AS month,
   EXTRACT(year from created_at) as year,
   SUM(desired_value) as desired_value
   FROM desired_table
   GROUP BY 1,2
   ORDER BY 1,2"
)
mekdigital
fonte
3
ou você pode fazer yourscopeorclass.group("extract(year from tablename.colname)")e você pode cadeia que junto 3 vezes para obter ano, mês, dia
nruth
1

Veja o exemplo E deste tutorial -> https://www.postgresqltutorial.com/postgresql-group-by/

Você precisa chamar a função no seu GROUP BY em vez de chamar o nome do atributo virtual que você criou ao selecionar. Eu estava fazendo o que todas as respostas acima recomendavam e estava recebendo um column 'year_month' does not existerro.

O que funcionou para mim foi:

SELECT 
    date_trunc('month', created_at), 'MM/YYYY' AS month
FROM 
    "orders"  
GROUP BY 
    date_trunc('month', created_at)
Lucas Kuhn
fonte
0

O Postgres possui alguns tipos de carimbos de data / hora:

registro de data e hora sem fuso horário - (Preferível para armazenar registros de data e hora UTC) Você encontra-o no armazenamento de banco de dados multinacional. O cliente, nesse caso, cuidará do deslocamento do fuso horário para cada país.

registro de data e hora com fuso horário - O deslocamento do fuso horário já está incluído no registro de data e hora.

Em alguns casos, seu banco de dados não usa o fuso horário, mas você ainda precisa agrupar registros em relação ao fuso horário local e ao horário de verão (por exemplo, https://www.timeanddate.com/time/zone/romania/bucharest )

Para adicionar fuso horário, você pode usar este exemplo e substituir o deslocamento do fuso horário pelo seu.

"your_date_column" at time zone '+03'

Para adicionar o deslocamento +1 no horário de verão específico para o horário de verão, você precisa verificar se o carimbo de data / hora se enquadra no horário de verão. Como esses intervalos variam de 1 a 2 dias, usarei uma aproximação que não afeta os registros do final do mês, portanto, neste caso, posso ignorar o intervalo exato de cada ano.

Se for necessário criar uma consulta mais precisa, você precisará adicionar condições para criar mais casos. Mas, grosso modo, isso funcionará bem na divisão de dados por mês em relação ao fuso horário e ao SummerTime quando você encontrar o carimbo de data e hora sem fuso horário no banco de dados:

SELECT 
    "id", "Product", "Sale",
    date_trunc('month', 
        CASE WHEN 
            Extract(month from t."date") > 03 AND
            Extract(day from t."date") > 26 AND
            Extract(hour from t."date") > 3 AND
            Extract(month from t."date") < 10 AND
            Extract(day from t."date") < 29 AND
            Extract(hour from t."date") < 4
        THEN 
            t."date" at time zone '+03' -- Romania TimeZone offset + DST
        ELSE
            t."date" at time zone '+02' -- Romania TimeZone offset 
        END) as "date"
FROM 
    public."Table" AS t
WHERE 1=1
    AND t."date" >= '01/07/2015 00:00:00'::TIMESTAMP WITHOUT TIME ZONE
    AND t."date" < '01/07/2017 00:00:00'::TIMESTAMP WITHOUT TIME ZONE
GROUP BY date_trunc('month', 
    CASE WHEN 
        Extract(month from t."date") > 03 AND
        Extract(day from t."date") > 26 AND
        Extract(hour from t."date") > 3 AND
        Extract(month from t."date") < 10 AND
        Extract(day from t."date") < 29 AND
        Extract(hour from t."date") < 4
    THEN 
        t."date" at time zone '+03' -- Romania TimeZone offset + DST
    ELSE
        t."date" at time zone '+02' -- Romania TimeZone offset 
    END)
profimedica
fonte