2

我有一个user_id按项目唯一的列跟踪(供外部使用)。

我想user_id在创建新用户时增加该列,这取决于该项目的计数。已经有许多现有记录,但从现在开始,我们希望user_id在给定的每个新记录上增加 1 project_id

示例用户表

id   user_id   project_id
-------------------------
1    100       1
2    101       1
3    1000      2
4    1001      2
5    17        3
6    18        3
7    102       1

New row with project_id = 1 should use user_id = 103
New row with project_id = 2 should use user_id = 1002
New row with project_id = 3 should use user_id = 19

如何构造user_id列和/或INSERT查询,使其始终根据相应中user_id现有的最大值递增,并保证在并发插入时不会为同一项目中的两个用户分配相同的值?user_idproject_iduser_id

4

5 回答 5

2

您需要使用 WITH 子句。

这是实现。

--PostgreSQL 9.6
create table tab
(
    id SERIAL ,
    user_id integer ,
    project_id integer 
 );

INSERT INTO tab(user_id, project_id ) VALUES (100 ,      1);
INSERT INTO tab(user_id, project_id ) VALUES (101     ,   1);
INSERT INTO tab(user_id, project_id ) VALUES (1000    ,   2);
INSERT INTO tab(user_id, project_id ) VALUES (1001    ,   2);
INSERT INTO tab(user_id, project_id ) VALUES (17      ,   3);
INSERT INTO tab(user_id, project_id ) VALUES (18      ,   3);
INSERT INTO tab(user_id, project_id ) VALUES (102     ,   1);

create table src 
(
    project_id integer
);

insert into src values  (1); 
insert into src values (2);
insert into src values (3) ; 

select * from src ; 
select * from tab ; 

with cur as 
(
select project_id , max(user_id)  as max_user_id from tab group by project_id
)
INSERT INTO tab(user_id, project_id ) 
    SELECT cur.max_user_id +  row_number() over( partition by src.project_id  ) , src.project_id 
    from src inner join cur on src.project_id = cur.project_id  ;

select * from tab order by project_id , user_id ;

结果 :

    project_id
1   1
2   2
3   3

    id  user_id project_id
1   1   100 1
2   2   101 1
3   3   1000    2
4   4   1001    2
5   5   17  3
6   6   18  3
7   7   102 1

    id  user_id project_id
1   1   100 1
2   2   101 1
3   7   102 1
4   8   103 1
5   3   1000    2
6   4   1001    2
7   9   1002    2
8   5   17  3
9   6   18  3
10  10  19  3

https://rextester.com/HREM53701

在此处阅读有关 with 子句的更多信息

https://www.tutorialspoint.com/postgresql/postgresql_with_clause.htm

于 2020-03-03T06:48:37.963 回答
2

保证同一项目中没有两个用户在user_id并发插入时被分配相同的直接方法是防止并发活动。

实现它的一种方法是将事务隔离级别设置为可序列化。

BEGIN TRANSACTION

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE

-- here I took the query from George Joseph's answer

insert into user_table
    (user_id, project_id)
select
    coalesce(max(user_id), 0) + 1 as user_id
    ,@project_id as project_id
from
    user_table
where
    project_id=@project_id

COMMIT TRANSACTION

您可以同时从多个会话运行此查询块,引擎将在后台处理并发。我真的不知道 Postgres 是如何做到的。最有可能的并发事务将等待前一个完成。

为了有效地工作,您需要一个关于(project_id, user_id). 您还需要使其唯一以强制执行您的约束。此索引中列的顺序很重要。


您还提到您预计会有数千个项目,最终每个项目会有数百万用户。这加起来有 10 亿行,这MAX对于每次插入来说都是相当多的。即使有适当的索引。

您可以创建一个单独的表project_sequences来存储user_id每个的最后一个值project_id。该表将有两列project_id,并且两列last_user_id都有主键(project_id, last_user_id)。索引中列的顺序很重要。

