MySQL 查询总百分比变化
MySQL query to get total percentage change
如何在 MySQL 中添加百分比变化列(不是 percentage points)?
有一个 table 的百分比变化列:
+---------+
| percent |
+---------+
| -0.50 |
| 0.50 |
| 1.00 |
| -0.20 |
| 0.50 |
| -1.00 |
| -2.00 |
| 0.75 |
| 1.00 |
| 0.50 |
+---------+
如何编写一个查询来计算每行值的总百分比变化,以便计算出的行表达其百分比变化以及所有先前行的百分比变化?
预期结果:
+---------+---------------+---------------+
| percent | nominal_value | total_percent |
+---------+---------------+---------------+
| -0.50 | 0.50 | -0.50 |
| 0.50 | 0.75 | -0.25 |
| 1.00 | 1.50 | 0.50 |
| -0.20 | 1.20 | 0.20 |
| 0.50 | 1.80 | 0.80 |
| -1.00 | 0.00 | -1.00 |
| -2.00 | -2.00 | -3.00 |
| 0.75 | -0.50 | -1.50 |
| 1.00 | 0.00 | -1.00 |
| 0.50 | 0.50 | -0.50 |
+---------+---------------+---------------+
其中 nominal_value
是被 percent
更改的任意值,因此对于第一行,如果标称值为 1.0 (100%) 但被 -0.50
更改( -50%
) 结果是标称值 0.5
.
然后在第二行 percent
变化是 +0.50
(+50%
) 所以标称值增加了一半 0.5 => 0.75
但也可以说它只是从其原始值降低 -0.25
(-25%
),因为从 1.0
到 0.75
是 -0.25
(-25%
) 1.0
。
这正是我在 total_percent
更改后的结果,nominal_value
只是为了说明目的,不需要。
我正在使用 MySQL 8,因此查询可能会使用 window 函数/范围等
这里是要复制的测试 table:
CREATE TABLE IF NOT EXISTS test
(
percent DECIMAL(5,2) NOT NULL
)
ENGINE = InnoDB
;
INSERT INTO test (percent) VALUES
(-0.50)
,(0.50)
,(1.00)
,(-0.20)
,(0.50)
,(-1.0)
,(-2.0)
,(0.75)
,(1.0)
,(0.50)
;
DROP TABLE IF EXISTS test;
CREATE TABLE test
( id SERIAL PRIMARY KEY
, percent DECIMAL(5,2) NOT NULL
);
INSERT INTO test (percent) VALUES
(-0.5)
,(0.5)
,(1)
,(-0.2)
,(0.5)
,(-1)
;
SELECT ROUND(@i:=(@i+(@i*percent)),2)n
FROM test
, (SELECT @i:=1) vars
ORDER
BY id;
+------+
| n |
+------+
| 0.50 |
| 0.75 |
| 1.50 |
| 1.20 |
| 1.80 |
| 0.00 |
+------+
6 rows in set (0.00 sec)
mysql>
这个查询会给你想要的结果。它使用两个 CTE,第一个简单地向数据添加行号,第二个递归 CTE 从当前 percent
和前面的 nominal_value
生成 nominal_value
值(其中前面由行号定义)。最后 total_percent
是从 nominal_value
.
计算出来的
备注
为了使这个(以及任何类似的)查询可靠地工作,必须有一个 PRIMARY KEY
可以让第一个 CTE 对其结果进行排序。为此,我在演示中添加了一个 AUTO_INCREMENT INT
列 id
。
WITH RECURSIVE cte AS (
SELECT percent, ROW_NUMBER() OVER () AS rn
FROM test
ORDER BY id),
cte2 AS (
SELECT 1 + percent AS nominal_value, rn
FROM cte
WHERE rn = 1
UNION ALL
SELECT CASE WHEN nominal_value = 0 THEN percent
ELSE nominal_value + percent * ABS(nominal_value)
END,
cte.rn
FROM cte
JOIN cte2 ON cte2.rn = cte.rn - 1
)
SELECT percent, nominal_value, (nominal_value - 1) AS total_percent
FROM cte2
JOIN cte ON cte.rn = cte2.rn
输出:
percent nominal_value total_percent
-0.5 0.5 -0.5
0.5 0.75 -0.25
1 1.5 0.5
-0.2 1.2 0.2
0.5 1.8 0.8
-1 0 -1
-2 -2 -3
0.75 -0.5 -1.5
1 0 -1
0.5 0.5 -0.5
由于 OP 编辑了 post 并在接受的答案后添加了额外的数据行和想要的结果,因此这是接受的答案的一个小变化。被 post 编辑并接受:
Query:
DROP TABLE IF EXISTS test;
CREATE TABLE test
(
id SERIAL PRIMARY KEY
, percent DECIMAL(5,2) NOT NULL
);
INSERT INTO test (percent) VALUES
(-0.50)
,(0.50)
,(1.00)
,(-0.20)
,(0.50)
,(-1.0)
,(-2.0)
,(0.75)
,(1.0)
,(0.50)
;
SELECT
percent,
CASE @i
WHEN 0 THEN ROUND(@i:=(@i+(percent * 1)),2) -1
ELSE ROUND(@i:=(@i+(percent * ABS(@i))) ,2) -1
END total_percent
FROM
test
, (SELECT @i:=1) vars
ORDER
BY id;
Result:
+---------+---------------+
| percent | total_percent |
+---------+---------------+
| -0.50 | -0.50 |
| 0.50 | -0.25 |
| 1.00 | 0.50 |
| -0.20 | 0.20 |
| 0.50 | 0.80 |
| -1.00 | -1.00 |
| -2.00 | -3.00 |
| 0.75 | -1.50 |
| 1.00 | -1.00 |
| 0.50 | -0.50 |
+---------+---------------+
10 rows in set, 3 warnings (0.00 sec)
请注意,已接受的答案在达到零标称值后停止计算,然后无论百分比变化如何,标称值都相同 = 0。对于某些情况,这可能是正确的方法。对于其他人,如果您使用 MySQL 8.
,这里是通过零或 @Nick 答案继续计算的
另一种计算此数据的方法是使用存储过程。这种方法的优点是它不需要递归 CTE 或变量,但缺点是使用结果可能很棘手(例如在 JOIN
中)。此过程创建一个临时 table 来存储结果,然后再返回它们;如果需要进一步处理,可以保留 table 而不是在过程结束时被 DROP
ped。与其他答案一样,此方法要求数据具有 PRIMARY KEY
以保证一致的结果。
DELIMITER //
CREATE PROCEDURE total_percent()
BEGIN
DECLARE nominal_value DECIMAL(10,2) DEFAULT 1;
DECLARE this_percent DECIMAL(5,2);
DECLARE done INT DEFAULT 0;
DECLARE p_cursor CURSOR FOR SELECT percent FROM test ORDER BY id;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
CREATE TEMPORARY TABLE p (percent DECIMAL(5, 2),
nominal_value DECIMAL(10, 2),
total_percent DECIMAL(10, 2));
OPEN p_cursor;
compute: LOOP
FETCH p_cursor INTO this_percent;
IF done THEN
LEAVE compute;
END IF;
IF nominal_value = 0 THEN
SET nominal_value = this_percent;
ELSE
SET nominal_value = nominal_value + this_percent * ABS(nominal_value);
END IF;
INSERT INTO p VALUES (this_percent, nominal_value, nominal_value -1);
END loop;
SELECT * FROM p;
DROP TABLE p;
END //
DELIMITER ;
CALL total_percent();
输出:
percent nominal_value total_percent
-0.5 0.5 -0.5
0.5 0.75 -0.25
1 1.5 0.5
-0.2 1.2 0.2
0.5 1.8 0.8
-1 0 -1
-2 -2 -3
0.75 -0.5 -1.5
1 0 -1
0.5 0.5 -0.5
如何在 MySQL 中添加百分比变化列(不是 percentage points)?
有一个 table 的百分比变化列:
+---------+
| percent |
+---------+
| -0.50 |
| 0.50 |
| 1.00 |
| -0.20 |
| 0.50 |
| -1.00 |
| -2.00 |
| 0.75 |
| 1.00 |
| 0.50 |
+---------+
如何编写一个查询来计算每行值的总百分比变化,以便计算出的行表达其百分比变化以及所有先前行的百分比变化?
预期结果:
+---------+---------------+---------------+
| percent | nominal_value | total_percent |
+---------+---------------+---------------+
| -0.50 | 0.50 | -0.50 |
| 0.50 | 0.75 | -0.25 |
| 1.00 | 1.50 | 0.50 |
| -0.20 | 1.20 | 0.20 |
| 0.50 | 1.80 | 0.80 |
| -1.00 | 0.00 | -1.00 |
| -2.00 | -2.00 | -3.00 |
| 0.75 | -0.50 | -1.50 |
| 1.00 | 0.00 | -1.00 |
| 0.50 | 0.50 | -0.50 |
+---------+---------------+---------------+
其中 nominal_value
是被 percent
更改的任意值,因此对于第一行,如果标称值为 1.0 (100%) 但被 -0.50
更改( -50%
) 结果是标称值 0.5
.
然后在第二行 percent
变化是 +0.50
(+50%
) 所以标称值增加了一半 0.5 => 0.75
但也可以说它只是从其原始值降低 -0.25
(-25%
),因为从 1.0
到 0.75
是 -0.25
(-25%
) 1.0
。
这正是我在 total_percent
更改后的结果,nominal_value
只是为了说明目的,不需要。
我正在使用 MySQL 8,因此查询可能会使用 window 函数/范围等
这里是要复制的测试 table:
CREATE TABLE IF NOT EXISTS test
(
percent DECIMAL(5,2) NOT NULL
)
ENGINE = InnoDB
;
INSERT INTO test (percent) VALUES
(-0.50)
,(0.50)
,(1.00)
,(-0.20)
,(0.50)
,(-1.0)
,(-2.0)
,(0.75)
,(1.0)
,(0.50)
;
DROP TABLE IF EXISTS test;
CREATE TABLE test
( id SERIAL PRIMARY KEY
, percent DECIMAL(5,2) NOT NULL
);
INSERT INTO test (percent) VALUES
(-0.5)
,(0.5)
,(1)
,(-0.2)
,(0.5)
,(-1)
;
SELECT ROUND(@i:=(@i+(@i*percent)),2)n
FROM test
, (SELECT @i:=1) vars
ORDER
BY id;
+------+
| n |
+------+
| 0.50 |
| 0.75 |
| 1.50 |
| 1.20 |
| 1.80 |
| 0.00 |
+------+
6 rows in set (0.00 sec)
mysql>
这个查询会给你想要的结果。它使用两个 CTE,第一个简单地向数据添加行号,第二个递归 CTE 从当前 percent
和前面的 nominal_value
生成 nominal_value
值(其中前面由行号定义)。最后 total_percent
是从 nominal_value
.
备注
为了使这个(以及任何类似的)查询可靠地工作,必须有一个 PRIMARY KEY
可以让第一个 CTE 对其结果进行排序。为此,我在演示中添加了一个 AUTO_INCREMENT INT
列 id
。
WITH RECURSIVE cte AS (
SELECT percent, ROW_NUMBER() OVER () AS rn
FROM test
ORDER BY id),
cte2 AS (
SELECT 1 + percent AS nominal_value, rn
FROM cte
WHERE rn = 1
UNION ALL
SELECT CASE WHEN nominal_value = 0 THEN percent
ELSE nominal_value + percent * ABS(nominal_value)
END,
cte.rn
FROM cte
JOIN cte2 ON cte2.rn = cte.rn - 1
)
SELECT percent, nominal_value, (nominal_value - 1) AS total_percent
FROM cte2
JOIN cte ON cte.rn = cte2.rn
输出:
percent nominal_value total_percent
-0.5 0.5 -0.5
0.5 0.75 -0.25
1 1.5 0.5
-0.2 1.2 0.2
0.5 1.8 0.8
-1 0 -1
-2 -2 -3
0.75 -0.5 -1.5
1 0 -1
0.5 0.5 -0.5
由于 OP 编辑了 post 并在接受的答案后添加了额外的数据行和想要的结果,因此这是接受的答案的一个小变化。被 post 编辑并接受:
Query:
DROP TABLE IF EXISTS test;
CREATE TABLE test
(
id SERIAL PRIMARY KEY
, percent DECIMAL(5,2) NOT NULL
);
INSERT INTO test (percent) VALUES
(-0.50)
,(0.50)
,(1.00)
,(-0.20)
,(0.50)
,(-1.0)
,(-2.0)
,(0.75)
,(1.0)
,(0.50)
;
SELECT
percent,
CASE @i
WHEN 0 THEN ROUND(@i:=(@i+(percent * 1)),2) -1
ELSE ROUND(@i:=(@i+(percent * ABS(@i))) ,2) -1
END total_percent
FROM
test
, (SELECT @i:=1) vars
ORDER
BY id;
Result:
+---------+---------------+
| percent | total_percent |
+---------+---------------+
| -0.50 | -0.50 |
| 0.50 | -0.25 |
| 1.00 | 0.50 |
| -0.20 | 0.20 |
| 0.50 | 0.80 |
| -1.00 | -1.00 |
| -2.00 | -3.00 |
| 0.75 | -1.50 |
| 1.00 | -1.00 |
| 0.50 | -0.50 |
+---------+---------------+
10 rows in set, 3 warnings (0.00 sec)
请注意,已接受的答案在达到零标称值后停止计算,然后无论百分比变化如何,标称值都相同 = 0。对于某些情况,这可能是正确的方法。对于其他人,如果您使用 MySQL 8.
,这里是通过零或 @Nick 答案继续计算的另一种计算此数据的方法是使用存储过程。这种方法的优点是它不需要递归 CTE 或变量,但缺点是使用结果可能很棘手(例如在 JOIN
中)。此过程创建一个临时 table 来存储结果,然后再返回它们;如果需要进一步处理,可以保留 table 而不是在过程结束时被 DROP
ped。与其他答案一样,此方法要求数据具有 PRIMARY KEY
以保证一致的结果。
DELIMITER //
CREATE PROCEDURE total_percent()
BEGIN
DECLARE nominal_value DECIMAL(10,2) DEFAULT 1;
DECLARE this_percent DECIMAL(5,2);
DECLARE done INT DEFAULT 0;
DECLARE p_cursor CURSOR FOR SELECT percent FROM test ORDER BY id;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
CREATE TEMPORARY TABLE p (percent DECIMAL(5, 2),
nominal_value DECIMAL(10, 2),
total_percent DECIMAL(10, 2));
OPEN p_cursor;
compute: LOOP
FETCH p_cursor INTO this_percent;
IF done THEN
LEAVE compute;
END IF;
IF nominal_value = 0 THEN
SET nominal_value = this_percent;
ELSE
SET nominal_value = nominal_value + this_percent * ABS(nominal_value);
END IF;
INSERT INTO p VALUES (this_percent, nominal_value, nominal_value -1);
END loop;
SELECT * FROM p;
DROP TABLE p;
END //
DELIMITER ;
CALL total_percent();
输出:
percent nominal_value total_percent
-0.5 0.5 -0.5
0.5 0.75 -0.25
1 1.5 0.5
-0.2 1.2 0.2
0.5 1.8 0.8
-1 0 -1
-2 -2 -3
0.75 -0.5 -1.5
1 0 -1
0.5 0.5 -0.5