3 回答
首先让我们陈述问题。我们想要每个类别中评分最高的所有电影。然后,其中,我们想要最低的价格。
首先获得最高评分
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
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
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
,如果没有找到匹配项,p
则f
必须具有该类别中的最低价格。这就是常见的解决方案。
您在此问题中增加的转折是您有另一个要过滤的属性。因此,考虑到价格最低的电影(或领带的电影),您需要评分最高的电影。r
该技术是相同的,在类别和价格相等且评级较高的地方使用外连接。如果没有找到具有更高评级的此类电影,则f
必须具有给定类别和价格的最高评级。
我将为您的问题添加一个标签,greatest-n-per-group
以便您可以关注它并查看使用相同技术解决的其他 SQL 问题。