15

I have API that returns dome paginated rows from DB. It works, however when I order rows by RANDOM() I get duplicates on consecutive pages. Is there any option to set random seed per query?

If not is it possible to set random SEED globally to force RANDOM() to generate same values per query? Then I could just change global random every 3 minutes or something like that...


U use this code:

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

Now I want pass seed to this query so I can paginate random results. I should do this like this?:

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

And results will be correctly paginated?

4

5 回答 5

11

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

更新:请参阅我的其他答案以获得更灵活和可随机化的解决方案。

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

为了使这个可重复,你必须弄乱种子。这感觉很恶心。根据文档

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

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

模数呢?

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

  • 每次都不一样的顺序
  • 同一结果集的页面内的可预测顺序,因此我们不会在后续页面上得到重复

例如,这会很好:

  • 清单 1
    • 第 1 页:第 1、4 项
    • 第 2 页:第 3、2 项
  • 清单 2(不同的用户,或稍后返回的相同用户)
    • 第 1 页:项目 3、1
    • 第 2 页:第 2、4 项

为了得到这样的东西,模数似乎工作得很好。例如,ORDER BY id % 7, id对于请求 1ORDER 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

但是你需要一个函数索引来提高性能。

于 2017-10-02T14:01:31.550 回答
5

使用这种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
;
于 2014-08-27T00:24:25.547 回答
2

您可以使用[-1.0, 1.0] 中的种子播种setseed(dp)random()例如:

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,引擎是我的数据库的名称)

于 2014-08-25T09:39:22.377 回答
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 格式中选择一个索引,并在发送到数据库之前将它们打乱。

如果您不太关心伪随机性,您可以通过仅使用单个子字符串索引来简化这一点,尽管您希望将子字符串长度(nin from x for n)增加到至少 3,因为您最终可能会得到一个破折号在您的子字符串中。

于 2020-02-24T17:57:09.937 回答
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。
  • 您可以为每个用户或每天创建不同的洗牌
于 2019-06-03T14:18:36.857 回答