1
4

3 回答 3

2

首先让我们陈述问题。我们想要每个类别中评分最高的所有电影。然后,其中,我们想要最低的价格。

首先获得最高评分

SELECT * FROM Films 
INNER JOIN 
(SELECT Max(Rating) as Rating, Category
  FROM Films AS FM1 INNER JOIN Category AS C1 ON C1.CategoryId = FM1.CategoryId
  GROUP BY Category
) x on Films.Rating = x.Rating and Films.Category = x.Category

现在,从中,获得最便宜的价格

  SELECT * FROM Films INNER JOIN
    (SELECT Min(DVDPrice), x.Rating, Category FROM 
      (SELECT * FROM Films INNER JOIN 
        (SELECT MAX(Rating) as Rating, Category
          FROM Films AS FM1 INNER JOIN Category AS C1 ON C1.CategoryId = FM1.CategoryId
          GROUP BY Category
        ) x on Films.Rating = x.Rating and Films.Category = x.Category
      )
      WHERE DVDPrice IS NOT NULL
      GROUP BY Category, DVDPrice
    ) y on Films.Rating = y.Rating and Films.Category = y.Category and Films.DVDRating = y.DVDRating
于 2009-11-05T19:52:23.363 回答
2

what you want is:
-----------------
for each category, retrieve a film that meets the following 2 conditions:
_ condition1:____ rating= max rating in that category_
_
condition2: ____ price= min price in that category for films verifying condition 1_

--> in other terms it's equivalent to order films by Rating Desc then DVDPrice Asc for each category and take the first one.

1个解决方案是:

SELECT FilmName, Rating, DVDPrice, Category
FROM Films FM1 INNER JOIN Category AS C1 ON C1.CategoryId = FM1.CategoryId
WHERE FM1.FilmId = (SELECT TOP 1 FilmId
                      FROM Films AS FM2
                     WHERE FM2.CategoryId = FM1.CategoryId
                  ORDER BY Rating DESC, DVDPrice)

或者:

SELECT FM.FilmName, FM.Rating, FM.DVDPrice, C1.Category
  FROM (SELECT FM0.*, ROW_NUMBER() over (ORDER BY Rating DESC, DVDPrice) rank
          FROM Films FM0) FM 
INNER JOIN Category AS C1 ON C1.CategoryId = FM.CategoryId
INNER JOIN (SELECT FM1.CategoryId, MIN(FM1.rank) rank
              FROM (SELECT CategoryId,
                           ROW_NUMBER() over (ORDER BY Rating DESC,DVDPrice) rank
                  FROM Films) AS FM1
        GROUP BY CategoryId) FM2
 ON FM.CategoryId = FM2.CategoryId
AND FM.rank = FM2.rank

用你的数据,我做了一些测试,似乎下面的查询比上面的 2 更好:

SELECT FM.*, C.Category
FROM (SELECT FM1.CategoryId, MAX(FM1.FilmId) FilmId
        FROM Films FM1
        WHERE NOT EXISTS (SELECT NULL 
                                FROM Films AS FM2
                               WHERE FM2.CategoryId = FM1.CategoryId
                                 AND (FM1.Rating < FM2.Rating 
                                      OR (    FM1.Rating = FM2.Rating 
                                          AND FM1.DVDPrice > FM2.DVDPrice)
                                     )
                          )
      GROUP BY FM1.CategoryId) FF
INNER JOIN Films FM on FM.FilmId = FF.FilmId
                   AND FM.CategoryId = FF.CategoryId
INNER JOIN Category AS C1 ON C1.CategoryId = FM.CategoryId
于 2009-11-05T19:55:33.310 回答
1

1)是的,您提供的第二个查询看起来更好。但我给@Russell Steen 的解决方案 +1,因为它避免使用相关子查询。

这是我在 SO 上经常看到的 best-n-per-group 问题的一种变体。这是另一种可能的解决方案:

SELECT f.*
FROM Films f
LEFT OUTER JOIN Films p
 ON (f.CategoryId = p.CategoryId AND f.DVDPrice > p.DVDPrice)
