从行权概率的PostgreSQL表选择随机行(Select random row from a Po

2019-07-02 10:28发布

示例性输入:

SELECT * FROM test;
 id | percent   
----+----------
  1 | 50 
  2 | 35   
  3 | 15   
(3 rows)

你会怎么写这样的查询,上的时间平均50%我能得到该行与ID = 1,使用id = 2的时间排的35%,并与ID = 3时间排的15%?

我想是这样SELECT id FROM test ORDER BY p * random() DESC LIMIT 1 ,但它给错误的结果。 万次运行后我得到这样一个分布: {1=6293, 2=3302, 3=405}但我预计分布几乎是: {1=5000, 2=3500, 3=1500}

有任何想法吗?

Answer 1:

这应该做的伎俩:

WITH CTE AS (
    SELECT random() * (SELECT SUM(percent) FROM YOUR_TABLE) R
)
SELECT *
FROM (
    SELECT id, SUM(percent) OVER (ORDER BY id) S, R
    FROM YOUR_TABLE CROSS JOIN CTE
) Q
WHERE S >= R
ORDER BY id
LIMIT 1;

子查询Q提供了以下结果:

1  50
2  85
3  100

然后,我们简单地生成在范围[0,100的随机数),并挑选的第一行是在或超过该数( WHERE子句)。 我们使用公共表表达式( WITH ),以确保随机数只计算一次。

顺便说一句,在SELECT SUM(percent) FROM YOUR_TABLE可以让你有任何的权重percent -他们不要求一定要百分比(即添加,最多100个)。

[SQL小提琴]



Answer 2:

ORDER BY随机()^(1.0 / p)的

从由Efraimidis和Spirakis描述的算法。



Answer 3:

你提出的查询会出现工作; 看到这个SQLFiddle演示 。 它创建了错误的分布虽然; 见下文。

为了防止从PostgreSQL的优化子查询,我把它包在一个VOLATILE SQL函数。 PostgreSQL有没有办法知道你想让你的子查询一次外部查询的每一行运行,所以如果你不强制其挥发性,它会只执行一次。 另一种可能性-尽管一个查询规划可能在将来优化了-是让它看起来是一个相关子查询,这样黑客使用一个始终保持正确的where子句,像这样: http://sqlfiddle.com/# !12 / 3039b / 9

在猜测(更新你解释为什么没有工作之前)你的测试方法有不当之处,或者你想利用这个作为一个外部查询子查询,其中的PostgreSQL已经注意到它不是一个相关子查询和执行它只有一次,就像在这个例子 。 。

UPDATE:产生的分布不是你期待什么。 这里的问题是,你采取的多个样本偏斜分布random() ; 你需要一个样本。

该查询产生正确的分布( SQLFiddle ):

WITH random_weight(rw) AS (SELECT random() * (SELECT sum(percent) FROM test))
 SELECT id
FROM (                   
  SELECT 
    id,
    sum(percent) OVER (ORDER BY id),
    coalesce(sum(prev_percent) OVER (ORDER BY id),0) FROM (
      SELECT 
        id,
        percent,
        lag(percent) OVER () AS prev_percent
      FROM test
    ) x
) weighted_ids(id, weight_upper, weight_lower)
CROSS JOIN random_weight
WHERE rw BETWEEN weight_lower AND weight_upper;

性能,不用说了,太可怕了。 它使用两个嵌套组窗口。 我正在做的是:

  • 创建(ID,百分比,previous_percent)然后使用该创建用作范围括号权重两个运行总和; 然后
  • 取随机值,它扩展到权重的范围,然后拾取所述目标支架内具有权重值


Answer 4:

这里是东西给你一起玩:

select t1.id as id1
  , case when t2.id is null then 0 else t2.id end as id2
  , t1.percent as percent1
  , case when t2.percent is null then 0 else t2.percent end as percent2 
from "Test1" t1 
  left outer join "Test1" t2 on t1.id = t2.id + 1
where random() * 100 between t1.percent and 
  case when t2.percent is null then 0 else t2.percent end;

从本质上进行左外连接,使你有两列条款之间施加。

请注意,如果你得到你的表以正确的方式下令才有效。



Answer 5:

基于茨尔Dimitrijevic的回答,我写了这个查询,这可能会或可能不会使用总和更快percent使用分层窗口功能(不同于一个ROLLUP )。

WITH random AS (SELECT random() AS random)
SELECT id FROM (
    SELECT id, percent,
    SUM(percent) OVER (ORDER BY id) AS rank,
    SUM(percent) OVER () * random AS roll
    FROM test CROSS JOIN random
) t WHERE roll <= rank LIMIT 1

如果顺序并不重要, SUM(percent) OVER (ROWS UNBOUNDED PRECEDING) AS rank,因为它避免了对数据排序第一可能是可取的。

我也试过技工伟的回答( 在本文中,显然说明 ,这似乎在性能方面非常有前途的),但一些测试后, 分配似乎是关闭 :

SELECT id
FROM test
ORDER BY random() ^ (1.0/percent)
LIMIT 1


文章来源: Select random row from a PostgreSQL table with weighted row probabilities