如何避免重复多列相关子查询?

问题描述 投票:0回答:5

我希望相关子查询返回多列并且根据主表中的值过滤子查询。

某天的学生考试成绩列表:

create table student
(id, name     ); insert into student values
(1 , 'joe'    ),
(2 , 'steve'  );

create table testScore
(id, studentId, day  , score); insert into testScore values
(1 , 1        , 5    , 70   ),
(2 , 1        , 10   , 68   ),
(3 , 1        , 15   , 95   ),
(4 , 1        , 20   , 81   ),
(5 , 2        , 5    , 100  ),
(6 , 2        , 10   , 75   ),
(7 , 2        , 15   , 98   ),
(8 , 2        , 20   , 92   );
create index testScore_studentTopScores on testScore (studentId, score, day);

如果我需要为每个学生获得最高的考试成绩(包括相应的天数,有使用的

testScore_studentTopScores
索引)我很乐意这样做:

select
  student.name,
  topScore.score as topScore,
  topScore.day as topScoreDay
from
  student
  left join (
    select * from
      testScore
    where
      testScore.studentId = student.id
    order by
      score desc
    limit 1
  ) as topScore
order by
  topScore desc;

并让它返回:

名字 最高分 最高得分日
史蒂夫 100 5
95 15

但是

from
子句子查询不能引用外部/父表。列子查询和
where
子句子查询能够访问
from
子句中的表,但不能返回多列。所以我必须为每一列复制子查询:

select
  student.name,

  (
    select
      score
    from
      testScore
    where
      testScore.studentId = student.id
    order by
      score desc
    limit 1
  )  as topScore,

  (select day from (
    select
      score,
      day
    from
      testScore
    where
      testScore.studentId = student.id
    order by
      score desc
    limit 1
  ))  as topScoreDay

from
  student
order by
  topScore desc;

SQLite 和 MySQL 都无法在不重复每列的情况下做到这一点。 Microsoft SQL Server 有一个

outer apply
连接运算符;类似于
left join
,但该子句可以引用
from
子句中先前的表。这就是我想要的 SQLite。

sql sqlite correlated-subquery
5个回答
0
投票

可以通过计算

row_number()
上的
partition by studentId order by score desc
,然后在行号 = 1 时对其执行
left join
来解决此特定查询。为了清楚起见,我已将该部分提取到 CTE 中,但您可以直接将其放入如果需要,在
left join
内:

with testScoreWithRowNumber as (
  select *, row_number() over (partition by studentId order by score desc) as rn
  from testScore
)
select 
  s.name,
  r.score as topScore,
  r.day as topScoreDay
from student s
left join testScoreWithRowNumber r on r.studentId = s.id and r.rn = 1
order by r.score desc;

输出:

name,topScore,topScoreDay
steve,100,5
joe,95,15

0
投票

您可以与学生的最高分左连接,并与更好的分数左连接,并过滤掉任何具有更好分数的元组:

select
  student.name,
  topScore.score as topScore,
  topScore.day as topScoreDay
from
  student
  left join testScore as topScore
  on topScore.studentId = student.id
  left join testScore as evenBetterScore
  on topScore.studentId = evenBetterScore.studentId and topScore.score < evenBetterScore.score
where evenBetterScore.studentId is null
order by
  topScore.score desc;

因此,您最终将获得每个学生的单个记录,以及该学生的最高分(如果存在)。


0
投票

我认为答案要简单得多;

SELECT s.name, MAX(t.score) as topScore, t.day AS topScoreDay
FROM student as s
LEFT JOIN testScore AS t ON s.id = t.studentId
GROUP BY s.id
ORDER BY topScore DESC, t.score ASC;

结果:

名字 最高分 最高得分日
史蒂夫 100 5
95 15

0
投票

高效,每个学生只需查找一次;将我想要的列塞入一个 JSON 列并稍后提取它们:

select
  topScore.name,
  topScore.topScoreJson ->> 'score' as topScore,
  topScore.topScoreJson ->> 'day' as topScoreDay
from (
  select
    student.name,
    (
      select
        json_object(
          'score', testScore.score,
          'day', testScore.day
        )
      from
        testScore
      where
        testScore.studentId = student.id
      order by
        testScore.score desc
      limit 1
    ) as topScoreJson
  from
    student
) as topScore
order by
  topScore desc;

0
投票

根据 数据库管理员的回答 SQLite 仅在直接连接子查询时不允许访问外部表。它确实允许访问属于

on
子句一部分的子查询内的外部表。

我根据 EXPLAIN QUERY PLAN

 使用“
相关标量子查询”,它允许访问父
student
表的当前行。无需在子查询中使用
limit 1
,因为 SQLite 仅返回满足
on
子句的第一行。

select
  student.name,
  testScore.score as topScore,
  testScore.day as topScoreDay
from
  student
  left join testScore on testScore.id = (
    select id from
      testScore
    where
      testScore.studentId = student.id
    order by
      score desc
  )
order by
  topScore desc;
© www.soinside.com 2019 - 2024. All rights reserved.