避免SQL查询中的冗余代码

时间:2013-02-07 23:14:50

标签: sql oracle aggregate-functions

我有Light Athletic比赛结果的表格。球员获得前三名的积分。 我需要列出他们的分数的田径名称。 我有这个:

SELECT NAME, SUM(many) as sum FROM
(
(SELECT NAME, count(*) * (SELECT points from "points for place" where place = 1)  as many
FROM RESULTS R1 WHERE
(SELECT count(*) FROM RESULTS R2 WHERE
    R1.result < R2.result
    AND R1.DISCIPLINE = R2.DISCIPLINE
    AND R1.CITY = R2.CITY) = 0
GROUP BY NAME)
UNION
(SELECT NAME, count(*) * (SELECT points from "points for place" where place = 2)  as many
FROM RESULTS R1 WHERE
(SELECT count(*) FROM RESULTS R2 WHERE
    R1.result < R2.result
    AND R1.DISCIPLINE = R2.DISCIPLINE
    AND R1.CITY = R2.CITY) = 1
GROUP BY NAME)
UNION
(SELECT NAME, count(*) * (SELECT points from "points for place" where place = 3)  as many
FROM RESULTS R1 WHERE
(SELECT count(*) FROM RESULTS R2 WHERE
    R1.result < R2.result
    AND R1.DISCIPLINE = R2.DISCIPLINE
    AND R1.CITY = R2.CITY) = 2
GROUP BY NAME)
)
GROUP BY NAME ORDER BY SUM;

我有三次几乎相同的冗余代码。如果这不是取决于地方,我可以使用View ...

2 个答案:

答案 0 :(得分:3)

我认为你可以从根本上简化为:

SELECT name, sum(p.points) AS total_points
FROM  (
    SELECT r1.name
          ,sum((r1.result > r2.result)::int) + 1 AS place
    FROM   results r1
    LEFT   JOIN results r2 USING (discipline, city)
    GROUP  BY 1
    HAVING sum((r1.result > r2.result)::int) < 3
    ) r
JOIN   "points for place" p USING (place)
GROUP  BY r.name
ORDER  BY total_points;

使用 PostgreSQL 进行测试 由于OP后来宣布了Oracle,这里是 Oracle 的另一个版本:

SELECT name, SUM(p.points) AS total_points
FROM  (
   SELECT r1.name
         ,SUM(CASE WHEN r1.result > r2.result THEN 1 ELSE 0 END) + 1 AS place
   FROM   results r1
   LEFT   JOIN results r2 USING (discipline, city)
   GROUP  BY r1.name
   HAVING SUM(CASE WHEN r1.result > r2.result THEN 1 ELSE 0 END) < 3
) r
JOIN   "points for place" p USING (place)
GROUP  BY r.name
ORDER  BY total_points;

应该提供与问题中的查询相同的结果。只有更快更简单 ->sqlfiddle并排显示新查询和原始信息。

现在我知道这个查询是关于什么的:dense_rank()显然是更好的解决方案。

重点

  • 对于Postgres,sum((r1.result < r2.result)::int)计算r1.result低于r2.result的次数。 boolean转换为integer会产生1为TRUE,0为FALSE 对于Oracle,这个表达式也是如此:

    SUM(CASE WHEN r1.result < r2.result THEN 1 ELSE 0 END)
    
  • 我将1添加到该计数中,并将其命名为place,以便从表points中获取"points for place"

答案 1 :(得分:2)

您可以通过以下方式简化:

select r.name, 
       sum(p.points) total_points
  from (select city, discipline, name, 
               row_number() over (partition by city, discipline order by result) place
         from results r) r
       inner join "points for place" p
               on p.place = r.place
 where p.place <= 3 
 group by r.name
 order by total_points desc;

*注意:如果可以存在平局,并且您希望两者都计为该位置,请使用dense_rank()代替row_number()

考虑到您的测试数据,where p.place <= 3也可能是多余的,因为您只有3个得分......所以您可以省略它。我已经离开了。

但令我感到困惑的是,在你原来的SQL和Erwins的答案中,你们两个都倒置了。那个跑得最久的家伙是第一名?!

,即你的小提琴:

-- RESULTS --
INSERT INTO results VALUES
('9.87', 'Doha', '100m', 'Justin GATLIN');
...
INSERT INTO results VALUES
('10.28', 'Doha', '100m', 'Jimmy VICAUT');

现在我在9.87秒内读到Justin WON。但你们都认为这是吉米的胜利。

如果是这样,那么分析应该是

partition by city, discipline order by result desc