我有一个系统,用户可以通过不同类型的贡献获得 1 个或多个积分。它们存储在 2 个表中:
CREATE TABLE user_contribution_types (
type_id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
title VARCHAR(255) NOT NULL,
credits DECIMAL(5,2) UNSIGNED NOT NULL,
valid TINYINT(1) UNSIGNED NOT NULL DEFAULT 1,
PRIMARY KEY (type_id)
);
CREATE TABLE user_contributions (
user_id INTEGER UNSIGNED NOT NULL,
type_id INTEGER UNSIGNED NOT NULL,
create_date DATETIME NOT NULL,
valid TINYINT(1) UNSIGNED NOT NULL DEFAULT 1,
FOREIGN KEY (user_id)
REFERENCES users(user_id),
FOREIGN KEY (type_id)
REFERENCES user_contribution_types(type_id)
);
我可以通过以下方式选择自特定日期以来获得的总积分:
SELECT SUM(credits) AS total
FROM user_contribution_types AS a
JOIN user_contributions AS b ON a.type_id = b.type_id
WHERE b.create_date >= '2017-05-01 00:00:00'
AND a.valid = TRUE
AND b.valid = TRUE
同样,我可以包含 b.user_id
的匹配项来查找该特定用户的总积分。
我想要做的是将获得的每个积分视为赠品,并从总数中选择 3 个随机(唯一)user_id
。因此,如果一名用户获得了 26 个积分,他们将有 26 次获胜机会。
如何使用 SQL 来完成此操作,还是在应用程序级别执行此操作更有意义?我更喜欢一个尽可能接近真正随机的解决方案。
最佳答案
您可以通过计算累积分布并使用rand()
来选择一个用户:
SELECT uc.*
FROM (SELECT uc.user_id, (@t := @t + total) as running_total
FROM (SELECT uc.user_id, SUM(credits) as total
FROM user_contribution_types ct JOIN
user_contributions c
ON ct.type_id = c.type_id
WHERE c.create_date >= '2017-05-01' AND ct.valid = TRUE AND c.valid = TRUE
GROUP BY uc.user_id
) uc CROSS JOIN
(SELECT @t := 0) params
ORDER BY rand()
) uc
WHERE rand()*@t BETWEEN (running_total - total) AND running_total;
如果 rand()
恰好位于边界上,则返回两个值的可能性很小。对于您的目的来说,这不是问题;您只需添加限制 1
即可。
要将其扩展到多行,您只需将 WHERE
子句修改为:
WHERE rand()*@t BETWEEN (running_total - total) AND running_total OR
rand()*@t BETWEEN (running_total - total) AND running_total OR
rand()*@t BETWEEN (running_total - total) AND running_total
问题是所有结果值可能都是相同的结果。
您可以随机选择三个以上的值。我倾向于选择一个更大的数字,例如 9:
WHERE 0.1*@t BETWEEN (running_total - total) AND running_total OR
0.2*@t BETWEEN (running_total - total) AND running_total OR
0.3*@t BETWEEN (running_total - total) AND running_total OR
. . .
ORDER BY rand() -- redundant, but why not?
LIMIT 3
或更简单地说:
WHERE FLOOR( 10*(running_total - total)/@t)) <> FLOOR( 10*running_total/@t)
ORDER BY rand()
LIMIT 3
这更容易,因为您可以更改 10
并沿累积分布测试任意数量的等距点。
关于mysql - 如何从相乘结果中选择随机唯一值,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43867354/