índice
Um, o problema
Existe uma mesa como esta:
encontro | montante |
---|---|
31/12/2015 | 3000 |
22/01/2016 | 3100 |
23/01/2016 | 3100 |
24/01/2016 | 3100 |
25/01/2016 | 3100 |
26/01/2016 | 3100 |
27/01/2016 | 3100 |
28/01/2016 | 3100 |
29/01/2016 | 3100 |
30/01/2016 | 3100 |
31/01/2016 | 3300 |
01/02/2016 | 3400 |
02/02/2016 | 3500 |
Deseja obter resultados como os seguintes:
ano | mês | diferença |
---|---|---|
2016 | 1 | 300 |
2016 | 2 | 200 |
Escreva instruções SQL.
Pode-se adivinhar a partir do resultado que se trata de encontrar a diferença entre o valor acumulado de cada mês e do mês anterior, onde o valor do montante já é o valor acumulado, por isso precisa ser calculado novamente.
É muito simples à primeira vista, não é apenas agrupar estatísticas por ano e mês?
Se você pensar bem, não é tão fácil quanto você pensa. O mais importante é calcular a diferença entre as linhas. É muito fácil calcular a diferença entre as colunas no MySQL. A dificuldade está em calcular a diferença entre as linhas , o que requer um pequeno truque. , Converta valores de coluna em valores de linha por meio de variáveis e subconsultas MySQL.
Nota: Para negócios de alta concorrência, geralmente não colocamos tais cálculos no MySQL, tentamos processá-los na camada de aplicativo ou usamos estatísticas diretamente, porque proteger o banco de dados em negócios de alta concorrência é nossa responsabilidade importante.
Claro, se forem apenas alguns relatórios offline ou serviços estatísticos, é claro que não há problema, porque relatórios offline e outros serviços podem ser usados, então as dicas a seguir ainda podem ser entendidas.
Dois, importar dados
Primeiro crie a tabela:
CREATE TABLE `stat_year` (
`stat_date` date NULL DEFAULT NULL,
`amount` int UNSIGNED NULL DEFAULT NULL
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
Carregue os dados, ignorando o cabeçalho da primeira linha:
load data local infile 'data.txt' into table amount fields terminated by '\t' ignore 1 lines;
Três, use variáveis para salvar o valor anterior
Vamos primeiro entender como o MySQL usa variáveis em instruções SQL.
SELECT
tmp.stat_date,
tmp.current_amount,
tmp.pre,
( tmp.current_amount - tmp.pre ) AS diff
FROM
(
SELECT
stat_date,
amount AS current_amount,
@pre_amount AS pre,
@pre_amount := sp.amount
FROM
stat_pay sp,
( SELECT @pre_amount := 0 ) AS pre_temp
) AS tmp;
Em primeiro lugar, as variáveis do usuário no MySQL começam com @, e as variáveis do sistema começam com @@. A atribuição usa: =
Então, em sql
( SELECT @pre_amount := 0 ) AS pre_temp
É equivalente a definir uma variável de usuário @pre_amount e inicializar seu valor para 0.
A parte da subconsulta da primeira instrução from é equivalente a não selecionar uma linha. Primeiro, o valor de acessar @pre_amount é usado como o valor anterior e um alias pre é dado, e então o valor da linha atual é atribuído a @pre_amount.
Agora a consulta externa é muito mais fácil de entender, que consiste em verificar o valor da linha atual, o valor da linha anterior da linha atual e a diferença entre o valor da linha atual e o valor da linha anterior.
Vamos dar uma olhada no resultado de explicação da instrução SQL acima:
A descrição de saída de explain:
- O id é a identificação de cada select, quanto maior o id, maior a prioridade, mais ele será executado primeiro, e aqueles com o mesmo id serão executados de cima para baixo.
- select_type: PRIMARY representa a última seleção executada; DERIVED representa a subconsulta na instrução from
- tabela significa a tabela usada, significa a tabela derivada obtida usando id 2
Agora, vamos olhar para a saída de explain novamente, é muito mais claro:
Primeiro encontre o maior id, o maior id 3, execute primeiro, podemos ver que select_type é DERIVED, o que significa que irá gerar uma tabela derivada, na verdade, é equivalente a definir uma variável @pre_amount em uma tabela, desta tabela O alias é pre_temp, que é a subconsulta na segunda instrução from.
Existem dois com um id de 2, e ambos select_type são DERIVADOS, porque esses dois são subconsultas na primeira instrução from.
De cima para baixo, vemos que a tabela na linha 2 é, indicando que ela usa a tabela derivada gerada pela consulta com id 3, que é a tabela pre_temp. O tipo é sistema, o que significa que esta tabela possui apenas 1 linha, que também pode ser vista nas linhas.
A terceira linha da tabela é sp, o que significa que a tabela real de sp é usada diretamente e sp é o apelido de stat_pay.
Finalmente, select_type com id 1 é PRIMARY, o que significa que esta é a consulta externa executada por último, e table significa que a tabela usada é uma tabela derivada obtida de uma consulta com id 2.
Quarto, a solução final
Como queremos agrupar por ano e mês, e temos apenas data, podemos calcular o valor de ano e mês por meio de substring ou date_format.
SELECT substring(stat_date,1,4) AS stat_year,substring(stat_date,6,2) AS mon FROM stat_pay;
SELECT date_format(stat_date,'%Y') AS stat_year,DATE_FORMAT(stat_date,'%m') AS mon FROM stat_pay;
Vamos dar uma olhada em nosso SQL final:
SELECT
tmp.stat_year,
tmp.mon,
tmp.current_amount,
tmp.pre,
( tmp.current_amount - tmp.pre ) AS diff
FROM
(
SELECT
total_tmp.stat_year,
total_tmp.mon,
total_tmp.total_amount AS current_amount,
@pre_amount AS pre,
@pre_amount := total_tmp.total_amount
FROM
(
SELECT
substring( stat_date, 1, 4 ) AS stat_year,
substring( stat_date, 6, 2 ) AS mon,
max( amount ) AS total_amount
FROM
stat_pay
GROUP BY
stat_year,
mon
) AS total_tmp,
( SELECT @pre_amount := 0 ) AS pre_temp
) AS tmp;
Se você é um perfeccionista e deseja exatamente o mesmo resultado e não deseja o prefixo 0 no ano e no mês, pode converter a string em um número inteiro de uma das seguintes maneiras:
substring( stat_date, 1, 4 ) + 0 AS stat_year
convert(substring( stat_date, 6, 2 ),unsigned integer) as stat_year
cast(substring( stat_date, 6, 2 ) as unsigned integer) as stat_year
Por fim, filtre a primeira linha por meio da instrução de limite para obter o resultado final:
V. Resumo
Podemos criar uma tabela derivada para armazenar uma variável temporária usando select na instrução from e, em seguida, manipular essa variável na instrução select.
Por analogia, podemos também armazenar várias variáveis na tabela temporária, não apenas cálculos entre a mesma coluna, mas também cálculos em colunas diferentes.