【问题标题】:Biased random in SQL?SQL中的随机偏差?
【发布时间】:2011-05-03 02:24:52
【问题描述】:

我的数据库中有一些条目,在我的情况下是带有评分和受欢迎程度以及其他因素的视频。在所有这些因素中,我计算了一个或多个似然因素,即提升因素。

所以我基本上有字段 ID 和 BOOST。boost 的计算方式是,结果是一个整数,表示比较应该命中此条目的频率百分比。

ID  Boost
1   1
2   2
3   7

因此,如果我无限期地运行我的随机函数,我最终会在 ID 1 上获得 X 次命中,在 ID 2 上是两倍,在 ID 3 上是 7 倍。

所以每次点击都应该是随机的,但概率为(boost / sum of boosts)。所以这个例子中 ID 3 的概率应该是 0.7(因为总和是 10。为了简单起见,我选择了这些值)。

我想到了类似以下查询:

SELECT id FROM table WHERE CEIL(RAND() * MAX(boost)) >= boost ORDER BY rand();

很遗憾,在考虑了表格中的以下条目之后,这不起作用:

ID  Boost
1   1
2   2

它将有 50/50 的机会,只有第二个或两个元素可供随机选择。

所以 0.5 命中到第二个元素 并且 0.5 命中进入随机选择的(第二个和第一个)元素,因此每个 0.25。 所以我们最终得到一个 0.25/0.75 的比率,但它应该是 0.33/0.66

我需要一些修改或新的方法来以良好的性能做到这一点。

我还考虑过累积存储 boost 字段,所以我只从 (0-sum()) 进行范围查询,但是如果我更改它或开发一些交换算法或开发一些交换算法或一些东西......但这真的不是优雅的东西。

插入/更新和选择都应该很快!

你有解决这个问题的办法吗?

要考虑的最佳用例可能是广告投放。 “请以给定的概率选择一个随机广告”......但是我需要它用于另一个目的,但只是为了给你最后一张图片它应该做什么。

编辑:

感谢肯斯的回答,我想到了以下方法:

  1. 从 0-sum(distinct boost) 中计算一个随机值

    SET @randval = (select ceil(rand() * sum(DISTINCT boost)) from test);

  2. 从所有加起来超过随机值的不同提升因子中选择提升因子

那么在我们的第一个示例中,1 的概率为 0.1,2 的概率为 0.2,7 的概率为 0.7。

  1. 现在从具有此提升因子的所有条目中随机选择一个条目

问题:因为具有一次提升的条目数总是不同的。例如,如果只有 1 个增强条目,我会在 10 个调用中的 1 个中得到它,但是如果有 100 万个有 7 个,则它们中的每一个都几乎不会返回...... 所以这行不通:(试图完善它。

我必须以某种方式将条目数包含在这个提升因子中......但我不知何故被困在了那个......

【问题讨论】:

  • 好吧,我被卡住了,请帮帮我:D 我还考虑过选择一个子集,将概率除以 rand 值,但结果也不好......

标签: sql mysql random statistics


【解决方案1】:

我敢于用两个查询提出直接的解决方案,使用累积提升计算。

首先,选择 boosts 的总和,并生成一个介于 0 和 boost sum 之间的数字:

select ceil(rand() * sum(boost)) from table;

这个值应该存储为一个变量,我们称之为{random_number}

然后,选择表行,计算 boosts 的累积总和,找到第一行,其累积 boost 大于 {random number}:

SET @cumulative_boost=0;
SELECT
  id,
  @cumulative_boost:=(@cumulative_boost + boost) AS cumulative_boost,
FROM
  table
WHERE
  cumulative_boost >= {random_number}
ORDER BY id
LIMIT 1;

【讨论】:

  • 我喜欢这种方法,但整个加起来正是我想要避免的事情。但是+1,因为您可能已将我指向正确的方向。我只是在研究一个解决方案,我只是将提升值相加,然后确切地知道要随机选择什么提升值。还没有完全考虑清楚,但我认为它会做到。
  • 如果你在解决问题的时候在这里发布你的解决方案会很好,因为这个问题看起来很有趣。
  • 不幸的是没有解决,但我正在努力改进它。一定有办法解决这个问题!
【解决方案2】:

您需要为每行生成一个随机数并对其加权。

在这种情况下,RAND(CHECKSUM(NEWID())) 绕过RAND 的“每个查询”评估。然后只需将其乘以 boost 并 ORDER BY 结果 DESC。 SUM..OVER 为您提供全面提升

DECLARE @sample TABLE (id int, boost int)

INSERT @sample VALUES (1, 1), (2, 2), (3, 7)

SELECT
    RAND(CHECKSUM(NEWID())) * boost  AS weighted,
    SUM(boost) OVER () AS boostcount,
    id
FROM
    @sample
GROUP BY
    id, boost
ORDER BY
    weighted DESC

如果你有非常不同的提升值(我想你提到过),我也会考虑使用 LOG(以 e 为底)来平滑分布。

最后,ORDER BY NEWID() 是一个不考虑提升的随机性。播种 RAND 是有用的,但不是单独播种。

此示例放在 SQL Server 2008 上,顺便说一句

【讨论】:

  • +1 看起来很有希望,必须将其移植到 mysql 并再次考虑 b4 接受 :)
  • @Joe Hopfgartner:非常接近标准 SQL,所以希望很简单
【解决方案3】:

我的问题也是类似的:每个人在最终抽签中都有计算出的票数。如果你有更多的票,那么你就有更高的机会赢得“彩票”。

由于我不相信任何找到的结果 rand() * multiplier 或网络上带有 -log(rand()) 的结果,我想实施我自己的简单解决方案。

我所做的,在你的情况下看起来有点像这样:

(SELECT id, boost FROM foo) AS values
INNER JOIN (
    SELECT id % 100 + 1 AS counter 
    FROM user 
    GROUP BY counter) AS numbers ON numbers.counter <= values.boost
ORDER BY RAND()

由于我不必经常运行它,所以我并不真正关心未来的性能,目前它对我来说很快。

在我使用这个查询之前,我检查了两件事:

  1. boost的最大数量小于数量查询中返回的最大值
  2. 内部查询返回 1..100 之间的所有数字。它可能与您的餐桌无关!

因为我在 1..100 之间有所有不同的数字,所以加入 numbers.counter &lt;= values.boost 意味着如果一行的提升为 2,它最终会在最终结果中重复。如果一行的提升为 100,它将最终进入决赛 100 次。或者换句话说。如果提升的总和是 4212(在我的情况下),那么最终集合中将有 4212 行。

最后我让MySql随机排序。

编辑:要使内部查询正常工作,请确保使用大表,或确保 id 不跳过任何数字。更好的是,可能更快一点,您甚至可以创建一个临时表,其中包含 1..n 之间的所有数字。然后你可以简单地使用INNER JOIN numbers ON numbers.id &lt;= values.boost

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-02-19
    • 1970-01-01
    • 2016-11-22
    • 1970-01-01
    • 1970-01-01
    • 2018-12-31
    • 2010-11-28
    • 1970-01-01
    相关资源
    最近更新 更多