从表中返回随机,无重复的行 - PostgreSQL

时间:2014-07-20 15:26:53

标签: sql postgresql unique session-state random-sample

我有一个有趣的问题,我正在寻找一些指导。 我有一个表“图像”,其中包含数千行。 我希望能够一次返回一组限制为50的随机行。

客户方面,我有一个初始的GetImages()方法,它最初将返回50个“随机”图像(如果有那么多)。当用户滚动它们并达到一定数量(大约40+)时,另一个函数将触发 - GetMoreImages()。

问题是我不知道如何在没有返回相同结果的风险的情况下检索更多图像。

例如,如果总共有60个图像,我希望GetMoreImages()调用仅返回剩余的10个图像。

我觉得我还应该提到我的Id表是非连续的,因为我正在使用Instagram方法(http://instagram-engineering.tumblr.com/post/10853187575/sharding-ids-at-instagram) 这让我在每一行id之间留下了很大的差距。

我可以尝试的一种方法是传递我已经拥有的所有图像的id,但如果用户滚动浏览数千张图像,那将会变得笨拙。

我想另一种方法可能是在每个用户的应用服务器上存储缓存的“随机”值集,但我也不太喜欢这个。

如果有任何最佳实践可以指导我,我们将不胜感激。

2 个答案:

答案 0 :(得分:4)

您可以使用以下查询获取随机图片:

select *
from images
order by random()
limit 50;

我不是100%认为以下内容可行,但可能会如此。你想要的是一个随机数生成器,它可以重现相同的值。为此,请使用setseed()。所以,你可以这样做:

with t as (
      select setseed(1)
     )
select *
from images cross join t
order by random()
limit 50;

然后您可以获得后续值:

with t as (
      select setseed(1)
     ) 
select *
from images cross join t
order by random()
limit 50;

问题是在后续调用中是否以完全相同的顺序调用random()。您可以通过以下方式强制执行此操作:

with t as (
      select setseed(1)
     ),
     i as (
      select i.*, random() as rand
      from images i cross join t
     )
select *
from i
order by i.rand
limit 50;

但是,这仍然假设对同一个表的多次调用将采用相同的顺序。 然后,您可以使用limit 10 offset 50运行相同的查询,依此类推。

您可以使用计数器,与当前日期时间相关的函数或仅使用随机数生成器来更改每次调用的种子值。

编辑:

我的常用方法是使用伪随机数生成器。我只需要使用相对较大的素数,做一些算术并使用该值。

通过更改等式中的值,您可以根据需要调整参数。例如,我记得8,191和131,071是素数(因为它们是Mersenne素数)。所以,我可能会这样做:

select i.*
from images i
order by mod(i.id * 8191 + 1, 131071)
limit 50 offset xxx;

您可以调整" + 1"创造不同的序列。这不是真正的"随机"它取决于id是一个整数类型,但它避免了随机数生成器方法的不稳定性。这仍然是order by,因此可能效率低下,具体取决于您的数据大小。

答案 1 :(得分:0)

底部的表定义。对于一组极其快速的随机ID:

with r as (
    select distinct
        ceil(
            random() *
            (select max(image_id) from image)
        )::int as image_id
    from generate_series(1, 200)
    limit 100
)
select image_id
from image inner join r using (image_id)
limit 50

由于主键有间隙,因此必须将表连接到50个以上随机生成的ID,以确保至少有50个。还有多少将取决于PK的“gappyness”。在样本表中,每5个缺少2个。

要获得不同的随机生成ID,还需要生成多于(在上面的示例中)要连接的100。还有多少将取决于桌子有多大。对于一个非常大的桌子,只需要几个就足够了。

即使上述数字被夸大,对性能的影响也可以忽略不计。为了使它不会返回已经显示的图像,我将创建seem_imagessession_id(或user_id更长的到期时间)和image_id并在每个GetImages插入{1}}。无需额外的功能GetMoreImages

with r as (
    select distinct
        ceil(
            random() *
            (select max(image_id) from image)
        )::int as image_id
    from generate_series(1, 200)
    limit 100
), t as (
    select image_id, image
    from image inner join r using (image_id)
    where not exists (
        select 1
        from seem_image
        where image_id = image.image_id and session_id = 1
    )
    limit 50
), i as (
    insert into seem_image (image_id, session_id)
    select image_id, 1
    from t
)
select * from t;

上面的查询只会返回不显示图像。对于样本300万行image表,它非常快。对于长时间图片浏览和会话,有必要从上面的100200更改为适当的数字。应该从seem_image表定期删除过期的会话,具体取决于会话到期时间,以避免它变得过大。

示例image(带有间隙的整数作为主键)和seem_image

create table image (
    image_id integer primary key,
    image bytea
);
insert into image (image_id, image)
select image_id, image
from
    generate_series(1, 5000000) g (image_id)
    cross join
    (values (decode(rpad('', 1024 * 100, 'F'), 'hex'))) i (image)
where mod (image_id, 5) not in (0, 1)
;
analyze image;

create table seem_image (
    session_id integer,
    image_id integer,
    primary key (image_id, session_id)
);