SQL:随机选取一排,但考虑到权重(SQL : select one row randomly, b

2019-07-17 11:59发布

我使用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领域。 无论价值weighttable1 ,这就是我在多少个链接的记录创建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