我使用MySQL。 我有一个表,看起来像这样:
id: primary key
content: varchar
weight: int
我想要做的是随机选择从该表中的一行,但考虑到重量。 举例来说,如果我有3行:
id, content, weight
1, "some content", 60
2, "other content", 40
3, "something", 100
第一行具有被选择的30%的机会,第二行具有被选择的20%的机会,而第三排具有被选择的50%的机会。
有没有办法做到这一点 ? 如果我要执行2个或3查询这不是一个问题。
Answer 1:
我曾尝试面包车的解决方案,虽然它的工作原理,它不是快速。
我的解决办法
我正在解决这一问题的方法是通过保持一个独立的,挂表的权重。 基本的表结构与此类似:
CREATE TABLE `table1` (
`id` int(11) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`name` varchar(100),
`weight` tinyint(4) NOT NULL DEFAULT '1',
);
CREATE TABLE `table1_weight` (
`id` bigint(20) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`table1_id` int(11) NOT NULL
);
如果我有一个记录table1
权重为3,那么我创建3个记录table1_weight
,挂table1
通过table1_id
领域。 无论价值weight
在table1
,这就是我在多少个链接的记录创建table1_weight
。
测试
在有976条记录中的数据集table1
为2031,总重量和因此2031记录table1_weight
,我跑了以下两个sql语句:
1)面包车的解决方案的一个版本
SELECT t.*
FROM table1 t
INNER JOIN
( SELECT t.id,
SUM(tt.weight) AS cum_weight
FROM table1 t
INNER JOIN table1 tt ON tt.id <= t.id
GROUP BY t.id) tc ON tc.id = t.id,
( SELECT SUM(weight) AS total_weight
FROM table1) tt,
( SELECT RAND() AS rnd) r
WHERE r.rnd * tt.total_weight <= tc.cum_weight
ORDER BY t.id ASC
LIMIT 1
2)加入到二级表对于加权
SELECT t.*
FROM table1 t
INNER JOIN table1_weight w
ON w.table1_id = t.id
ORDER BY RAND()
LIMIT 1
SQL 1采取一致0.4秒。
SQL 2仅需数秒0.01和0.02之间。
结论
如果随机,加权记录的选择的速度是不是一个问题,然后由van建议的单表SQL是好的,没有保持一个单独的表的开销。
如果,在我的情况下,很短的时间选择是至关重要的,那么我会推荐这两个表的方法。
PS这是我第一次的StackOverflow职位,它是我花时间,所以希望有人会发现它的帮助!
Answer 2:
这部作品在MSSQL,我相信,它应该是可以改变的情侣关键字,使其在MySQL的工作,以及(甚至更好):
SELECT TOP 1 t.*
FROM @Table t
INNER JOIN (SELECT t.id, sum(tt.weight) AS cum_weight
FROM @Table t
INNER JOIN @Table tt ON tt.id <= t.id
GROUP BY t.id) tc
ON tc.id = t.id,
(SELECT SUM(weight) AS total_weight FROM @Table) tt,
(SELECT RAND() AS rnd) r
WHERE r.rnd * tt.total_weight <= tc.cum_weight
ORDER BY t.id ASC
我们的想法是具有每行(子选择-1)的累积重量,然后找到的跨区RAND()在此累积范围的位置。
Answer 3:
一种简单的方法(避免连接或子)是由0和1之间的随机数,只是乘以权重以产生临时权重排序:
SELECT t.*, RAND() * t.weight AS w
FROM table t
ORDER BY w DESC
LIMIT 1
要理解这一点,考虑RAND() * 2x
会比更大的值RAND() * x
约两的时间三分之二。 因此,随着时间的推移各行应与这正比于它的相对权重的频率选择(例如,与100重量一行将被更频繁地选择的约100倍,比一排具有重量1等)。
更新:这个方法其实并没有产生正确的分布 ,所以现在不使用它! (见下面的评论)。 我觉得应该还有类似将工作上面的一个简单的方法,但现在下面的更复杂的方法,包括加入,可能会更好。 我要离开这个答案,因为:(1)有相关的讨论在下面的意见,以及(b)如果/当我得到一个机会,我会尝试修复它。
Answer 4:
这一个似乎工作,但我不知道它背后的数学。
SELECT RAND() / t.weight AS w, t.*
FROM table t
WHERE t.weight > 0
ORDER BY 1
LIMIT 1
我在它工作的原因的猜测是升序寻找最小的结果,并通过更高的权重随机结果被聚集更紧密地接近零的权重划分。
我用209000次的查询进行了测试(实际上在PostgreSQL中的相同算法)超过3000行和重量表现出来正确的。
我的输入数据:
select count(*),weight from t group by weight
count | weight
-------+--------
1000 | 99
1000 | 10
1000 | 100
(3 rows)
我的结果:
jasen=# with g as ( select generate_series(1,209000) as i )
,r as (select ( select t.weight as w
FROM t
WHERE t.weight > 0
ORDER BY ( random() / t.weight ) + (g.i*0) LIMIT 1 ) from g)
select r.w, count(*), r.w*1000 as expect from r group by r.w;
w | count | expect
-----+-------+--------
99 | 98978 | 99000
10 | 10070 | 10000
100 | 99952 | 100000
(3 rows)
的+(gi*0)
对运算结果没有影响,但外部基准必须向强制规划重新评估的子选择用于每个中制作在209K输入行的g
Answer 5:
我认为最简单的其实是使用加权水库取样:
SELECT
id,
-LOG(RAND()) / weight AS priority
FROM
your_table
ORDER BY priority
LIMIT 1;
这是一个伟大的方法,让你选择次M,其中被选择的概率为每个元素正比于它的重量N个元素的。 当你碰巧只想要一个元素,它的作品一样好。 该方法描述于本文中 。 需要注意的是,他们选择POW(RAND(),1 /重量),这相当于选择-log(RAND())/重量的最小值最大的值。
Answer 6:
也许这一个:
SELECT * FROM <Table> T JOIN (SELECT FLOOR(MAX(ID)*RAND()) AS ID FROM <Table> ) AS x ON T.ID >= x.ID LIMIT 1;
或者这一个:
SELECT * FROM tablename
WHERE somefield='something'
ORDER BY RAND() LIMIT 1
Answer 7:
我不记得如何RND()在MySQL,但在这里工作示例为MSSQL:
SELECT TOP(1) (weight +RAND ()) r, id, content, weight FROM Table
ORDER BY 1 DESC
如果TOP(1)不适用你只取总从结果集的第一条记录。
文章来源: SQL : select one row randomly, but taking into account a weight