随机排序的行可重复分页

时间:2014-08-25 09:29:56

标签: postgresql random pagination random-seed

我有API从DB返回圆顶分页行。它工作正常,但是当我按RANDOM()订购行时,我会在连续的页面上得到重复。是否有为每个查询设置随机种子的选项?

如果不是,是否可以全局设置随机SEED以强制RANDOM()为每个查询生成相同的值?然后我可以每隔3分钟改变一次全局随机或类似的东西......


你使用这段代码:

SELECT * FROM "table" ORDER BY RANDOM() OFFSET 5 LIMIT 5

现在我希望将种子传递给此查询,以便我可以对随机结果进行分页。 我应该这样做吗?:

SELECT "table".*, SETSEED(0.1) FROM "table" ORDER BY RANDOM() OFFSET 5 LIMIT 5
SELECT "table".*, SETSEED(0.1) FROM "table" ORDER BY RANDOM() OFFSET 10 LIMIT 5

结果将正确分页?

5 个答案:

答案 0 :(得分:6)

如果订单需要"洗牌"但不是真正的随机......

你说"随机" order,这是你在调用ORDER BY random()时获得的 - 对于每一行,PostgreSQL调用random(),获取一个值,并使用它来决定如何在结果集中对该行进行排序。

为了使这个可重复,你必须弄乱种子。这感觉很蠢。根据{{​​3}}:

  

效果将持续到会话结束,除非被另一个SET

覆盖

我认为这意味着在使用连接池时,setseed会改变使用该连接的下一个进程的连接。

模数怎么样?

我有一个案例,我不需要真正的随机性。我的标准是:

  • 每次都不一样
  • 同一结果集页面中的可预测顺序,以便我们不会在后续页面上获得重复

例如,这没关系:

  • 清单1
    • 第1页:第1,4项
    • 第2页:第3项,第2项
  • 清单2(不同的用户或稍后回来的同一用户)
    • 第1页:第3项,第1项
    • 第2页:第2,4项

为了获得这样的东西,模数似乎运作良好。例如,ORDER BY id % 7, id表示请求1的所有页面,ORDER BY id % 11, id表示请求2的所有页面。也就是说,对于每一行,将其id除以模数并按余数排序。在具有相同余数的行中,按id排序(以确保排序稳定)。

可以为第一页随机选取模数,然后将其重新用作每个后续页面请求的参数。

您可以看到这对您的数据库有何影响:

echo "select id, id % 7 FROM my_table ORDER BY id % 77, id" | psql my_db > sort.txt

素数模数可能会给你最大的变化。如果你的id从1开始(这样% 77会使前77行以正常顺序返回),你可以尝试在时间戳字段上执行模数。例如:

ORDER BY (extract(epoch from inserted_at)* 100000)::bigint % 77

但是你需要一个功能索引来提高效率。

答案 1 :(得分:4)

使用此union all技术,随机顺序是可重复的

select a, b
from (
    select setseed(0.1), null as a, null as b

    union all

    select null, a, b
    from t

    offset 1
) s
order by random()
offset 0
limit 5
;

答案 2 :(得分:2)

您可以使用setseed(dp)种子random()种子[-1.0,1.0]。 E.g:

engine=> SELECT SETSEED(0.16111981);
 setseed 
---------

(1 row)

engine=> SELECT RANDOM();
      random       
-------------------
 0.205839179921895
(1 row)

engine=> SELECT RANDOM();
      random       
-------------------
 0.379503262229264
(1 row)

engine=> SELECT RANDOM();
      random       
-------------------
 0.268553872592747
(1 row)

engine=> SELECT RANDOM();
      random       
-------------------
 0.788029655814171
(1 row)

当然,每次重新种植时,您都会得到完全相同的结果:

engine=> SELECT SETSEED(0.16111981), RANDOM();
 setseed |      random       
---------+-------------------
         | 0.205839179921895
(1 row)

engine=> SELECT SETSEED(0.16111981), RANDOM();
 setseed |      random       
---------+-------------------
         | 0.205839179921895
(1 row)

engine=> SELECT SETSEED(0.16111981), RANDOM();
 setseed |      random       
---------+-------------------
         | 0.205839179921895
(1 row)

engine=> SELECT SETSEED(0.16111981), RANDOM();
 setseed |      random       
---------+-------------------
         | 0.205839179921895

(澄清:输出是从psql复制的,引擎是我数据库的名称)

答案 3 :(得分:0)

指定确切的行ID(提前随机分配)

此查询将再次以正确的顺序为您提供ID为4、2、1和4的行。

SELECT items.id, items.name
FROM items
-- unnest expands array values into rows
INNER JOIN unnest(ARRAY[4,2,1,4]) AS item_id
ON items.id = item_id

收益

 id |     name
----+---------------
  4 | Toast Mitten
  2 | Pickle Juicer
  1 | Horse Paint
  4 | Toast Mitten

知道这一点,您可以根据需要提供每个页面上应包含的ID。

例如,您可以SELECT id FROM items ORDER BY random(),将列表分成(例如)5个ID的“页面”,并将其保存在应用程序内存,Redis中或任何地方。对于请求的每个页面,您将使用正确的ID页面运行上面的查询。

变化:

  • 要获得真正的随机性,可以启用pgcryptoORDER BY gen_random_uuid()
  • 您可以省略ORDER BY并以您的编程语言将id随机排列在内存中。
  • 您可以为每个用户或每天创建一个不同的改组

答案 4 :(得分:0)

对于主键为UUID(或其他某种哈希)的表,一个非常快速且肮脏的选项是按UUID的子字符串对结果进行排序。在您的应用中,随机生成1到36之间的一些正整数,并在查询中使用这些位置。

例如,如果我要五个数字并生成{23,12,35,16,3},我将使用:

select * from (
  select
    organization_id,
    substring(cast(organization_id as text) from 23 for 1) as o1,
    substring(cast(organization_id as text) from 12 for 1) as o2,
    substring(cast(organization_id as text) from 35 for 1) as o3,
    substring(cast(organization_id as text) from 16 for 1) as o4,
    substring(cast(organization_id as text) from 3 for 1) as o5
  from channels_organizations
) t order by o1, o2, o3, o4, o5;

这将生成如下结果,您可以轻松分页:

organization_id                       o1  o2  o3  o4  o5
a059cd76-9d91-48db-8982-986fcd217b2a   2   9   2   8   5
3ce14f26-3e56-46eb-9a74-22862cc3ed4e   4   5   4   6   e
8e115b7e-2e7e-480e-9bc6-296deff3ed87   6   7   8   8   1
e1969c52-5028-47da-92ea-9f2918dcbf4d   a   2   4   7   9
42eb7292-e881-4a04-b83a-3bf78548dab4   a   8   b   a   e
e8a33112-532f-4fec-b25b-416c5409ac7e   b   2   7   f   a
a763efaa-79a4-4cfa-92bc-803ebc5ff221   c   a   2   c   6
581cae5b-5000-4aa6-837d-002ccf806e28   d   0   2   a   1
6b0ed7b4-b44d-4a51-910f-f3f2f0354d55   f   4   5   a   0
9369a547-f7e0-43e7-96ef-62bf631a0f0b   f   e   0   3   6

在此示例中,我在UUID格式的每个组中选择一个索引,并在将其发送到数据库之前对它们进行随机排序。

如果您不关心伪随机性,可以通过只使用一个子字符串索引来简化此操作,尽管您希望将子字符串长度(n中的from x for n)增加到至少3,因为您有可能在子字符串中以破折号结尾。