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.mysql.com.tw/tech-resources/articles/wizard/print_version.html
ADOdb (PHP) 可以使用 PivotTableSQL() 生成透視表。
對於 Perl,請檢視 DBIx-SQLCrosstab。