LEFT OUTER JOIN Films r
 ON (f.CategoryId = r.CategoryId AND f.DVDPrice = r.DVDPrice AND f.Rating < r.Rating)
WHERE p.CategoryId IS NULL AND r.CategoryId IS NULL;

解释是,我们试图p在同一类别中寻找价格更低的电影“ ”。当我们没有找到时,p.*将为 NULL,因为这就是外连接的工作方式。当没有价格较低的 DVD 时,我们会找到价格最低的 DVD。

我们进一步尝试相同的技巧来找到r评分最高的电影“ ”。这次我们限制在与影片同类别价(即最低价)的影片f。否则我们会无意中找到该类别中评分最高的电影,即使它并不便宜。

您还可以颠倒连接的顺序,首先找到最高评级,然后在评级最高的那些中找到最低价格。这取决于您将什么放在更重要的位置——低价或高评级。无论您使用什么解决方案,您都必须对此优先级做出决定。

2)您尝试的另一个查询不起作用,因为您在子查询中使用的条件不会消除 FT2 子查询的任何错误行。这是一个“绿色鸡蛋和火腿”问题:无论是在火车上还是在飞机上,在船上还是在山羊上,您的膳食中仍然包含绿色鸡蛋和火腿。


更新:好的,感谢您的示例数据。当您第一次问这个问题时,您没有提供某些电影可能不合格的信息,因为它们在 DVD 上不可用并且在DVDPrice列中有一个 NULL。这是一个使用我的技术的更新查询,它返回正确的电影,每个类别一个,不包括 DVD 上不可用的电影,价格最低,评分最高:

SELECT f.FilmName, f.Rating, f.DVDPrice, f.CategoryId
FROM Films f
LEFT OUTER JOIN Films p ON (f.CategoryId = p.CategoryId
  AND p.AvailableOnDvd = 'Y' AND f.DVDPrice > p.DVDPrice)
LEFT OUTER JOIN Films r ON (f.CategoryId = r.CategoryId
  AND r.AvailableOnDvd = 'Y' AND f.DVDPrice = r.DVDPrice AND f.Rating < r.Rating)
WHERE f.AvailableOnDvd = 'Y' AND p.CategoryId IS NULL AND r.CategoryId IS NULL
ORDER BY f.CategoryId;

输出:

+-------------------------+--------+----------+------------+
| FilmName                | Rating | DVDPrice | CategoryId |
+-------------------------+--------+----------+------------+
| The Maltese Poodle      |      1 |     2.99 |          1 |
| Third                   |      7 |    10.00 |          2 |
| Nightmare on Oak Street |      2 |     9.99 |          3 |
| Planet of the Japes     |      5 |    12.99 |          4 |
| Soylent Yellow          |      5 |    12.99 |          5 |
| Sense and Insensitivity |      3 |    15.99 |          6 |
+-------------------------+--------+----------+------------+

这与您在类别 6 中的结果不同,因为您的样本数据中的Sense and Insensitive是 DVD 上唯一可用的电影。 15 Late Afternoon不可用,即使 DVDPrice 具有非空值。如果我将其更改为AvailableOnDvd='Y'15 Late Afternoon而不是其他电影。


关于您关于我如何解决此问题的问题,这是 SQL 中一个常见问题的变体,我已将其标记为“greatest-n-per-group”问题。您希望查询返回每部电影,以便在同一类别f中不存在具有较低级别的电影。DVDPrice我通过外部连接解决p,如果没有找到匹配项,pf必须具有该类别中的最低价格。这就是常见的解决方案。

您在此问题中增加的转折是您有另一个要过滤的属性。因此,考虑到价格最低的电影(或领带的电影),您需要评分最高的电影。r该技术是相同的,在类别和价格相等且评级较高的地方使用外连接。如果没有找到具有更高评级的此类电影,则f必须具有给定类别和价格的最高评级。

我将为您的问题添加一个标签,greatest-n-per-group以便您可以关注它并查看使用相同技术解决的其他 SQL 问题。

于 2009-11-05T20:06:47.320 回答