按与分组功能最大值相关的值查找分组

时间:2019-04-04 01:13:50

标签: sql oracle

假设我有几个表格,用于存储学生的测试标记,就像这样

tests (
    testId INT,
    proctoredDate DATE,
    totalMark INT,
    weight FLOAT,
    CONSTRAINT test_id_primary_key PRIMARY KEY(testId),
    ...
)
student_test_marks (
    studentId INT,
    testId INT,
    awardedMark INT,
    CONSTRAINT student_test_composite_key PRIMARY KEY(studentId, testId),
    ...
)

我正在尝试确定给定月份中哪个学生的累积分数最高。因此,该过程是对每个学生在指定月份中分组的所有测试成绩和权重求和,并仅返回最大和。我已经有一个有效的查询,但我认为它不是非常有效。

SELECT studentId
FROM student_test_marks INNER JOIN tests
ON student_test_marks.testId = tests.testId
GROUP BY studentId, EXTRACT(MONTH FROM proctoredDate)
HAVING EXTRACT(MONTH FROM proctoredDate) = 4 AND 
SUM((awardedMark / totalMark) * weight) IN (
    SELECT MAX(SUM((awardedMark / totalMark) * weight))
    FROM student_test_marks INNER JOIN tests
    ON student_test_marks.testId = test.testId
    GROUP BY studentId, EXTRACT(MONTH FROM proctoredDate)
    HAVING EXTRACT(MONTH FROM proctoredDate) = 4
)

它确实可以工作,但是我感觉它不是最佳的,因为查询将联接,分组和过滤两次。我考虑过按组的“求和”列排序,然后仅获取第一行,但感觉很笨拙。

是否有更好的方法来解决这个问题?

2 个答案:

答案 0 :(得分:1)

我认为您只想使用分析功能:

SELECT m.*
FROM (SELECT TRUNC(proctoredDate, 'MON') as yyyymm,
             studentId, SUM((awardedMark / totalMark) * weight) as weighted_mark,
             RANK() OVER (PARTITION BY TRUNC(proctoredDate, 'MON')
                          ORDER BY SUM((awardedMark / totalMark) * weight)
                         ) as seqnum
      FROM student_test_marks stm INNER JOIN
           tests t
           ON stm.testId = t.testId
      GROUP BY TRUNC(proctoredDate, 'MON'), studentId
     ) m
WHERE seqnum = 1;

答案 1 :(得分:0)

with tests as (
select 1 testid, date '2019-01-10' proctoredDate, 100 totalmark from dual union all
select 2 testid, date '2019-02-22' proctoredDate, 100 totalmark from dual union all
select 3 testid, date '2019-03-14' proctoredDate, 100 totalmark from dual union all
select 4 testid, date '2019-04-19' proctoredDate, 100 totalmark from dual),
student_test_marks as (
select  1 studentid, 1 testid, 50 awardedmark from dual union all
select  2 studentid, 1 testid, 30 awardedmark from dual union all
select  3 studentid, 1 testid, 70 awardedmark from dual union all
select  4 studentid, 1 testid, 60 awardedmark from dual union all
select  1 studentid, 2 testid, 30 awardedmark from dual union all
select  2 studentid, 2 testid, 40 awardedmark from dual union all
select  3 studentid, 2 testid, 50 awardedmark from dual union all
select  4 studentid, 2 testid, 70 awardedmark from dual union all
select  1 studentid, 3 testid, 70 awardedmark from dual union all
select  2 studentid, 3 testid, 60 awardedmark from dual union all
select  2 studentid, 4 testid, 30 awardedmark from dual union all
select  1 studentid, 4 testid, 40 awardedmark from dual union all
select  2 studentid, 4 testid, 50 awardedmark from dual
)
select 
mn, 
max(studentid)  keep (dense_rank last order by sumawarded) studentid,
max(sumawarded) max_sumawarded
from 
  (select trunc(t.proctoredDate, 'month') mn, s.studentid, sum(awardedmark) sumawarded
   from                    tests t
   inner join student_test_marks s on t.testId = s.testId
   group by trunc(t.proctoredDate, 'month'), s.studentid
  )
group by mn;

MN                   STUDENTID MAX_SUMAWARDED
------------------- ---------- --------------
2019-01-01 00:00:00          3             70
2019-02-01 00:00:00          4             70
2019-03-01 00:00:00          1             70
2019-04-01 00:00:00          2             80