2014-02-11 425 views
2

我在追蹤學生測試結果的表格上執行查詢。測試由多個部分組成,並且每個部分的評分都有一列。每一行都是學生測試的一個實例。這些部分可以一次完成,也可以分成多次嘗試。例如,一名學生今天可以參加一節,其餘的則可以參加明天。此外,學生可以重新參加任何考試。按列分組,選擇最近的值

樣品學生

 
StudentID WritingSection ReadingSection MathSection DateTaken 
1   65    85    54   4/1/2013 14:53 
1   98    NULL    NULL   4/8/2013 13:13 
1   NULL    NULL    38   5/3/2013 12:43 

NULL意味着該部分未施用對於給定的測試例,和第二部分得分意味着切片奪回。

我想要一個由StudentID組成的查詢,這樣每個學生只有一行,並且返回每個節的最近分數。我正在尋找一種有效的方法來解決這個問題,因爲我們在數據庫中有數十萬次的測試嘗試。

預期結果:

 
StudentID WritingSection ReadingSection MathSection DateTaken 
1   98    85    38    5/3/2013 12:43 

編輯: 已經有很多好的解決方案。在選擇答案之前,我想在下週再試一次。感謝大家!

+0

最近的得分或最佳分數?例如,基於上述,我假設你想讓學生1,98,86,38 ??? – Sparky

+0

最新的評分。我將編輯並放置預期結果。 –

回答

2

對不起 - 我以前的答案回答比一個提出不同的問題:)它會從最返回的所有數據最近行。要問的問題是聚合所有行以獲取每個主題的最新分數。

但我要離開它在那裏,因爲我回答的問題是常見的一種,也許有人降落在這個問題上其實是有這個問題,而不是:)

現在回答這個問題實際:

我認爲這樣做最徹底的方法是用透視和逆透視:

SELECT StudentID, [WritingSection], [ReadingSection], [MathSection], MAX(DateTaken) DateTaken 
FROM (
    SELECT StudentID, Subject, DateTaken, Score 
    FROM (
    SELECT StudentID, Subject, DateTaken, Score 
     , row_number() OVER (PARTITION BY StudentID, Subject ORDER BY DateTaken DESC) as rowNum 
    FROM Students s 
    UNPIVOT (
     Score FOR Subject IN ([WritingSection],[ReadingSection],[MathSection]) 
    ) u 
) x 
    WHERE x.rowNum = 1 
) y 
PIVOT (
    MAX(Score) FOR Subject IN ([WritingSection],[ReadingSection],[MathSection]) 
) p 
GROUP BY StudentID, [WritingSection], [ReadingSection], [MathSection] 

最裏面的子查詢(X)使用SQL的UNPIVOT功能正常化數據(意思是把每個學生的成績在測試的每個部分成單排) 。

下一個子查詢out(y)只是簡單地將行過濾爲最近的分數FOR EACH SUBJECT INDIVIDUALLY(這是一個解決SQL錯誤的方法,您不能在row_number()中使用窗口函數WHERE子句)。我們使用SQL的PIVOT函數,因爲您希望數據以非規格化的原始格式顯示(測試的每個部分爲1列),所以我們使用SQL的PIVOT函數。這隻需將行轉換爲列 - 測試的每個部分都有一個列。最後,你說你想要顯示最近的測試(儘管每個部分都有自己獨特的「最新」日期)。所以我們簡單地聚合這3個可能不同的DateTakens來找到最近的。

如果將來添加更多的部分,這將比其他解決方案更容易擴展 - 只需將列名添加到列表中即可。

+0

這看起來很有趣。我會閱讀'PIVOT'和'UNPIVOT' :) –

+0

確實,不得不這樣做是一種代碼氣味,原始表格未正確標準化。這似乎是在修復表格設計時暫時引入標準化的好方法。 –

0

如何使用下面的最大DateTaken?

SELECT MAX(DateTaken)FROM TABLE_NAME WHERE StudentID = 1

你可以使用一個子查詢來獲得行怎麼樣?

SELECT WritingSection FROM TABLE_NAME WHERE StudentID = 1和DateTaken =(SELECT MAX(DateTaken)FROM TABLE_NAME WHERE StudentID = 1和WritingSection IS NOT NULL)

您將需要運行這個的兩倍多爲ReadingSection和MathSection?

2

這很棘手。每個部分的分數都可能來自不同的記錄。但max()min()的正常規則不適用。

以下查詢從最新的非NULL值開始獲取每個節的序列號。這則用於條件聚集在外部查詢:

select s.StudentId, 
     max(case when ws_seqnum = 1 then WritingSection end) as WritingSection, 
     max(case when rs_seqnum = 1 then ReadingSection end) as ReadingSection, 
     max(case when ms_seqnum = 1 then MathSection end) as MathSection, 
     max(DateTaken) as DateTaken 
