PostgreSQL - 如何使用先前记录的结果插入新记录

PostgreSQL - How to use the result of previous record to insert a new record

我正在尝试为我的 customer table 编写一个迁移,其中我的新列 new_number 应该为每个获取增量值(由于业务规则从 10000 开始)公司如下:

id company_id new_number
1 1 10000
2 1 10001
3 1 10002
4 2 10000
5 2 10001

我编写了一个查询,它通过使用获取最后插入的数字并将 1 加到新行的函数来实现这一目的。但是太慢了,当 运行 数以千计的行时会导致超时。

create or replace function get_number (id_company uuid)
returns int
language plpgsql
as
$$
declare
new_number integer;
begin
select
    c.new_number
into new_number
from
   customer c
where
  c.company_id = id_company
  and c.new_number is not null
order by c.new_number desc
limit 1;
return new_number;
end;
$$;

update
   customer c
set
    new_number =
        case
            when get_number(c.company_id) is null then 1 else get_number(c.company_id) + 1
            end
where c.new_number is null;

有没有更简单的方法来实现我想要做的事情?

提前致谢!

您可以避免使用 UDF,而是在 ROW_NUMBER 的帮助下使用更新连接:

UPDATE customer AS c1 
SET new_number = c2.rn
FROM
(
    SELECT id, ROW_NUMBER() OVER (PARTITION BY company_id ORDER BY id) rn
    FROM customer
) c2
WHERE c1.id = c2.id;

如果我没理解错你可以尝试ROW_NUMBER window函数而不是函数。

WITH cte AS (
    SELECT *,ROW_NUMBER() OVER(PARTITION BY company_id ORDER BY id) rn
    FROM customer 
)
UPDATE customer t1
SET new_number = cte.rn + 9999
FROM cte
WHERE t1.new_number IS NULL AND t1.id = cte.id

如果这是一个持续的需求,最好为插入创建一个触发器。

create table customer(id serial, company_id int, new_number int);
CREATE OR REPLACE FUNCTION customer_new_number_function()
  RETURNS TRIGGER 
  LANGUAGE PLPGSQL
  AS
$$
BEGIN
  IF  NEW.new_number is null THEN
       select coalesce(max(new_number),0) + 1  into NEW.new_number from customer where company_id = NEW.company_id;
  END IF;

  RETURN NEW;
END;
$$
CREATE TRIGGER customer_new_number_trigger
BEFORE INSERT ON customer
FOR EACH ROW 
EXECUTE PROCEDURE customer_new_number_function();
insert into customer (company_id) values (1),(1),(1),(2),(2);
select * from customer;
id | company_id | new_number
-: | ---------: | ---------:
 1 |          1 |          1
 2 |          1 |          2
 3 |          1 |          3
 4 |          2 |          1
 5 |          2 |          2

db<>fiddle here

如果新列仅用于报告目的(即不作为搜索键),您还可以将 new_column 定义为仅作为视图列。

例子

create view v_company as 
select 
 id, company_id,
 9999+row_number() over (partition by company_id  order by id) as new_number
from customer;

select * from v_company
order by id;

id|company_id|new_number|
--+----------+----------+
 1|         1|     10000|
 2|         1|     10001|
 3|         1|     10002|
 4|         2|     10000|
 5|         2|     10001|

这个轻量级解决方案的大优势是您不必在之后解决问题 分配新值的迁移。

由于您的业务逻辑,您不能使用 identityserial 列。

如果您同时插入同一公司的两个客户并且触发器获得相同的当前 max 值,则另一个答案中提议的“触发器”分配解决方案将导致重复。

我将很难保持 new_column 独特且与某些用户定义的序列化没有间隙。