我正在尝试优化查询以从DB获取特定国家/地区的用户的排名。目前看来效率很低。
我正在努力确定如何改进它。
这是当前的SQL:
Just :: a -> Maybe a
-- ((->) a) (Maybe a)
-- f (g a) for f ~ ((->) a) and g ~ Maybe
(fmap . fmap) :: (a -> b) -> f (g a ) -> f (g b)
-- Num x => ([x] -> x) -> f (g [x]) -> f (g x)
-- Num x => ([x] -> x) -> ([x] -> Maybe [x]) -> [x] -> Maybe x
-- ^ ^ ^
-- sum Just [1,2,3]
详细说明:
SELECT COUNT(*) + 1
FROM leaderboard lb, users u
WHERE u.country = 'United States'
AND lb.id = u.id
AND lb.score + 1 > (SELECT lb2.score
FROM leaderboard lb2
WHERE lb2.id = some_user_id);
表:
Users
是主键id
列已编入索引 country
表:
Leadeboard
是主键id
列已编入索引执行计划显示2个警告:
没有统计数据的列:[dbo]。[排行榜] .id(费用23%)
没有统计数据的列:[dbo]。[用户] .id(成本28%)
答案 0 :(得分:5)
我认为你正在寻找类似的东西:
SELECT u.id AS user_id, u.country, lu.score, RANK() OVER (ORDER BY lu.score DESC) AS rnk
FROM users u
INNER JOIN leaderboard lu ON u.id = lu.id
WHERE u.country = 'United States'
您可以在此处查看演示:http://rextester.com/KHM76159
答案 1 :(得分:3)
根据您的评论,如果我的第一个建议没有改善性能,那么我认为您唯一能做的就是:首先,确保您创建了所有需要的索引和统计和从WHERE
子句中删除计算,因为没有必要,并且使用JOIN 而不是在where子句中链接表(使用加入并没有提高绩效,但它是syntax is clearer and less ambiguous)
SELECT COUNT(*) + 1
FROM leaderboard lb INNER JOIN users u
ON lb.id = u.id
WHERE u.country = 'United States'
AND lb.score > (SELECT lb2.score
FROM leaderboard lb2
WHERE lb2.id = some_user_id)
请注意,如果得分为整数,则lb.score + 1> (SELECT lb2.score FROM leaderboard lb2 WHERE lb2.id = some_user_id)
相当于lb.score >= (SELECT lb2.score FROM leaderboard lb2 WHERE lb2.id = some_user_id)
,并且您不需要它。
我认为最好使用像RANK()
<强>子查询强>
SELECT * FROM (
SELECT u.id AS user_id, u.country, lb.score, RANK() OVER (ORDER BY lb.score DESC) AS rnk
FROM users u
INNER JOIN leaderboard lb ON u.id = lb.id
WHERE u.country = 'United States'
) T1 WHERE T1.user_id = some_user_id
公用表格式
WITH CTE_1 AS (
SELECT u.id AS user_id, u.country, lb.score, RANK() OVER (ORDER BY lb.score DESC) AS rnk
FROM users u
INNER JOIN leaderboard lb ON u.id = lb.id
WHERE u.country = 'United States'
) SELECT * FROM CTE_1 WHERE CTE_1.user_id = some_user_id
<强>参考强>
答案 2 :(得分:2)
尝试将score
存储在变量中,然后在JOIN
子句中使用它。
declare @score int = ( select top 1 score
FROM leaderboard
WHERE id = some_user_id
);
SELECT COUNT(*) + 1 as 'rank'
FROM leaderboard lb
JOIN users u
ON lb.id = u.id
AND lb.score > @score
AND u.country = 'United States';
答案 3 :(得分:0)
使其更具可读性/可测试性并使用CTE强制执行步骤
WITH scoreToRank AS (
SELECT score
FROM leaderboard
WHERE id = {some_user_id}
)
, usersInCountry AS (
SELECT id
FROM users
WHERE country = 'United States'
)
, countOfUsersWithGreaterScore AS (
SELECT COUNT(*) AS count
FROM leaderboard l
INNER JOIN usersInCountry u ON u.id = l.id
WHERE l.score > (SELECT score FROM scoreToRank)
)
SELECT count + 1 AS usersRank FROM countOfUsersWithGreaterScore
根据SQL版本和数据密度,使用countOfUsersWithGreaterScore / usersInCountry作为IN子句可能更有效
答案 4 :(得分:0)
SELECT COUNT(*) + 1
FROM leaderboard lb, users u, leaderboard lb2
WHERE u.country = 'United States'
AND lb.id = u.id
AND lb.score + 1 > lb2.score AND lb2.id = some_user_id
答案 5 :(得分:0)
我更喜欢连接子查询,下面的查询应该从你的问题中得到相同的结果。
SELECT COUNT(*) + 1
FROM leaderboard lb2
LEFT OUTER JOIN users u ON u.Id <> lb2.Id AND u.country = 'United States'
LEFT OUTER JOIN leaderboard lb ON lb.Id = u.Id
WHERE lb2.Id = some_user_id AND lb.score >= lb2.score
如果某些用户没有得分,则应检查空值,或者您可以更改加入顺序,在某些情况下甚至可能更好:
SELECT COUNT(*) + 1
FROM leaderboard lb2
LEFT OUTER JOIN leaderboard lb ON lb.Id <> lb2.Id AND lb.score >= lb2.score
LEFT OUTER JOIN users u ON u.Id = lb.Id
WHERE lb2.Id = some_user_id AND u.country = 'United States'
答案 6 :(得分:0)
这不是解决您的查询,而是解决了有用的整体问题。
我在国际比赛中遇到同样的问题,球队的表格可能会变得非常大。我从来没有能够获得一个SQL排名查询,以获得良好的用户体验(目标是80毫秒,并且查询比你的更复杂),所以最后决定使用redis server只是为了返回排名。
它提供了一个完全适合这个问题的排名函数。速度很快:1000万参赛者的桌子几毫米。
我仍然认为存储在SQL DB中的分数是真相的来源。 Redis不是ACID。它仅在RAM中保存其数据映像的快照。如果服务器出现故障,它将恢复为上一个快照。因此,redis和真相的来源可能会有所不同。
这在我的案件中没有任何问题,因为即时返回的队伍被认为是非正式的,等待法官的最终审查。由于快照重新启动而丢失的数据是“自我修复”。也就是说,如果我查询团队的级别并且它不在redis商店中,我会添加它然后重新查询。我还运行了每日同步工作以恢复完美的协议。我可以随时运行此同步以从头开始初始化新的redis。
该方案已经证明非常快速和稳健7年。它替换的实现使用了基于BerkeleyDB的自定义服务。那一个在过去的7年中运作良好。
另一点是,redis服务可以非常方便用于缓存等其他目的。
答案 7 :(得分:0)
也许尝试非规范化?对于排行榜表格中的每一行,请包含用户的国家/地区。
此外,使用CountryID而不是国家/地区名称,因为int比查询更快查询。 (您可以单独查找国家/地区的名称。)
然后你可以得到你不需要连接或子选择的搜索计数 - 它只是一个表上的选择(而且更快,因为你将使用int)。
答案 8 :(得分:-1)
尝试类似:
SELECT score FROM leaderboard WHERE id in
SELECT id FROM users WHERE country='United States' and id=some_user_id