【问题标题】:Select a random row but with odds选择一个随机的行,但有几率
【发布时间】:2011-03-24 17:18:54
【问题描述】:

我有一个行数据集,每行的“赔率”数字介于 1 到 100 之间。我希望以最有效的方式进行。几率加起来不一定是 100。

我有一些想法。

一) 选择整个数据集,然后将所有几率相加并生成一个介于 1 和该数字之间的随机数。然后循环遍历数据集,从数字中减去几率,直到它为 0。

我希望尽量减少对数据库的影响,所以我考虑是否只能选择我需要的行。

b)

SELECT * FROM table WHERE (100*RAND()) < odds

我考虑过LIMIT 0,1

但是如果物品具有相同的概率,则只会返回其中之一

或者,取整个数据集并从中选择一个随机数据集...但随后赔率会受到影响,因为它变成一个有赔率的随机数,然后是一个没有赔率的随机数,因此赔率变得有利于更高的赔率(偶数更是如此)。

我想我可以 order by oddsASC 然后获取整个数据集,然后使用 PHP 从行中随机抽取与第一条记录相同的概率(最低)。

似乎是一个笨拙的解决方案。

有人有更好的解决方案吗?如果不是,以上哪一项最好?

【问题讨论】:

标签: php mysql random probability


【解决方案1】:

做一些前期工作,在表格中添加一些有助于选择的列。例如假设你有这些行

 X  2  
 Y  3
 Z  1

我们添加一些累积值

 Key Odds Start  End 
 X    2     0     1      // range 0->1, 2 values == odds
 Y    3     2     4      // range 2->4, 3 values == odds
 Z    1     5     5      // range 5->5, 1 value == odds

开始和结束的选择如下。第一行从零开始。后续行的开始比前一个结束多一个。结束是(开始 + 赔率 - 1)。

现在在 0 到 Max(End) 范围内选择一个随机数 R

Select * from T where R >= T.Start and R <= T.End

如果数据库足够聪明,我们也许可以使用

 Select * from T where R >= T.Start and R <= (T.Start + T.Odds - 1)

我推测拥有带有索引的 End 列可能会提供更好的性能。此外,Max(End) 可能会隐藏在某个地方,并在必要时由触发器更新。

显然在更新开始/结束时有些麻烦。如果有的话,这可能还不错

  • 表格内容稳定
  • 或插入以某种方式自然排序,因此每个新行都从旧的最高行继续。

【讨论】:

  • 这是一个有趣的解决方案。它可以通过使用 'between' 而不是 '>= 和
  • 如果其中一行被删除,这也需要更新多条记录。我认为“插入”不是问题,因为顺序无关紧要。这会产生加权结果。这是 OP 想要的吗?
  • 是的,我正在寻找加权结果。
【解决方案2】:

如果你拿了你的代码,并添加了 ORDER BY RAND()LIMIT 1 怎么办?

SELECT * FROM table WHERE (100*RAND()) < odds ORDER BY RAND() LIMIT 1

这样,即使你有多个相同的概率,它总是会随机排序回来,然后你只取第一个条目。

【讨论】:

  • 随着行数的增加,这个查询的开销也会增加。
  • 这也是随机加权+随机....这会改变权重。如果 WHERE RAND 返回的数字较低,例如 0.001,则将返回重量最低的项目 - 以及所有其他记录。然后它有 1 in(总记录)被退回。如果 WHERE rand 是 0.9,它只会返回几条记录,因此权重变得更加倾向于更高的权重
  • @Pablo,我同意。应该删除 WHERE 子句。
  • 不,我正在寻找加权,但这是加权+随机。这会扭曲权重。
【解决方案3】:
select * from table 
where id between 1 and 100 and ((id % 2) <> 0) 
order by NewId() 

