PostgreSQL 13.2 (Debian 13.2-1.pgdg100+1) on x86_64-pc-linux-gnu,
compiled by gcc (Debian 8.3.0-6) 8.3.0, 64-bit
Eu tenho uma consulta que fornece a contagem de 2 coisas em 2 períodos de tempo:
- período desta semana, início da semana (domingo/segunda-feira meia-noite) -> hoje
- período de tempo da semana passada, início da semana passada (meia-noite de domingo/segunda-feira) -> hoje mas semana passada
O que eu gostaria é de também realizar um cálculo nos dois ints de contagem de "carros" que são retornados e fornecer isso com o resultado da consulta.
É claro que posso fazer isso no aplicativo de consumo, mas adoraria poder fazer isso no meu SQL.
SQL de trabalho
Este é o SQL base e é o que tenho atualmente e trabalhando :
WITH last_week AS (
SELECT COUNT(car_name) as car_count_last_week
FROM car_store
WHERE car_name = 'awesome'
AND car_time >= date_trunc('week', CURRENT_DATE - INTERVAL '1 week')
AND car_time <= CURRENT_DATE - INTERVAL '6 days'
), current_week AS (
SELECT COUNT(car_name) AS car_count_current_week
FROM car_store
WHERE car_name = 'awesome'
AND car_time >= date_trunc('week', CURRENT_DATE)
AND car_time <= CURRENT_DATE
)
SELECT car_name,
date_trunc('week', CURRENT_DATE - INTERVAL '1 week') AS start_of_last_week,
CURRENT_DATE - INTERVAL '6 days' AS today_but_last_week,
date_trunc('week', CURRENT_DATE) AS start_of_current_week,
CURRENT_DATE AS today,
car_count_last_week,
car_count_current_week
FROM car_store
CROSS JOIN last_week, current_week
WHERE car_name = 'awesome'
GROUP BY car_name, car_count_last_week, car_count_current_week
ORDER BY car_name;
Configurar tabela de banco de dados
CREATE TABLE IF NOT EXISTS car_store (
car_id INT GENERATED ALWAYS AS IDENTITY,
car_time TIMESTAMP NOT NULL,
car_name VARCHAR(255) NOT NULL,
PRIMARY KEY(car_id)
)
INSERT INTO car_store(car_name, car_time) VALUES ('awesome', '2021-05-03 15:28:00.116594');
INSERT INTO car_store(car_name, car_time) VALUES ('awesome', '2021-05-11 16:13:07.217903');
INSERT INTO car_store(car_name, car_time) VALUES ('awesome', '2021-05-01 18:03:27.217903');
INSERT INTO car_store(car_name, car_time) VALUES ('awesome', '2021-05-14 18:03:27.217903');
INSERT INTO car_store(car_name, car_time) VALUES ('awesome', '2021-05-12 18:03:27.217903');
INSERT INTO car_store(car_name, car_time) VALUES ('awesome', '2021-05-13 18:03:27.217903');
Coisa que causa erro
Eu gostaria de adicionar o seguinte código - para calcular difference_between_weeks
:
ROUND(car_count_current_week/(car_count_last_week/100) - 100)
AS difference_between_weeks
Eu tentei calcular nas colunas, tentei adicionar outra subconsulta. Mas eu não pareço ser capaz de calcular o difference_between_weeks
como ele me diz "não existe" ouERROR: division by zero
SQL completo que erros
Um exemplo do SQL com o código adicionado que apresenta erros :
WITH last_week AS (
SELECT COUNT(car_time) as car_count_last_week
FROM car_store
WHERE car_name = 'awesome'
AND car_time >= date_trunc('week', CURRENT_DATE - INTERVAL '1 week')
AND car_time <= CURRENT_DATE - INTERVAL '6 days'
), current_week AS (
SELECT COUNT(car_time) AS car_count_current_week
FROM car_store
WHERE car_name = 'awesome'
AND car_time >= date_trunc('week', CURRENT_DATE)
AND car_time <= CURRENT_DATE
)
SELECT car_name,
date_trunc('week', CURRENT_DATE - INTERVAL '1 week') AS start_of_last_week,
CURRENT_DATE - INTERVAL '6 days' AS today_but_last_week,
date_trunc('week', CURRENT_DATE) AS start_of_current_week,
CURRENT_DATE AS today,
car_count_last_week,
car_count_current_week,
ROUND(car_count_current_week/(car_count_last_week/100)) - 100 AS difference_between_weeks
FROM car_store
CROSS JOIN last_week, current_week
WHERE car_name = 'awesome'
GROUP BY car_name, car_count_last_week, car_count_current_week
ORDER BY car_name;
Erro retornado
Estou tendo o erro a seguir:
ERROR: division by zero
SQL state: 22012
Sinto que estou perto, mas também sinto que isso pode não ser possível? Quaisquer indicações para o que estou perdendo seriam muito apreciadas.
Seu erro foi que o Postgres interpretou a coluna como Integer e realiza uma divisão euclidiana , que leva ao zero encontrado.
Alterar o car_count_last_week para um decimal, fornecerá a divisão de números racionais
corrige o problema
db<>fique aqui
Não vejo por que você precisa de um
CROSS JOIN
neste caso! Eles são assassinos de desempenho!Você pode simplificar bastante esse problema fazendo o seguinte (e torná-lo mais geral na barganha!) - todo o código abaixo está disponível no violino aqui :
Observe o uso dos
GENERATED
campos (também conhecidos como "calculados" ou "virtuais"). O PG 12 apresentou isso e, como você está com 13 anos, somos de ouro!A implementação do PG tem apenas o
STORED
tipo de armazenamento no momento, mas quando oVIRTUAL
tipo aparecer, provavelmente seria melhor neste caso - embora os campos sejam meros 6 bytes (4 paraDATE
e 2 paraSMALLINT
).Eu também adicionei dados - porque 6 registros não dão uma boa base para qualquer teste!
Amostra:
O que eu fiz então foi:
Resultado:
Observe o uso da função LAG() do PostgreSQL WINDOW - elas são muito poderosas e vale a pena conhecê-las - elas retribuirão o tempo e o esforço gastos em aprendê-las muitas vezes... LAG() é perfeito para o seu caso de uso onde você deseja para comparar as vendas da semana passada com as desta semana!
Em seguida, adicionei cálculos para as porcentagens:
Resultado (não formatado, exceto as duas últimas (novas) colunas):
Então, temos nossa diferença em números absolutos e a variação percentual da semana anterior
Eu também adicionei isso:
É outro método para arrumar seu SQL - você não precisa colocar essa
WINDOW
definição complicada em todo lugar - apenas um pouco de açúcar, mas é bom ter!Eu também cuidei do problema que o @nbk apontou com a divisão INTEGER - sim, pode enganar as pessoas, mas apenas aprenda sobre o problema e siga em frente!
Eu usei o
REAL
tipo em vez deDECIMAL
- daqui , são apenas 4 bytes e precisos de 6 casas decimais - dificilmente acho que mais do que isso será necessário para este caso de uso. Além disso, a partir dessa página:Você pode se livrar dos
CASE WHEN LAG(wk_cnt) OVER w IS NULL
bits complicados colocando o lote em uma subconsulta da seguinte maneira:Você pode nem precisar disso - se você sempre vender pelo menos um carro por semana - caso contrário, você pode
JOIN
com uma tabela de calendário, como mostrado aqui .Performance with the sub-query doesn't appear to suffer too much (see fiddle), although I would urge you to test various indexes with your own data and h/ware setup - although, with car sales, the number of records isn't likely to be huge.