【问题标题】:Is there a more elegant way of writting this SQL query?有没有更优雅的方式来编写这个 SQL 查询?
【发布时间】:2013-02-10 16:07:31
【问题描述】:

我正在为斯坦福大学的 DB 课程做介绍,这是家庭作业之一。我的代码做得很好,但我不太喜欢我两次重复使用相同的 SELECT-FROM-JOIN 部分:

SELECT name, grade
FROM Highschooler
WHERE
    ID IN (
        SELECT H1.ID
        FROM Friend
        JOIN Highschooler AS H1
            ON Friend.ID1 = H1.ID
        JOIN Highschooler AS H2
            ON Friend.ID2 = H2.ID
        WHERE H1.grade = H2.grade    
    ) AND
    ID NOT IN (
        SELECT H1.ID
        FROM Friend
        JOIN Highschooler AS H1
            ON Friend.ID1 = H1.ID
        JOIN Highschooler AS H2
            ON Friend.ID2 = H2.ID
        WHERE H1.grade <> H2.grade
    )
ORDER BY grade, name

这是代码中使用的两个表的 SQL 架构:

Highschooler(ID int, name text, grade int);
Friend(ID1 int, ID2 int);

我必须查询所有只有同年级朋友而不是其他年级朋友的高中生。有没有办法以某种方式只编写一次下面的代码,并为两个不同的 WHERE 子句 = 和 重复使用两次?

    SELECT H1.ID
    FROM Friend
    JOIN Highschooler AS H1
        ON Friend.ID1 = H1.ID
    JOIN Highschooler AS H2
        ON Friend.ID2 = H2.ID

编辑:我们需要提供 SQLite 代码。

【问题讨论】:

    标签: sql sqlite


    【解决方案1】:

    这是WHERE EXISTS 查询的“典型儿童”示例:

    SELECT name, grade
    FROM Highschooler ME
    WHERE EXISTS (
        SELECT 1
        FROM Friend F
        JOIN Highschooler OTHER on F.ID2=OTHER.ID
        WHERE F.ID1=ME.ID AND OTHER.Grade = ME.GRADE
    )
    AND NOT EXISTS (
        SELECT 1
        FROM Friend F
        JOIN Highschooler OTHER on F.ID2=OTHER.ID
        WHERE F.ID1=ME.ID AND OTHER.Grade <> ME.GRADE
    )
    

    如果SELECT 返回一行或多行,则EXISTS 条件为true;否则为false。您需要做的就是将内部子查询与外部子查询(F.ID1=ME.ID 部分)关联,然后将所需的其余约束(OTHER.Grade = ME.GRADEOTHER.Grade &lt;&gt; ME.GRADE)添加到您的查询。

    【讨论】:

      【解决方案2】:

      这是关于与个人相关的群体的典型问题。当您遇到这样的问题时,一种方法是使用连接(成对查看事物)。通常更好的方法是使用聚合来一次查看整个组。

      这里的见解是,如果您有一群朋友并且所有人的成绩相同,那么最低和最高成绩将相同。

      该提示可能足以让您编写查询。如果是这样,请停在这里。

      返回您想要的查询比您所做的要简单得多。你只需要看看朋友的成绩:

      SELECT f.id1
      FROM Friend f jJOIN
           Highschooler fh
           ON Friend.ID1 = fh.ID join
      group by f.id1
      having max(fh.grade) = min(fh.grade)
      

      having 子句确保所有内容都相同(忽略 NULL 值)。

      编辑:

      这个版本回答了这个问题:哪些高中生的朋友都在同一个年级。你的问题模棱两可。也许你的意思是朋友原来的人都在同一年级。如果是这样,那么你可以通过一个小的修改来做到这一点。一种方法是将having 子句更改为:

      having max(fh.grade) = min(fh.grade) and
             max(fh.grade) = (select grade from Highschooler h where f.id1 = h.id1)
      

      这会检查朋友原人都在同一年级。

      【讨论】:

      • +1 是的,这可能是最好的建议,因为它正确地利用了我们拥有的有关要返回的数据的所有信息,并且它在查询的同一层上这样做。
      • 很抱歉,但我不明白如何使用/熟练使用您的代码。我尝试了它的各种变体,但没有一个返回正确的记录。
      【解决方案3】:

      有时,当您将一些过滤联接转换为 UNION 或 MINUS/EXCEPT 等集合操作时,您可以获得更自然的查询形状。例如,您的查询可以写成(伪代码):

        SELECT H.id
        FROM Highschooler H
        JOIN .... | has a friend
        WHERE ... | in SAME grade
      
      EXCEPT
      
        SELECT H.id
        FROM Highschooler H
        JOIN .... | has a friend
        WHERE ... | in OTHER grade
      

      有些 SQL 引擎使用关键字“MINUS”,有些使用“EXCEPT”。

      但请注意,与 UNION 非常相似,这将执行两个查询,然后过滤它们的结果。这可能具有与单个全能查询不同的性能,但请注意不一定更糟。很多时候我发现它甚至有更好的性能,因为对单列的“例外”,特别是排序,非常快

      另外,如果您的数据库引擎允许,您可能会尝试使用 View 或 CTE 来缩短您的原始查询,但我认为这样做没有多大意义,除了美观

      【讨论】:

        【解决方案4】:

        一些数据库支持减号关键字。

        select whatever
        from wherever
        where id in
        (select id
         from somewhere
         where something
         minus
         select id
         from somewhere
         where something else
         )
        

        其他数据库支持相同的概念,但使用关键字 except,而不是 minus。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2012-08-31
          • 2019-12-25
          • 2019-01-12
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多