看完測試完下面試題,你的SQL水平一定會提高。
下面我們先看一下題設:
二維表 T(F1,F2,F3,F4,F5,F6,F7) 表示如下關系:
┌─────┬────┬─────┬─────┬─────┬─────┬─────┐
│ 學生ID │學生姓名│ 課程ID │ 課程名稱 │ 成績 │ 教師ID │ 教師姓名 │
├─────┼────┼─────┼─────┼─────┼─────┼─────┤
│ S3 │ 王五 │ K4 │ 政治 │ 53 │ T4 │ 趙老師 │
├─────┼────┼─────┼─────┼─────┼─────┼─────┤
│ S1 │ 張三 │ K1 │ 數學 │ 61 │ T1 │ 張老師 │
├─────┼────┼─────┼─────┼─────┼─────┼─────┤
│ S2 │ 李四 │ K3 │ 英語 │ 88 │ T3 │ 李老師 │
├─────┼────┼─────┼─────┼─────┼─────┼─────┤
│ S1 │ 張三 │ K4 │ 政治 │ 77 │ T4 │ 趙老師 │
├─────┼────┼─────┼─────┼─────┼─────┼─────┤
│ S2 │ 李四 │ K4 │ 政治 │ 67 │ T5 │ 周老師 │
├─────┼────┼─────┼─────┼─────┼─────┼─────┤
│ S3 │ 王五 │ K2 │ 語文 │ 90 │ T2 │ 王老師 │
├─────┼────┼─────┼─────┼─────┼─────┼─────┤
│ S3 │ 王五 │ K1 │ 數學 │ 55 │ T1 │ 張老師 │
├─────┼────┼─────┼─────┼─────┼─────┼─────┤
│ S1 │ 張三 │ K2 │ 語文 │ 81 │ T2 │ 王老師 │
├─────┼────┼─────┼─────┼─────┼─────┼─────┤
│ S4 │ 趙六 │ K2 │ 語文 │ 59 │ T1 │ 王老師 │
├─────┼────┼─────┼─────┼─────┼─────┼─────┤
│ S1 │ 張三 │ K3 │ 英語 │ 37 │ T3 │ 李老師 │
├─────┼────┼─────┼─────┼─────┼─────┼─────┤
│ S2 │ 李四 │ K1 │ 數學 │ 81 │ T1 │ 張老師 │
├─────┼────┼─────┼─────┼─────┼─────┼─────┤
│ .... │ │ │ │ │ │ │
├─────┼────┼─────┼─────┼─────┼─────┼─────┤
│ .... │ │ │ │ │ │ │
└─────┴────┴─────┴─────┴─────┴─────┴─────┘
為便於大家更好的理解,我們將 T 表起名為"成績表"
1.如果 T 表還有一字段 F 數據類型為自動增量整型(唯一,不會重復),
而且 T 表中含有除 F 字段外,請刪除其它字段完全相同的重復多余的髒記錄數據:
本問題就是一個清理"邏輯重復"記錄的問題,當然,這種情況完全可以利用主鍵約束來
杜絕!然而,現實情況經常是原始數據在"洗滌"後,方可安全使用,而且邏輯主鍵過早的
約束,將會給采集原始數據帶來不便,例如:從刷卡機上讀取考勤記錄。到了應用數據
的時候,髒數據就該被掃地出門了! 之所以題中要保留一個自動標識列,是因為它的確
是下面答案所必須的前提:
DELETE L
FROM "成績表" L
JOIN "成績表" R
ON L."學生ID" = R."學生ID" AND L."課程ID" = R."課程ID" AND L.F > R.F
這是思路最精巧且最直接有效的方法之一。用不等自聯接,正好可以將同一組重復數
據中 F 字段值最小的那一條留下,並選出其它的刪掉,如果只有一條,自然也不會被選
中了。這裡還要強調一下,大家一定要分清楚被操作的基本表也就是 DELETE 關鍵字
後的表和過濾條件所使用的由基本表連接而成的二維表數據集,也就是 FROM 子句的
全部。在自連接的 FROM 子句至少要取一個別名來引用基本表。別名的使用在編寫大
量類似結構的 SQL 時非常方便,而且利於統一程序構造動態 SQL。如有必要加強條件,
還可繼續使用 WHERE 子句。如果上面的例子還不夠直觀,下面模仿一個不等自聯接,
有一組數 (1,2,3),作一個不等自聯接,令左子集大於右子集,是:
2 1
3 1
3 2
如果現在選出左子集,就是 2 和 3 了。1 在右邊沒有比它更小的數據可以與之匹配,
因此被過濾了。如果數據大量重復,效率會差強人意,幸虧不是 SELECT ,而是 DELETE
無需返回結果集,影響自然小多了。
DELETE T
FROM 成績表 T
WHERE F NOT IN (SELECT MIN(F)
FROM 成績表 I
GROUP BY I.學生ID,I.課程ID
HAVING COUNT(*)>1
)
AND F NOT IN (SELECT MIN(F)
FROM 成績表 I
GROUP BY I.學生ID, I.課程ID
HAVING COUNT(*)=1
)
這種方法思路很簡單,就像翻譯自然語言,很精確地描述了符合條件記錄的特性,甚至
第二個條件的確多余。至少應該用一個 >= 號合並這兩個條件或只保留任意一個條件,
提高效率。
DELETE T
FROM 成績表 T
WHERE F > (SELECT MIN(F)
FROM 成績表 AS I
WHERE I.學生ID = T.學生ID
AND I.課程ID = T.課程ID
GROUP BY I.學生ID, I.課程ID
)
這種方法,基本上是方法一的相關子查詢版本,了解笛卡爾積的讀者能會好理解些,而
且用到了統計函數,因此效率不是太高。細心的讀者會發現子查詢裡的 GROUP BY 子
句沒有必要,去掉它應該會提高一些效率的。
關於 DELETE 語句的調試,有經驗的程序員都會先用無害的 SELECT 暫時代替危險的
DELETE。例如:
SELECT L.*
--DELECT L 暫時注釋掉
FROM "成績表" L
JOIN "成績表" R
ON L."學生ID" = R."學生ID" AND L."課程ID" = R."課程ID" AND L.F>R.F
這樣,極大地減小了在線數據被無意破壞的可能性,當然數據提前備份也很重要。同理
UPDATE 和 INSERT 寫操作也應照此行事。從數據庫原理的關系運算的角度來看 INSERT、
UPDATE 和 DELETE 這些寫操作都屬於典型的"選擇(Selection)"運算,UPDATE 和 INSERT
而且還是"投影(Projection)"運算,它們都是這些關系運算的"寫"應用的表現形式。
其實,查詢的目的也本來無非就是浏覽、刪除、更
新或插入。通常寫操作也比讀操作消耗更大,如果索引過多,只會降低效率。
選擇"子查詢"還是"連接"在效率是有差別的,但最關鍵的差別還是表現在查詢的結果
集的讀寫性上,開發人員在寫一個"只讀"應用的查詢記錄集時,"子查詢"和"連接"各自
的效率就是應該首先考慮的問題,但是如果要實現"可寫"應用的查詢結果集,則無論是
相關還是非相關子查詢都是在復雜應用中難以避免的。
以上解決方案中,應該說第一種方法,簡潔有效,很有創意,是值得推薦的方法。當然,
最簡單的寫法應該是:
DELETE T
FROM T,T T1
WHERE T.學生ID=T1.學生ID and T.課程ID=T.課程ID and T.F < T1.F
其實這就是方法一的"標准"(但確實實不是《ANSI/ISO SQL》標准)連接寫法,以下各
題答案為了便於讀者理解,一般不采用這種寫法,這也是《ANSI/ISO SQL》標准所鼓
勵的,JOIN 確實更容易地表達表之間的關系,有興趣的讀者可自行改寫。如果使用
"*="實現兩表以上的外連接時,要注意此時 WHERE 子句的 AND 條件是有順序的,盡
管《ANSI/ISO SQL》標准不允許 WHERE 條件的順序影響查詢結果,但是 FROM 子句
的各表連接的順序可以影響查詢結果。
2.列印各科成績最高和最低的相關記錄: (就是各門課程的最高、最低分的學生和老師)
課程ID,課程名稱, 最高分,學生ID,學生姓名,教師ID,教師姓名, 最低分,學生ID,學生姓名,教師ID,教師姓名
如果這道題要是僅僅求出各科成績最高分或最低分,則是一道非常簡單的題了:
SELECT L.課程ID, MAX(L.課程名稱), MAX(L.成績) AS 最高分, MIN(L.成績) AS 最低分
FROM 成績表 L
GROUP BY L.課程ID
但是,刁鑽的題目卻是要列出各科最高和最低成績的相關記錄,這也往往才是真正需求。
既然已經選出各科最高和最低分,那麼,剩下的就是把學生和教師的信息並入這個結果
集。如果照這樣寫下去,非常麻煩,因為要添加的字段太多了,很快就使代碼變得難於
管理。還是換個思路吧:
SELECT L.課程ID,L.課程名稱,L.[成績] AS 最高分,L.[學生ID],L.[學生姓名],L.[教師ID],L.[教師姓名]
,R.[成績] AS 最低分,R.[學生ID],R.[學生姓名],R.[教師ID],R.[教師姓名]
FROM 成績表 L
JOIN 成績表 AS R ON L.[課程ID] = R.[課程ID]
WHERE L.[成績] = (SELECT MAX(IL.[成績])
FROM 成績表 AS [IL]
WHERE L.[課程ID] = IL.[課程ID]
GROUP BY IL.[課程ID]
)
AND
R.[成績] = (SELECT MIN(IR.[成績])
FROM 成績表 AS [IR]
WHERE R.[課程ID] = IR.[課程ID]
GROUP BY IR.[課程ID]
)
乍一看答案,好像很復雜,其實如果掌握了構造交叉透視表的基本方法和相關子查詢的
知識,問題迎刃而解。由於最低和最高分都是針對課程信息的,該答案巧妙地把課程信
息合並到了最高分的數據集中,當然也可以合並到最低分中。代碼中規中矩,風格很好,
可讀性也是不錯的。
3.按平均成績從高到低順序,列印所有學生的四門(數學,語文,英語,政治)課程成績: (就是每個學生的四門課程的成績單)
學生ID,學生姓名,數學,語文,英語,政治,有效課程數,有效平均分
(注: 有效課程即在 T 表中有該學生的成績記錄,如不明白可不列印"有效課程數"和"有效平均分")
需要說明的是: 題目之所以明確提出"四門(數學,語文,英語,政治)課程"是有道理的,
因為實現時,的確無法避免使原基本表中的行上的數據的值影響列,這又是一個典型的
"行變列"的相關子查詢:
SELECT 學生ID,MAX(學生姓名) AS 學生姓名,
(SELECT 成績 FROM 成績表 WHERE 學生ID=T.學生ID AND 課程ID='K1') AS 數學 ,
(SELECT 成績 FROM 成績表 WHERE 學生ID=T.學生ID AND 課程ID='K2') AS 語文 ,
(SELECT 成績 FROM 成績表 WHERE 學生ID=T.學生ID AND 課程ID='K3') AS 英語 ,
(SELECT 成績 FROM 成績表 WHERE 學生ID=T.學生ID AND 課程ID='K4') AS 政治 ,
COUNT(*) AS 有效課程數, AVG(T.成績) AS 平均成績
FROM 成績表 AS T
GROUP BY 學生ID
ORDER BY 平均成績
這可以說也是一個很規矩的解法,在這種應用場合,子查詢要比聯接代碼可讀性強得多。
如果數據庫引擎認為把它解析成聯接更好,那就由它去吧,其實本來相關子查詢也肯定含有連接。
這裡再補充一下,在實際應用中如果再加一張表 Ranks(Rank,MinValue,MaxValue):
┌─────┬─────┬─────┐
│ Rank │ MinValue │ MaxValue │
├─────┼─────┼─────┤
│ A │ 90 │ 100 │
├─────┼─────┼─────┤
│ B │ 80 │ 89 │
├─────┼─────┼─────┤
│ C │ 70 │ 79 │
├─────┼─────┼─────┤
│ D │ 60 │ 69 │
├─────┼─────┼─────┤
│ E │ 0 │ 59 │
└─────┴─────┴─────┘
就可以實現一個非常有實用價值的應用:
select 學生ID,MAX(學生姓名) as 學生姓名
,(select 成績 from 成績表 t where 學生ID=T0.學生ID and 課程ID='K1') as 數學
,(SELECT max(Rank) from Ranks ,成績表 t
where t.成績 >= Ranks.MinValue
and t.成績 <= Ranks.MaxValue
and t.學生ID=T0.學生ID and t.課程ID='K1'
) as 數學級別
,(select 成績 from 成績表 t where 學生ID=T0.學生ID and 課程ID='K2') as 語文
,(SELECT min(Rank)
from Ranks ,成績表 t
where t.成績 >= Ranks.MinValue
and t.成績 <= Ranks.MaxValue
and t.學生ID=T0.學生ID and t.課程ID='K2'
) as 語文級別
,(select 成績 from 成績表 t where 學生ID=T0.學生ID and 課程ID='K3') as 英語
,(SELECT max(Rank)
from Ranks ,成績表 t
where t.成績 >= Ranks.MinValue
and t.成績 <= Ranks.MaxValue
and t.學生ID=T0.學生ID and t.課程ID='K3'
) as 英語級別
,(select 成績 from 成績表 t where 學生ID=T0.學生ID and 課程ID='K4') as 政治
,(SELECT min(Rank)
from Ranks ,成績表 t
where t.成績 >= Ranks.MinValue
and t.成績 <= Ranks.MaxValue
and t.學生ID=T0.學生ID and t.課程ID='K4'
) as 政治級別
,count(*),avg(t0.成績)
,(SELECT max(Rank)
from Ranks
where AVG(T0.成績) >= Ranks.MinValue
and AVG(T0.成績) <= Ranks.MaxValue
) AS 平均級別
from 成績表 t0
group by 學生ID
這裡表面上使用了不等連接,再仔細想想,Ranks 表中每條記錄的區間是沒有交集的,
其實也可以認為是等值連接,這樣的表設計無疑存在著良好的擴展性,如果題目只要求
列印(學生ID,學生姓名,有效課程數,有效平均分,平均分級別):
select 學生ID,MAX(學生姓名) as 學生姓名,count(*),avg(t0.成績)
,(SELECT max(Rank)
from Ranks
where AVG(T0.成績) >= Ranks.MinValue
and AVG(T0.成績) <= Ranks.MaxValue
) AS 平均級別
from T as T0
group by 學生ID
則這樣的解決方案就比較全面了。
回到原題,再介紹一個比較取巧的辦法,僅需一個簡單分組查詢就可解決問題,有經驗的讀者可能已經想到了
,那就是 CASE:
SELECT 學生ID, MIN(學生姓名),
SUM(CASE 課程ID WHEN 'K1' THEN 成績 ELSE 0 END) AS 數學,
SUM(CASE 課程ID WHEN 'K2' THEN 成績 ELSE 0 END) AS 語文,
SUM(CASE 課程ID WHEN 'K3' THEN 成績 ELSE 0 END) AS 英語,
SUM(CASE 課程ID WHEN 'K4' THEN 成績 ELSE 0 END) AS 政治,
COUNT(*) AS 有效課程數, AVG(T.成績) AS 平均成績
FROM 成績表 AS T
GROUP BY 學生ID
ORDER BY 平均成績 DESC
雖然可能初看答案感覺有點怪,其實很好理解,可讀性並不低,效率也很高。但它不能
像前一個答案那樣,在成績中區分出某一門課這個學生究竟是缺考 (NULL),還是真得
零分。這個解法充分利用了 CASE 語句進行數據分類的作用: CASE 將成績按課程分
成四類,SUM 用來消去多余的 0。
SELECT [T].[學生ID],MAX([T].[學生姓名]) AS 學生姓名,
MAX([T1].[成績]) AS 數學,
MAX([T2].[成績]) AS 語文,
MAX([T3].[成績]) AS 英語,
MAX([T4].[成績]) AS 政治,
COUNT([T].[課程ID]) AS 有效課程數,
(ISNULL(MAX([T1].[成績]),0) +
ISNULL(MAX([T2].[成績]),0) +
ISNULL(MAX([T3].[成績]),0) +
ISNULL(MAX([T4].[成績]),0)) / COUNT([T].[課程ID]) AS 有效平均分
FROM 成績表 T
LEFT JOIN 成績表 AS [T1] ON [T].[學生ID] = [T1].[學生ID] AND [T1].[課程ID] = 'K1'
LEFT JOIN 成績表 AS [T2] ON [T].[學生ID] = [T2].[學生ID] AND [T2].[課程ID] = 'K2'
LEFT JOIN 成績表 AS [T3] ON [T].[學生ID] = [T3].[學生ID] AND [T3].[課程ID] = 'K3'
LEFT JOIN 成績表 AS [T4] ON [T].[學生ID] = [T4].[學生ID] AND [T4].[課程ID] = 'K4'
GROUP BY [T].[學生ID]
ORDER BY 有效平均分 DESC
這個方法是相當正統的聯接解法,盡管寫起來麻煩了些,但還是不難理解的。再從實用
角度考慮一下,真實需求往往不是象本題明確提出"列印四門 (數學,語文,英語,政治)
課程"這樣的相對靜態的需求,該是動態 SQL 大顯身手的時候了,很明顯方法一的寫法
無疑是利用程序構造動態 SQL 的最好選擇,當然另兩個 SQL 規律還是挺明顯的,同樣
不難構造。以 CASE 版答案為例: 先用一個游標遍歷,取出所有課程湊成:
SUM(CASE '課程ID' WHEN '課程名稱' THEN 成績 ELSE 0 END) AS 課程名稱 形式,
再補上 SELECT 和 FROM、WHERE 等必要條件,一個生成動態成績單的 SQL 就誕生了,
只要再由相關程序調用執行即可,這樣就可以算一個更完善的解決方案了。
其實,最類似的典型應用是在主、細關系中的主表投影中實現細表的匯總統計行,
例如兩張表:
Master(F,f1,f2 ...) 一對多 Details(F,f3,f4 ...)
SELECT *
,( SELECT COUNT(*)
FROM Details
WHERE Master.F = Details.F
)
,( SELECT SUM(F3)
FROM Details
WHERE Master.F = Details.F
)
FROM Master
4.按各科不平均成績從低到高和及格率的百分數從高到低順序,統計並列印各科平均成績和不及格率的百分數(用"N行"表示):
(就是分析哪門課程難)
課程ID,課程名稱,平均成績,及格百分比
SELECT 課程ID,MAX(課程名稱) AS 課程名稱,AVG(成績) AS 平均成績
,str(100 * SUM(CASE WHEN 成績 >=60 THEN 1 ELSE 0 END)/COUNT(*))+'%' AS 及格百分比
FROM 成績表 T
GROUP BY 課程ID
ORDER BY 及格百分比 DESC
這道題應該說是算簡單的了,就是用"行"來提供表現形式的。只要想明白要對數據如
何分組,取統計聚集函數,就萬事大吉了。
5.列印四門課程平均成績和及格率的百分數(用"1行4列"表示): (就是分析哪門課程難)
數學平均分,數學及格百分數,語文平均分,語文及格百分數,英語平均分,英語及格百分數,政治平均分,政治及格百分數
這道題其實就是上一題的"列"表現形式版本,相對於上一題,本題是靜態的,因為本題
同第三題一樣利用行上的數據構造了列,要實現擴展必須再利用另外的程序構造動態
SQL:
SELECT SUM(CASE WHEN 課程ID = 'K1' THEN 成績 ELSE 0 END)/SUM(CASE 課程ID WHEN 'K1' THEN 1 ELSE 0 END) AS 數學平均分
,100 * SUM(CASE WHEN 課程ID = 'K1' AND 成績 >= 60 THEN 1 ELSE 0 END)/SUM(CASE WHEN 課程ID = 'K1' THEN 1 ELSE 0 END) AS 數學及格百分數
,SUM(CASE WHEN 課程ID = 'K2' THEN 成績 ELSE 0 END)/SUM(CASE 課程ID WHEN 'K2' THEN 1 ELSE 0 END) AS 語文平均分
,100 * SUM(CASE WHEN 課程ID = 'K2' AND 成績 >= 60 THEN 1 ELSE 0 END)/SUM(CASE WHEN 課程ID = 'K2' THEN 1 ELSE 0 END) AS 語文及格百分數
,SUM(CASE WHEN 課程ID = 'K3' THEN 成績 ELSE 0 END)/SUM(CASE 課程ID WHEN 'K3' THEN 1 ELSE 0 END) AS 英語平均分
,100 * SUM(CASE WHEN 課程ID = 'K3' AND 成績 >= 60 THEN 1 ELSE 0 END)/SUM(CASE WHEN 課程ID = 'K3' THEN 1 ELSE 0 END) AS 英語及格百分數
,SUM(CASE WHEN 課程ID = 'K4' THEN 成績 ELSE 0 END)/SUM(CASE 課程ID WHEN 'K4' THEN 1 ELSE 0 END) AS 政治平均分
,100 * SUM(CASE WHEN 課程ID = 'K4' AND 成績 >= 60 THEN 1 ELSE 0 END)/SUM(CASE WHEN 課程ID = 'K4' THEN 1 ELSE 0 END) AS 政治及格百分數
FROM 成績表 T
這一句看起來很長,但實際上是最經典的 CASE 運用,很實用的數據分析技術。先將原
表中的成績一列連續投影 8 次備用於四門不同課程,充分利用 CASE 和數據的值域
['k1','k2','k3','k4']來劃分數據,再利用 SUM() [1 + ...+ 1] 實現了看似本來應
該用 COUNT(*) 的計數器的功能,這裡面不要說聯接和子查詢,甚至連 Group by 分組
的痕跡都找不到!如果讀起來吃力,完全可以先只保留一個字段,相對好理解些,看懂後
逐一補全。本題也可以算一個"行變列"的交叉透視表示例吧! 另外,"行"相對於"列"
是動態的,"行"是相對無限的,"列"是相對有限的,"行"的增刪是應用級的,可"隨意"增
刪,"列"的增刪是管理級的,不要輕易變動!
6.按不同老師所教不同課程平均分從高到低列印: (就是分析哪個老師的哪個課程水平高)
教師ID,教師姓名,課程ID,課程名稱,平均分
SELECT 教師ID,MAX(教師姓名) AS 教師姓名,課程ID,MAX(課程名稱) AS 課程名稱,AVG(成績) AS 平均成績
FROM 成績表 T
GROUP BY 課程ID,教師ID
ORDER BY AVG(成績) DESC
這道題的確沒啥好說的,就算閉著眼,不動手,答案也應脫口而出!
如果平均分按去掉一個最高分和一個最低分後取得,則也不難寫出:
SELECT 教師ID,MAX(教師姓名),課程ID,MAX(課程名稱) AS 課程名稱 --,AVG(成績) AS 平均成績
,(SUM(成績)
-(SELECT MAX(成績)
FROM 成績表
WHERE 課程ID= T1.課程ID AND 教師ID = T1.教師ID)
-(SELECT MIN(成績)
FROM 成績表
WHERE 課程ID= T1.課程ID and 教師ID = T1.教師ID))
/ CAST((SELECT COUNT(*) -2
FROM 成績表
WHERE 課程ID= T1.課程ID AND 教師ID = T1.教師ID) AS FLOAT) AS 平均分
FROM 成績表 AS T1
WHERE (SELECT COUNT(*) -2
FROM 成績表
WHERE 課程ID = T1.課程ID AND 教師ID = T1.教師ID) >0
GROUP BY 課程ID,教師ID
ORDER BY 平均分 DESC
********************************************************************************************
7.列印數學成績第 10 名到第 15 名的學生成績單
或列印平均成績第 10 名到第 15 名的學生成績單
[學生ID],[學生姓名],數學,語文,英語,政治,平均成績
如果只考慮一門課程,如:數學成績,非常簡單:
select Top 5 *
from T
where 課程id ='K1'
and 成績 not in(select top 15 成績
from T
order by 成績 desc
)
order by 成績 desc
union
select *
from T
where 課程id ='K1'
and 成績 not in(select top 10 成績
from T
order by 成績 desc
)
and 成績 in(select top 15 成績
from T
order by 成績 desc
)
order by 成績 desc
從邏輯上說,第 10 名到第 15 名就是從原前 15 名,"再"挑出前 5 名不要,保留剩下
的 5 名。第二種寫法是從前 15 名裡挑出不屬於原前 10 名的記錄,把兩個數據集做
一個差,因此要多用一個
子查詢,效率相對較低,它,如果要有《ANSI/ISO SQL》的 EXCEPT
關鍵字就是最理想的了。
這種技巧在數據"分頁"的應用中經常利用,只要遵循如下原則即可:
SELECT Top @PageSize *
FROM T
WHERE SortField NOT IN (SELECT TOP @PageSize * @Pagei SortFIEld
FROM T
ORDER BY SortFIEld
)
ORDER BY SortFIEld
至此,該題考察的主要目的已經達到。至於列印明晰成績單:
[學生ID],[學生姓名],數學,語文,英語,政治,平均成績 前面也有類似的題目,做起來
確實麻煩,因此下面僅提供參考答案,就不贅述了:
SELECT DISTINCT top 5
[成績表].[學生ID],
[成績表].[學生姓名] AS 學生姓名,
[T1].[成績] AS 數學,
[T2].[成績] AS 語文,
[T3].[成績] AS 英語,
[T4].[成績] AS 政治,
ISNULL([T1].[成績],0) + ISNULL([T2].[成績],0) + ISNULL([T3].[成績],0) + ISNULL([T4].[成績],0) as 總分
FROM [成績表]
LEFT JOIN [成績表] AS [T1]
ON [成績表].[學生ID] = [T1].[學生ID] AND [T1].[課程ID] = 'k1'
LEFT JOIN [成績表] AS [T2]
ON [成績表].[學生ID] = [T2].[學生ID] AND [T2].[課程ID] = 'k2'
LEFT JOIN [成績表] AS [T3]
ON [成績表].[學生ID] = [T3].[學生ID] AND [T3].[課程ID] = 'k3'
LEFT JOIN [成績表] AS [T4]
ON [成績表].[學生ID] = [T4].[學生ID] AND [T4].[課程ID] = 'k4'
WHERE ISNULL([T1].[成績],0) + ISNULL([T2].[成績],0) + ISNULL([T3].[成績],0) + ISNULL([T4].[成績],0)
NOT IN
(SELECT
DISTINCT
TOP 15 WITH TIES
ISNULL([T1].[成績],0) + ISNULL([T2].[成績],0) + ISNULL([T3].[成績],0) + ISNULL([T4].[成績],0)
FROM [成績表]
LEFT JOIN [成績表] AS [T1]
ON [成績表].[學生ID] = [T1].[學生ID] AND [T1].[課程ID] = 'k1'
LEFT JOIN [成績表] AS [T2]
ON [成績表].[學生ID] = [T2].[學生ID] AND [T2].[課程ID] = 'k2'
LEFT JOIN [成績表] AS [T3]
ON [成績表].[學生ID] = [T3].[學生ID] AND [T3].[課程ID] = 'k3'
LEFT JOIN [成績表] AS [T4]
ON [成績表].[學生ID] = [T4].[學生ID] AND [T4].[課程ID] = 'k4'
ORDER BY ISNULL([T1].[成績],0) + ISNULL([T2].[成績],0) + ISNULL([T3].[成績],0) + ISNULL([T4].[成績],0) DESC)
最後還要多說一句: 一般 TOP 關鍵字與 ORDER BY 子句合用才有真正意義。
8.統計列印各科成績,各分數段人數:
課程ID,課程名稱,[100-85],[85-70],[70-60],[<60]
盡管表面看上去不那麼容易,其實用 CASE 可以很容易地實現:
SELECT 課程ID, 課程名稱
,SUM(CASE WHEN 成績 BETWEEN 85 AND 100 THEN 1 ELSE 0 END) AS [100 - 85]
,SUM(CASE WHEN 成績 BETWEEN 70 AND 85 THEN 1 ELSE 0 END) AS [85 - 70]
,SUM(CASE WHEN 成績 BETWEEN 60 AND 70 THEN 1 ELSE 0 END) AS [70 - 60]
,SUM(CASE WHEN 成績 < 60 THEN 1 ELSE 0 END) AS [60 -]
FROM 成績表
GROUP BY 課程ID, 課程名稱
注意這裡的 BETWEEN,雖然字段名都是從高至低,可 BETWEEN 中還是要從低到高,這裡
如果不小心,會犯一個很難發現的邏輯錯誤: 在數學上,當a > b 時,[a, b]是一個空集。