【发布时间】:2015-01-13 02:39:32
【问题描述】:
用例是,我有一个表 products 和 user_match_product。对于特定用户,我想随机选择 X 个该用户没有匹配的产品。
这样做的天真方法是制作类似的东西
SELECT * FROM products WHERE id NOT IN (SELECT p_id FROM user_match_product WHERE u_id = 123) ORDER BY random() LIMIT X
但是当拥有数百万行时,这将成为性能瓶颈。
我想到了一些可能的解决方案,我现在将在此介绍。我很想听听您针对该问题的解决方案或有关我的解决方案的建议。
解决方案 1:相信随机性
基于产品ID单调递增的事实,可以乐观地生成X*C随机数R_i,其中i在1和X*C之间,在[min_id, max_id]范围内,以及希望像下面这样的 select 会返回 X 个元素。
SELECT * FROM products p1 WHERE p1.id IN (R_1, R_2, ..., R_XC) AND NOT EXISTS (SELECT * FROM user_match_product WHERE u_id = 123 AND p_id = p1.id) LIMIT X
优势
- 如果随机数生成器很好,这可能会在 O(1) 内很好地工作
- 新老产品被选中的概率相同
缺点
- 如果匹配数接近产品数,则冲突概率可能非常高。
解决方案 2:逐块 PRNG
可以为域[START, END] 创建一个置换函数permutate(seed, start, end, value),使用seed 表示随机性。在时间t0 用户A 有0 匹配的产品并观察到E0 产品存在。用户A 在t0 的第一个块用于域[1, E0]。用户记得一个计数器C,最初是0。
要选择 X 产品,用户 A 首先必须创建排列 P_i like
P_i = permutate(seed, START, END, C + i)
该功能必须满足以下条件。
-
permutate(seed, start, end, value)是[start, end]的元素 -
value是[start, end]的元素
以下查询将返回 X 个非重复元素。
SELECT * FROM products WHERE id IN (P_1, ..., P_X)
当C到达END时,使用END + 1作为新的START分配下一个块,当前产品计数E1作为新的END。 seed 和 C 保持不变。
优势
- 不可能发生冲突
- 保证O(1)
缺点
- 必须先完成当前块,然后才能选择新产品
【问题讨论】:
-
您使用的是哪个 DBMS?后格雷斯?甲骨文?
-
Postgres 但我猜这没关系? Oracle 是否提供任何特殊功能来帮助我解决这个问题?
标签: sql database algorithm math random