【问题标题】:PL/pgSQL function to randomly select an idPL/pgSQL 函数随机选择一个 id
【发布时间】:2015-09-23 22:22:49
【问题描述】:

目标:

  1. 使用顺序 ID 列表预填充表,例如1 到 1,000,000。该表有一个可空的附加列。 NULL 值被标记为 unassigned,非 NULL 值被标记为 assigned
  2. 具有我可以调用的函数,该函数要求x 从表中随机选择的未分配 ID 的数量。

这是针对一些非常具体的事情,虽然我知道有不同的方法可以做到这一点,但我想知道是否有解决这个特定实现中的缺陷的方法。

我有部分工作,但想知道功能的缺陷在哪里。

这是桌子:

CREATE SEQUENCE accounts_seq MINVALUE 700000000001 NO MAXVALUE;

CREATE TABLE accounts (
  id BIGINT PRIMARY KEY default nextval('accounts_seq'), 
  client VARCHAR(25), UNIQUE(id, client)
);

这个函数gen_account_ids 只是一个一次性设置,用固定数量的行预先填充表格,所有行都标记为未分配。

/*
  This function will insert new rows into the accounts table with ids being
  generated by a sequence, and client being NULL.  A NULL client indicates
  the account has not yet been assigned.
*/
CREATE OR REPLACE FUNCTION gen_account_ids(bigint)
  RETURNS INT AS $gen_account_ids$
DECLARE
  -- count is the number of new accounts you want generated
  count alias for $1;
  -- rowcount is returned as the number of rows inserted
  rowcount int;
BEGIN
  INSERT INTO accounts(client) SELECT NULL FROM generate_series(1, count);
  GET DIAGNOSTICS rowcount = ROW_COUNT;
  RETURN rowcount;
END;
$gen_account_ids$ LANGUAGE plpgsql;

所以,我用它来预填充表格,比如 1000 条记录:

SELECT gen_account_ids(1000);

下一个函数assign 旨在随机选择一个未分配 id(未分配意味着client 列为空),并使用客户端值对其进行更新,使其变为已分配。它返回受影响的行数。

有时可以工作,但我确实相信会发生冲突——这就是我尝试DISTINCT 的原因,但它返回的行数通常少于所需的行数。例如,如果我 select assign(100, 'foo'); 它可能会返回 95 行而不是所需的 100 行。

如何修改它以使其始终返回所需的确切行?

   /*
     This will assign ids to a client randomly
     @param int is the number of account numbers to generate
     @param varchar(10) is a string descriptor for the client
     @returns the number of rows affected -- should be the same as the input int

     Call it like this: `SELECT * FROM assign(100, 'FOO')`
   */
   CREATE OR REPLACE FUNCTION assign(INT, VARCHAR(10))
     RETURNS INT AS $$
   DECLARE
     total ALIAS FOR $1;
     clientname ALIAS FOR $2;
     rowcount int;
   BEGIN
     UPDATE accounts SET client = clientname WHERE id IN (
       SELECT DISTINCT trunc(random() * (
         (SELECT max(id) FROM accounts WHERE client IS NULL) - 
         (SELECT min(id) FROM accounts WHERE client IS NULL)) + 
         (SELECT min(id) FROM accounts WHERE client IS NULL)) FROM generate_series(1, total));
     GET DIAGNOSTICS rowcount = ROW_COUNT;
     RETURN rowcount;
   END;
   $$ LANGUAGE plpgsql;

这大致基于this,您可以在其中执行SELECT trunc(random() * (100 - 1) + 1) FROM generate_series(1,5); 之类的操作,它将选择1 到100 之间的5 个随机数。

我的目标是做类似的事情,在最小和最大未分配行之间选择一个随机 id,并将其标记为更新。

【问题讨论】:

  • 相关博客:blog.2ndquadrant.com/…
  • @CraigRinger 谢谢! TABLESAMPLE 看起来真的很酷!不确定它是否适合我,但该博客上有很多很好的信息......正在调查。

标签: postgresql plpgsql


【解决方案1】:

这不是最好的答案 b/c 它确实涉及全表扫描,但在我的情况下,我不关心性能,它可以工作。这是基于@CraigRinger 对博客文章getting random tuples的引用

我通常有兴趣了解其他(也许更好的)解决方案 - 并且特别好奇为什么最初的解决方案不足,以及 @klin 还设计了什么。

所以,这是我的蛮力随机顺序解决方案:

-- generate a million unassigned rows with null client column
insert into accounts(client) select null from generate_series(1, 1000000);

-- assign 1000 random rows to client 'foo'
update accounts set client = 'foo' where id in 
  (select id from accounts where client is null order by random() limit 1000);

【讨论】:

  • 我觉得这个方案对于sql来说是最好最自然的。不要担心性能,因为可能的替代品应该同样或更昂贵。你可以平静地接受你的回答(感谢克雷格)。
  • 感谢 klin -- 感谢您花时间调查并提供一些解决方案。还要感谢@CraigRinger 的链接——该博客上有很多很好的信息。如果您想提交答案,如果您想要业力,我会接受。在 19 小时过去之前,我无法接受自己的答案。
【解决方案2】:

由于随机行子集的ids 不连续,因此选择随机row_number() 而不是随机id。

with nulls as ( -- base query
    select id
    from accounts 
    where client is null
    ),
randoms as ( -- calculate random int in range 1..count(nulls.*) 
    select trunc(random()* (count(*) - 1) + 1)::int random_value
    from nulls
    ),
row_numbers as ( -- add row numbers to nulls
    select id, row_number() over (order by id) rn
    from nulls
    )
select id
from row_numbers, randoms
where rn = random_value; -- random row number

这里不需要函数,但如果需要,您可以轻松地将查询放在函数体中。

此查询更新 5 个随机行,其中包含 null client。

update accounts
set client = 'new value' -- <-- clientname
where id in (
    with nulls as ( -- base query
        select id
        from accounts 
        where client is null
        ),
    randoms as ( -- calculate random int in range 1..count(nulls.*) 
        select i, trunc(random()* (count(*) - 1) + 1)::int random_value
        from nulls
        cross join generate_series(1, 5) i -- <--  total
        group by 1
        ),
    row_numbers as ( -- add row numbers to nulls in order by id
        select id, row_number() over (order by id) rn
        from nulls
        )
    select id
    from row_numbers, randoms
    where rn = random_value -- random row number
)

但是,不能确定查询会准确更新 5 行,因为

select trunc(random()* (max_value - 1) + 1)::int
from generate_series(1, n)

不是生成n 不同随机值的正确方法。重复的概率随着商 n / max_value 的增加而增加。

【讨论】:

  • 感谢@klin——确实会生成一个唯一的 ID。我想我将其定义为函数的原因是它会定期用于生成,即 assign 一组随机 id 用于某些 x 总数。例如,分配 100 个随机 id,分配 1000 个随机 id。我的下一步是研究使用窗口函数。我会看看我是否可以尝试将其合并到分配多个随机 id 的总数 x
  • 啊,我说得太早了。它确实遇到了与我上面的解决方案相同的问题。见gist.github.com/davisford/dcec6dfe58f3a17aa1b7
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-12-27
  • 1970-01-01
  • 2012-07-16
  • 1970-01-01
  • 2014-03-27
  • 2016-05-02
相关资源
最近更新 更多