【讨论】:

    【解决方案4】:

    嗯。不完全清楚你想要什么结果,如果这有点疯狂,请耐心等待。话虽如此,怎么样:

    创建一个新表。该表为固定数据表,如下所示:

    Odds
    ====
       1
       2
       2
       3
       3
       3
       4
       4
       4
       4
    etc, 
    etc.
    

    然后从您的数据集连接到赔率列上的该表。对于表格中的每一行,您将获得与该行的给定几率一样多的行。

    然后随机选择一组。

    【讨论】:

      【解决方案5】:

      如果您在赔率列上有一个索引和一个主键,这将非常有效:

      SELECT id, odds FROM table WHERE odds > 0
      

      数据库甚至不必从表中读取,它会从赔率索引中获取所需的一切。

      然后,您将在 1 和返回的行数之间选择一个随机值。

      然后从返回的行数组中选择该行。

      然后,最后,选择整个目标行:

      SELECT * FROM table WHERE id = ?
      

      这确保了所有行之间的平均分布与赔率值。


      或者,将赔率放在不同的表中,使用自动增量主键。

      Odds
      ID     odds
      1      4
      2      9
      3      56
      4      12
      

      在主表中存储ID外键而不是赔率值,并对其进行索引。

      首先,获取最大值。这永远不会触及数据库。它使用索引:

      SELECT MAX(ID) FROM Odds
      

      获取一个介于 1 和最大值之间的随机值。

      然后选择记录。

      SELECT * FROM table
      JOIN Odds ON Odds.ID = table.ID
      WHERE Odds.ID >= ?
      LIMIT 1
      

      如果您倾向于删除 Odds 值或回滚插入以保持分布均匀,这将需要一些维护。

      SQL Antipatterns这本书里有一整章是关于随机选择的。

      【讨论】:

        【解决方案6】:

        我没有尝试过,但可能是这样的(带有 ? 一个从 0 到 SUM(odds) - 1 的随机数)?

        SET @prob := 0;
        
        SELECT
          T.*,
          (@prob := @prob + T.odds) AS prob
        FROM table T
        WHERE prob > ?
        LIMIT 1
        

        这与您的想法 a) 基本相同,但完全在一个(好吧,如果您计算变量设置,从技术上讲是两个)SQL 命令内。

        【讨论】:

        • 这将被加权到较高的值。例如,如果您有值 1 和 5,则只有 rand 0 等于 1,但 rand 2 到 4 等于 5。
        • @Marcus:我不明白你的意思。如果您的值为 1 和 5,prob 将分别为 1 和 6;如果随机数为 0(6 次中有 1 次机会),它将选择第一行,如果是 1-5(6 次中有 5 次机会),它将选择第二行。相反,如果你有 5 和 1,prob 将是 5 和 6;因此,随机数 0-4 将选择第一行(6 次中有 5 次机会),而 5 将选择第二行(6 次中有 1 次机会),正如所愿。偏差在哪里?
        • 我没有看到 OP 在哪里说他们想要加权随机选择。如果这两个值之一的赔率是 5:1,那将是有偏差的。你不这么认为吗?
        • 啊,我误解了你的抱怨。是的,显然它是加权的——这就是我理解这个问题的意思。具体来说,尝试重新阅读标题(“随机行但有几率”)和他的“a)”尝试解决方案(这正是我所写的,但在 SQL 中)。
        • 我可能是错的,但我认为 OP 抱怨解决方案无效,因为它是加权的。
        【解决方案7】:

        适用于 O(log(n)) 更新的通用解决方案是这样的:

        • 将对象存储为(平衡)树的叶子。
        • 在每个分支节点,存储其下所有对象的权重。
        • 添加、删除或修改节点时,更新其父节点的权重。

        然后选择一个介于 0 和(总重量 - 1)之间的数字并沿着树向下导航,直到找到正确的对象。

        由于您不关心树中事物的顺序,您可以将它们存储为一个包含 N 个指针和 N-1 个数字的数组。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2023-04-03
          • 1970-01-01
          • 2020-07-26
          • 2011-10-20
          • 1970-01-01
          • 2013-03-11
          • 1970-01-01
          • 2014-06-05
          相关资源
          最近更新 更多