MySQL/透视表
外观
< MySQL
| 一位 Wikibookian 建议将此书或章节合并到SQL中。 请在讨论页面上讨论是否应该进行此合并。 |
"透视表"或"交叉表报表"
(注意:此页面需要维基化)
SQL 特征函数:无需使用 "if"、"case" 或 "GROUP_CONCAT" 来实现。是的,这些函数在某些情况下有用...“if”语句有时在组合使用时会导致问题。
简单秘诀,这也是它们几乎在所有数据库中都能正常工作的原因,如下所示:
- sign (x) 分别针对值 x < 0、x = 0、x > 0 返回 -1、0、+1
- abs( sign( x) ) 如果 x = 0 则返回 0,否则,如果 x > 0 或 x < 0 则返回 1
- 1-abs( sign( x) ) 上述内容的补充,因为此函数仅在 x = 0 时返回 1
Quick example: sign(-1) = -1, abs( sign(-1) ) = 1, 1-abs( sign(-1) ) = 0
完整示例数据
CREATE TABLE exams (
pkey int(11) NOT NULL auto_increment,
name varchar(15),
exam int,
score int,
PRIMARY KEY (pkey)
);
insert into exams (name,exam,score) values ('Bob',1,75);
insert into exams (name,exam,score) values ('Bob',2,77);
insert into exams (name,exam,score) values ('Bob',3,78);
insert into exams (name,exam,score) values ('Bob',4,80);
insert into exams (name,exam,score) values ('Sue',1,90);
insert into exams (name,exam,score) values ('Sue',2,97);
insert into exams (name,exam,score) values ('Sue',3,98);
insert into exams (name,exam,score) values ('Sue',4,99);
mysql> select * from exams;
+------+------+------+-------+
| pkey | name | exam | score |
+------+------+------+-------+
| 1 | Bob | 1 | 75 |
| 2 | Bob | 2 | 77 |
| 3 | Bob | 3 | 78 |
| 4 | Bob | 4 | 80 |
| 5 | Sue | 1 | 90 |
| 6 | Sue | 2 | 97 |
| 7 | Sue | 3 | 98 |
| 8 | Sue | 4 | 99 |
+------+------+------+-------+
8 rows in set (0.00 sec)
mysql> select name,
sum(score*(1-abs(sign(exam-1)))) as exam1,
sum(score*(1-abs(sign(exam-2)))) as exam2,
sum(score*(1-abs(sign(exam-3)))) as exam3,
sum(score*(1-abs(sign(exam-4)))) as exam4
from exams group by name;
+------+-------+-------+-------+-------+
| name | exam1 | exam2 | exam3 | exam4 |
+------+-------+-------+-------+-------+
| Bob | 75 | 77 | 78 | 80 |
| Sue | 90 | 97 | 98 | 99 |
+------+-------+-------+-------+-------+
2 rows in set (0.00 sec)
注意,以上透视表是用一个 select 语句创建的。
为了使技巧更清晰,让我们分解第二个示例
mysql> select name, score, exam, exam-2, sign(exam-2), abs(sign(exam-2)), 1-abs(sign(exam-2)),
score*(1-abs(sign(exam-2))) as exam2 from exams;
+------+-------+------+--------+--------------+-------------------+---------------------+-------+
| name | score | exam | exam-2 | sign(exam-2) | abs(sign(exam-2)) | 1-abs(sign(exam-2)) | exam2 |
+------+-------+------+--------+--------------+-------------------+---------------------+-------+
| Bob | 75 | 1 | -1 | -1 | 1 | 0 | 0 |
| Bob | 77 | 2 | 0 | 0 | 0 | 1 | 77 |
| Bob | 78 | 3 | 1 | 1 | 1 | 0 | 0 |
| Bob | 80 | 4 | 2 | 1 | 1 | 0 | 0 |
| Sue | 90 | 1 | -1 | -1 | 1 | 0 | 0 |
| Sue | 97 | 2 | 0 | 0 | 0 | 1 | 97 |
| Sue | 98 | 3 | 1 | 1 | 1 | 0 | 0 |
| Sue | 99 | 4 | 2 | 1 | 1 | 0 | 0 |
+------+-------+------+--------+--------------+-------------------+---------------------+-------+
8 rows in set (0.00 sec)
您可能认为 IF 会很干净,但要注意!看看以下结果(不正确!!)
mysql> select name, if(exam=1,score,null) as exam1, if(exam=2,score,null) as exam2, if(exam=3,score,null) as exam3, if(exam=4,score,null) as exam4 from exams group by name; +------+-------+-------+-------+-------+ | name | exam1 | exam2 | exam3 | exam4 | +------+-------+-------+-------+-------+ | Bob | 75 | NULL | NULL | NULL | | Sue | 90 | NULL | NULL | NULL | +------+-------+-------+-------+-------+ 2 rows in set (0.00 sec)
注意:以下方法有效 - 毕竟所有的数学运算都是必要的吗?
mysql> SELECT name,
SUM(IF(exam=1,score,NULL)) AS exam1,
SUM(IF(exam=2,score,NULL)) AS exam2,
SUM(IF(exam=3,score,NULL)) AS exam3,
SUM(IF(exam=4,score,0)) AS exam4
FROM exams GROUP BY name;
+------+-------+-------+-------+-------+
| name | exam1 | exam2 | exam3 | exam4 |
+------+-------+-------+-------+-------+
| Bob | 75 | 77 | 78 | 80 |
| Sue | 90 | 97 | 98 | 99 |
+------+-------+-------+-------+-------+
2 rows in set (0.00 sec)
mysql> select name,
sum(score*(1-abs(sign(exam-1)))) as exam1,
sum(score*(1-abs(sign(exam-2)))) as exam2,
sum(score*(1-abs(sign(exam-3)))) as exam3,
sum(score*(1-abs(sign(exam-4)))) as exam4,
sum(score*(1-abs(sign(exam- 2)))) - sum(score*(1-abs(sign(exam- 1)))) as delta_1_2,
sum(score*(1-abs(sign(exam- 3)))) - sum(score*(1-abs(sign(exam- 2)))) as delta_2_3,
sum(score*(1-abs(sign(exam- 4)))) - sum(score*(1-abs(sign(exam- 3)))) as delta_3_4
from exams group by name;
+------+-------+-------+-------+-------+-----------+-----------+-----------+
| name | exam1 | exam2 | exam3 | exam4 | delta_1_2 | delta_2_3 | delta_3_4 |
+------+-------+-------+-------+-------+-----------+-----------+-----------+
| Bob | 75 | 77 | 78 | 80 | 2 | 1 | 2 |
| Sue | 90 | 97 | 98 | 99 | 7 | 1 | 1 |
+------+-------+-------+-------+-------+-----------+-----------+-----------+
2 rows in set (0.00 sec)
上面的 delta_1_2 显示了第一次和第二次考试之间的差异,数字为正,因为 Bob 和 Sue 的成绩在每次考试中都有所提高。在这里计算增量表明可以比较两行,而不是列,而列可以用标准 SQL 语句轻松完成,但在原始表中则是行。
mysql>select name, sum(score*(1-abs(sign(exam-1)))) as exam1, sum(score*(1-abs(sign(exam-2)))) as exam2, sum(score*(1-abs(sign(exam-3)))) as exam3, sum(score*(1-abs(sign(exam-4)))) as exam4, sum(score*(1-abs(sign(exam- 2)))) - sum(score*(1-abs(sign(exam- 1)))) as delta_1_2, sum(score*(1-abs(sign(exam- 3)))) - sum(score*(1-abs(sign(exam- 2)))) as delta_2_3, sum(score*(1-abs(sign(exam- 4)))) - sum(score*(1-abs(sign(exam- 3)))) as delta_3_4, sum(score*(1-abs(sign(exam- 2)))) - sum(score*(1-abs(sign(exam- 1)))) + sum(score*(1-abs(sign(exam- 3)))) - sum(score*(1-abs(sign(exam- 2)))) + sum(score*(1-abs(sign(exam- 4)))) - sum(score*(1-abs(sign(exam- 3)))) as TotalIncPoints from exams group by name; +------+-------+-------+-------+-------+-----------+-----------+-----------+----------------+ | name | exam1 | exam2 | exam3 | exam4 | delta_1_2 | delta_2_3 | delta_3_4 | TotalIncPoints | +------+-------+-------+-------+-------+-----------+-----------+-----------+----------------+ | Bob | 75 | 77 | 78 | 80 | 2 | 1 | 2 | 5 | | Sue | 90 | 97 | 98 | 99 | 7 | 1 | 1 | 9 | +------+-------+-------+-------+-------+-----------+-----------+-----------+----------------+ 2 rows in set (0.00 sec)
TotalIncPoints 显示增量的总和。
select name, sum(score*(1-abs(sign(exam-1)))) as exam1, sum(score*(1-abs(sign(exam-2)))) as exam2, sum(score*(1-abs(sign(exam-3)))) as exam3, sum(score*(1-abs(sign(exam-4)))) as exam4, sum(score*(1-abs(sign(exam- 2)))) - sum(score*(1-abs(sign(exam- 1)))) as delta_1_2, sum(score*(1-abs(sign(exam- 3)))) - sum(score*(1-abs(sign(exam- 2)))) as delta_2_3, sum(score*(1-abs(sign(exam- 4)))) - sum(score*(1-abs(sign(exam- 3)))) as delta_3_4, sum(score*(1-abs(sign(exam- 2)))) - sum(score*(1-abs(sign(exam- 1)))) + sum(score*(1-abs(sign(exam- 3)))) - sum(score*(1-abs(sign(exam- 2)))) + sum(score*(1-abs(sign(exam- 4)))) - sum(score*(1-abs(sign(exam- 3)))) as TotalIncPoints, (sum(score*(1-abs(sign(exam-1)))) + sum(score*(1-abs(sign(exam-2)))) + sum(score*(1-abs(sign(exam-3)))) + sum(score*(1-abs(sign(exam-4)))))/4 as AVG from exams group by name; +------+-------+-------+-------+-------+-----------+-----------+-----------+----------------+-------+ | name | exam1 | exam2 | exam3 | exam4 | delta_1_2 | delta_2_3 | delta_3_4 | TotalIncPoints | AVG | +------+-------+-------+-------+-------+-----------+-----------+-----------+----------------+-------+ | Bob | 75 | 77 | 78 | 80 | 2 | 1 | 2 | 5 | 77.50 | | Sue | 90 | 97 | 98 | 99 | 7 | 1 | 1 | 9 | 96.00 | +------+-------+-------+-------+-------+-----------+-----------+-----------+----------------+-------+ 2 rows in set (0.00 sec)
可以将 Total Increasing Point TotalIncPoints 与 AVG 组合起来。实际上,可以将所有示例数据切片组合到一个 SQL 语句中,这为在页面上显示数据提供了更多选择
select name, sum(score*(1-abs(sign(exam-1)))) as exam1, sum(score*(1-abs(sign(exam-2)))) as exam2, sum(score*(1-abs(sign(exam-3)))) as exam3, sum(score*(1-abs(sign(exam-4)))) as exam4, (sum(score*(1-abs(sign(exam-1)))) + sum(score*(1-abs(sign(exam-2)))))/2 as AVG1_2, (sum(score*(1-abs(sign(exam-2)))) + sum(score*(1-abs(sign(exam-3)))))/2 as AVG2_3, (sum(score*(1-abs(sign(exam-3)))) + sum(score*(1-abs(sign(exam-4)))))/2 as AVG3_4, (sum(score*(1-abs(sign(exam-1)))) + sum(score*(1-abs(sign(exam-2)))) + sum(score*(1-abs(sign(exam-3)))) + sum(score*(1-abs(sign(exam-4)))))/4 as AVG from exams group by name; +------+-------+-------+-------+-------+--------+--------+--------+-------+ | name | exam1 | exam2 | exam3 | exam4 | AVG1_2 | AVG2_3 | AVG3_4 | AVG | +------+-------+-------+-------+-------+--------+--------+--------+-------+ | Bob | 75 | 77 | 78 | 80 | 76.00 | 77.50 | 79.00 | 77.50 | | Sue | 90 | 97 | 98 | 99 | 93.50 | 97.50 | 98.50 | 96.00 | +------+-------+-------+-------+-------+--------+--------+--------+-------+ 2 rows in set (0.00 sec)
考试分数与移动平均值一起列出...同样,这一切都用一个 select 语句完成。
"交叉表"或反规范化数据以显示统计信息的优秀文章:https://dev.mysqlserver.cn/tech-resources/articles/wizard/print_version.html
ADOdb (PHP) 可以使用 PivotTableSQL() 生成透视表。
对于 Perl,请查看 DBIx-SQLCrosstab。