现在,您可以project_sequences在主大表中的每次插入中查询和更新只有 1000 行的小表。我不熟悉变量的 Postgres 语法,所以下面是伪代码。

BEGIN TRANSACTION

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE

-- read the last_user_id for the given project_id from the small table into a variable
-- and increment it
-- update the small table with the new last_user_id
-- use the freshly generated user_id to insert into the main table


-- or, without variables
-- increment the last_user_id
update project_sequences
set last_user_id = 
    (
    select coalesce(max(last_user_id), 0) + 1 
    from project_sequences
    where project_id=@project_id
    )
where
    project_id=@project_id


-- use the new id to insert into the main table
insert into user_table
    (user_id, project_id)
select
    last_user_id
    ,@project_id as project_id
from
    project_sequences
where
    project_id=@project_id


COMMIT TRANSACTION

project_id使用变量,当给定的是新的,表中尚不存在并将新的设置user_id为从 1 或您需要的任何起始值开始时,处理这种情况会更容易一些。

于 2020-03-03T23:50:14.730 回答
0

您可以通过 project_id 找出 user_id 的最大值然后将其增加 1 来做到这一点。如果您有一个多用户场景,那么需要考虑某种序列化以确保并发用户不使用相同的数字例如:假设您要将 project_id 作为变量 @project_id 传递

insert 
  into user_table
       (user_id
        ,project_id
       )
select 
       (select max(user_id)+1
          from user_table
         where project_id=@project_id) as user_id
       ,@project_id
于 2019-12-10T03:16:06.390 回答
0

我建议在插入之前使用触发器,这样你 99.99% 的 shure 就不会在序列中出现重复和漏洞(101,102,缺失,111,112)。

序列的问题在于,如果不小心使用,您可能会失去对当前数字的控制。最终数据库中缺少数字。

只需让触发器负责增加数字即可。

此外,通过这种方式,您不必担心会消耗大量内存和处理能力的复杂查询。

触发器是:

CREATE OR REPLACE FUNCTION set_user_id()
    RETURNS trigger AS $set_user_id$
BEGIN
    IF NEW.user_id IS NULL
    THEN
        NEW.user_id = COALESCE( ( SELECT MAX(user_id) FROM data WHERE project_id = NEW.project_id ), 0 ) + 1;
    END IF;
    RETURN NEW;
END $set_user_id$ LANGUAGE plpgsql;

CREATE TRIGGER table_user_id
    BEFORE INSERT ON data
    FOR EACH ROW EXECUTE PROCEDURE set_user_id();

笔记:

  • 如果插入将 null 发送到 user_id,它只会增加用户。例如:

INSERT INTO data (project_id) VALUES (1);

或者

INSERT INTO data (user_id,project_id) VALUES (NULL,1);

使用您的示例数据,这将插入

id   user_id   project_id
-------------------------
8    103       1
  • 如果没有之前的 user_id,则设置 1 ( COALESCE)中的值

INSERT INTO data (project_id) VALUES (4);

使用您的示例数据,这将插入

id   user_id   project_id
-------------------------
9    1         4
  • 如果要设置起始 user_id,只需在该 project_id 的第一个插入项上设置它。

INSERT INTO data (user_id,project_id) VALUES (10,5);

使用您的示例数据,这将插入

id   user_id   project_id
-------------------------
10   10        5
于 2020-03-05T22:14:23.160 回答
-1

要自动增加您的 id,您可以执行 3 种方法,

  1. 使用身份,例如 - 创建表时

        create table a( key int identity(1,1)) 
    
        -- first "1" is initial value
    
           -- second"1" is a value which is added to next one
    
  2. 创建一个序列

创建序列 seq_name

作为 dat_type -- bigint

从 1-- 开始

递增 1 - 递增值

参考 - https://www.techonthenet.com/sql_server/sequences.php

3 - 使用 select sum(col_name) from t_name ##### from 编程代码并将一个值添加到检索到的值并将该值用于 id 将添加到新创建的 id..

于 2019-12-10T03:12:19.993 回答