示例性输入:
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}
有任何想法吗?
这应该做的伎俩:
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小提琴]
ORDER BY随机()^(1.0 / p)的
从由Efraimidis和Spirakis描述的算法。
你提出的查询会出现工作; 看到这个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)然后使用该创建用作范围括号权重两个运行总和; 然后
- 取随机值,它扩展到权重的范围,然后拾取所述目标支架内具有权重值
这里是东西给你一起玩:
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;
从本质上进行左外连接,使你有两列条款之间施加。
请注意,如果你得到你的表以正确的方式下令才有效。
基于茨尔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