from (select s.*, 
      row_number() over (partition by studentid 
           order by (case when WritingSection is not null then 0 else 1 end), DateTaken desc 
           ) as ws_seqnum, 
      row_number() over (partition by studentid 
           order by (case when ReadingSection is not null then 0 else 1 end), DateTaken desc 
           ) as rs_seqnum, 
      row_number() over (partition by studentid 
           order by (case when MathSection is not null then 0 else 1 end), DateTaken desc 
           ) as ms_seqnum 
     from student s 
    ) s 
where StudentId = 1 
group by StudentId; 

where子句在這個查詢可選。你可以刪除它,它仍然適用於所有學生。

這個查詢比它需要更復雜,因爲數據沒有被標準化。如果您可以控制數據結構,請考慮關聯/聯結表,每個學生每個學生一行,分數和測試日期作爲表中的列。 (完全正常會在測試日期引入另一個表格,但這可能不是必需的。)

+1

+1提到規範化。 –

+0

這是我害怕我可能需要做的事情。 +1提到正常化。不幸的是,我不能控制表結構。設計比你期望的更加加重 - 我已經簡化了這個問題,以防止眼睛流血:P –

0

Joe的解決方案將只返回一個學生ID - 最後一次參加考試的學生。獲取每個學生ID的最新日期的方法是使用分析功能。舉個例子,如果你正在使用Oracle數據庫:

SELECT a.StudentID, a.DateTaken 
    FROM ( SELECT StudentID, 
      DateTaken, 
      ROW_NUMBER() 
       OVER (PARTITION BY StudentID ORDER BY DateTaken DESC) 
       rn 
     FROM pto.test 
    ORDER BY DateTaken DESC) a 
WHERE a.rn = 1 

注ROW_NUMBER()功能可按如何把1在每個學生證的最後日期。而在外層select中,你只需用rn = 1過濾這些記錄......只執行內層select來查看它是如何工作的。 讓我知道你正在使用什麼樣的數據庫來給你一個解決方案。每個數據庫都有它自己的分析功能實現,但邏輯是一樣的...

0

這是一個非常經典的煩人的SQL問題 - 沒有超級優雅的方式來做到這一點。下面是我發現的最好的:

SELECT s.* 
FROM Students s 
JOIN (
    SELECT StudentID, MAX(DateTaken) as MaxDateTaken 
    FROM Students 
    GROUP BY StudentID 
) f ON s.StudentID = f.StudentID AND s.DateTaken = f.MaxDateTaken 

入世對日期字段是不是超級理想(這打破關係的事件爲MAX)或快速(取決於表是如何索引)。如果你有一個int ROWID是在所有行獨特的,這將是最好的事:

SELECT s.* 
FROM Students s 
JOIN (
    SELECT rowID 
    FROM (
    SELECT StudentID, rowID, row_number() OVER (PARTITION BY StudentID ORDER BY DateTaken DESC) as rowNumber 
    FROM Students 
) x 
    WHERE x.rowNumber = 1 
) f ON s.rowID = f.rowID 
0
SELECT student.studentid, 
     WRITE.writingsection, 
     READ.readingsection, 
     math.mathsection, 
     student.datetaken 
FROM 
-- list of students/max dates taken 
(SELECT studentid, 
     Max(datetaken) datetaken 
FROM test_record 
GROUP BY studentid) student, 
-- greatest date for student with a writingsection score (dont care what the date is here, just that the score comes from the greatest date) 
(SELECT studentid, 
     writingsection 
FROM test_record t 
WHERE writingsection IS NOT NULL 
     AND datetaken = (SELECT Max(datetaken) 
         FROM test_record 
         WHERE studentid = t.studentid 
           AND writingsection IS NOT NULL)) WRITE, 
(SELECT studentid, 
     readingsection 
FROM test_record t 
WHERE readingsection IS NOT NULL 
     AND datetaken = (SELECT Max(datetaken) 
         FROM test_record 
         WHERE studentid = t.studentid 
           AND readingsection IS NOT NULL)) READ, 
(SELECT studentid, 
     mathsection 
FROM test_record t 
WHERE mathsection IS NOT NULL 
     AND datetaken = (SELECT Max(datetaken) 
         FROM test_record 
         WHERE studentid = t.studentid 
           AND mathsection IS NOT NULL)) math 
WHERE 
    -- outer join in case a student has no score recorded for one or more of the sections 
    student.studentid = READ.studentid(+) 
    AND student.studentid = WRITE.studentid(+) 
    AND student.studentid = math.studentid(+);