8wDlpd.png
8wDFp9.png
8wDEOx.png
8wDMfH.png
8wDKte.png

如何考虑权重并随机选择一行?

JsonKody 1月前

77 0

我有一张如下所示的表:id:主键内容:varcharweight:int 我想要做的是从该表中随机选择一行,但要考虑权重。例如,我...

我有一张如下所示的表格:

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 个查询,那不是问题。

帖子版权声明 1、本帖标题:如何考虑权重并随机选择一行?
    本站网址:http://xjnalaquan.com/
2、本网站的资源部分来源于网络,如有侵权,请联系站长进行删除处理。
3、会员发帖仅代表会员个人观点,并不代表本站赞同其观点和对其真实性负责。
4、本站一律禁止以任何方式发布或转载任何违法的相关信息,访客发现请向站长举报
5、站长邮箱:yeweds@126.com 除非注明,本帖由JsonKody在本站《mysql》版块原创发布, 转载请注明出处!
最新回复 (0)
  • 我认为最简单的方法实际上是使用加权水库采样:

    SELECT
      id,
      -LOG(RAND()) / weight AS priority
    FROM
      your_table
    ORDER BY priority
    LIMIT 1;
    

    这是一种很棒的方法,可让您从 N 个元素中选择 M 个,其中每个元素被选中的概率与其权重成正比。当您恰好只想要一个元素时,它同样有效。本文介绍了该方法请注意,它们选择 POW(RAND(), 1/weight) 的最大值,这相当于选择 -LOG(RAND()) / weight 的最小值。

  • 这真是一个很棒的答案!谢谢!我只想补充一点:写 log(1-rand()) 来避免 log(0) 不是更优雅吗,因为随机值可能在 [0,1[ 中(但未检查)?

  • 这看起来是个不错的方法,但分布可能会非常不均匀。我尝试了几行权重,所有权重都是 67 或 33(即大约 2/3 或 1/3),在我的例子中,所有选择的行都有更高的权重。不知道为什么。

  • 这在 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
    

    这个想法是对每一行(subselect-1)有一个累积权重,然后在这个累积范围内找到跨越 RAND() 的位置。

  • 我尝试过 van 的解决方案,虽然有效,但速度并不快。

    我的解决方案

    我解决这个问题的方法是维护一个单独的链接表来计算权重。基本表结构类似于:

    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
    );
    

    权重为 3 的 table1 记录 table1_weight 通过 table1 链接到 table1_id 。无论 中的 值 weight table1 多少,这就是我在 中创建的链接记录数 table1_weight .

    测试

    在一个包含 976 条记录、 table1 总权重为 2031、因此有 2031 条记录的数据集中 table1_weight ,我运行了以下两个 SQL:

    1. p4

      SELECT t.*FROM table1 tINNER 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) rWHERE r.rnd * tt.total_weight <= tc.cum_weightORDER BY t.id ASCLIMIT 1
    2. p5

    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 就可以了,并且没有维护单独表的开销。

    如果像我的情况一样,较短的选择时间至关重要,那么我会建议使用双表方法。

  • 一种简单的方法(避免连接或子查询)是将权重乘以 0 到 1 之间的随机数,以产生一个临时权重进行排序:

    SELECT t.*, RAND() * t.weight AS w 
    FROM table t 
    ORDER BY w DESC
    LIMIT 1
    

    要理解这一点,请考虑RAND() * 2x值在约三分之二的情况下会大于RAND() * x 。因此,随着时间的推移,每行的选择频率应与其相对权重成比例(例如,权重为 100 的行的选择频率将比权重为 1 的行高出约 100 倍,等等)。

    更新:此方法实际上不会产生正确的分布 ,因此目前 不要使用它! (请参阅下面的评论)。我认为应该仍然有一个类似于上面的简单方法可以工作,但目前,下面涉及连接的更复杂的方法可能会更好。我保留这个答案是因为:(a)下面的评论中有相关讨论,(b)如果/当我有机会时,我会尝试修复它。

  • 当您从较少的行数(最好 2 行)中选择时,这种方法效果很好。我需要从 50 行中随机选择。1 行的权重为 32,1 行的权重为 3,48 行的权重为 1,总权重为 83。所以我的 32 行应该有 38.6% 的几率被选中,但使用这种方法,它被选中的几率比所有权重为 1 的行高出 32%。有没有办法把总权重考虑进去?谢谢!!

  • 这对你的案例来说行不通吗?在你的案例中,权重为 32 的行被选中的概率应该是 32/83(0.386,即 38.6%)。权重为 1 的行被选中的概率应该是 1/83(0.012,即 1.2%)。但是由于 32/83 = 32 * 1/83,因此权重为 32 的行被选中的概率仍然是权重为 1 的行被选中的概率的 32 倍!

  • 我可能在脚本中犯了一个错误,但我有 30 多次权重为 32 的行,偶尔也会有其他行。它被选中的次数比其他所有行多 32 次。我最终创建了一个包含总权重的临时表,用它来表示权重的百分比 (SELECT id FROM near50, total_weight ORDER BY Random()*(1/(WEIGHT*100/total_weight.weight)) LIMIT 1)。

  • 引用 11

    我明白你的意思。当然,它被选中的概率应该是权重为 1 的任何其他元素的 32 倍。我的意思是,在我的脚本中,它被选中的次数是 32 次,其他所有元素都一致。在 1000 次测试中,我选中的元素是权重为 32 的元素的 960 倍,其余元素的 40 倍。我应该选中它的次数约为 386 次。我的评论是基于我的观察。

  • 很确定这不会给你预期的分布。考虑 3 行,权重分别为 80、10 和 10。我们预计第一行被选中的概率为 80%,其他行被选中的概率相同,即 20%。如果 rand()*80 > 10,那么我们必须选择第一行。如果 rand()*80 在 [0, 80] 之间均匀分布,超过 10 的几率为 69/81,即 85%。它将被过度代表。即使我在这里犯了一些差错。

  • 这个似乎有效,但我不确定其背后的数学原理。

    SELECT RAND() / t.weight AS w, t.* 
    FROM table t 
    WHERE t.weight > 0
    ORDER BY 1
    LIMIT 1
    

    我猜测它起作用的原因是升序寻找最小的结果,并且通过除以更高权重的权重,随机结果更紧密地聚集在零附近。

    我用 3000 行以上的 209000 个查询对其进行了测试(实际上是 postgresql 中的相同算法),权重表示结果正确。

    我的输入数据:

    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)
    

    没有 +(g.i*0) 影响,但需要外部引用来强制规划器重新评估在中产生的 209K 个输入行中的每一个的子选择 g

  • 也许是这个:

    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
    
返回
作者最近主题: