基于另一列的PostgreSQL序列

基于另一列的PostgreSQL序列,sql,postgresql,Sql,Postgresql,假设我有一张这样的桌子: Column | Type | Notes ---------+------------ +---------------------------------------------------------- id | integer | An ID that's FK to some other table seq | integer | Each ID gets i

假设我有一张这样的桌子:

Column   |     Type    |                        Notes
---------+------------ +----------------------------------------------------------
 id      | integer     | An ID that's FK to some other table
 seq     | integer     | Each ID gets its own seq number
 data    | text        | Just some text, totally irrelevant.
id
+
seq
是一个组合键

我想看到的是:

ID  | SEQ   |                        DATA
----+------ +----------------------------------------------
 1  | 1     | Quick brown fox, lorem ipsum, lazy dog, etc etc.
 1  | 2     | Quick brown fox, lorem ipsum, lazy dog, etc etc.
 1  | 3     | Quick brown fox, lorem ipsum, lazy dog, etc etc.
 1  | 4     | Quick brown fox, lorem ipsum, lazy dog, etc etc.
 2  | 1     | Quick brown fox, lorem ipsum, lazy dog, etc etc.
 3  | 1     | Quick brown fox, lorem ipsum, lazy dog, etc etc.
 3  | 2     | Quick brown fox, lorem ipsum, lazy dog, etc etc.
 3  | 3     | Quick brown fox, lorem ipsum, lazy dog, etc etc.
 3  | 4     | Quick brown fox, lorem ipsum, lazy dog, etc etc.
如您所见,
id
seq
的组合是唯一的

我不知道如何设置我的表(或插入语句?)来执行此操作。我想插入
id
data
,导致
seq
成为依赖于
id

的子序列,您可以使用a来分配
seq
值,类似于:

INSERT INTO YourTable
    (ID, SEQ, DATA)
    SELECT ID, ROW_NUMBER() OVER(PARTITION BY ID ORDER BY DATA), DATA
        FROM YourSource

PostgreSQL支持分组的唯一列,例如:

CREATE TABLE example (
    a integer,
    b integer,
    c integer,
    UNIQUE (a, c)
);
见第5.3.3节


简单:-)

我没有任何特定于postgresql的经验,但是您能在insert语句中使用子查询吗?比如,用我的语言

INSERT INTO MYTABLE SET 
   ID=4, 
   SEQ=(  SELECT MAX(SEQ)+1 FROM MYTABLE WHERE ID=4  ),
   DATA="Quick brown fox, lorem ipsum, lazy dog, etc etc."
只是猜测而已

INSERT INTO TABLE (ID, SEQ, DATA)
VALUES
(
 IDVALUE,
 (SELECT max(SEQ) +1 FROM TABLE WHERE ID = IDVALUU),
 DATAVALUE
);

没问题!我们将制作两张表,
东西
东西
stuff
将是您在问题中描述的表格,
things
是它所指的表格:

CREATE TABLE things (
    id serial primary key,
    name text
);

CREATE TABLE stuff (
    id integer references things,
    seq integer NOT NULL,
    notes text,
    primary key (id, seq)
);
然后,我们将使用触发器设置
事物
,该触发器将在每次创建行时创建一个新序列:

CREATE FUNCTION make_thing_seq() RETURNS trigger
    LANGUAGE plpgsql
    AS $$
begin
  execute format('create sequence thing_seq_%s', NEW.id);
  return NEW;
end
$$;

CREATE TRIGGER make_thing_seq AFTER INSERT ON things FOR EACH ROW EXECUTE PROCEDURE make_thing_seq();
现在我们将以
东西1
东西2
等结束

现在,在
stuff
上再触发一个触发器,以便每次使用正确的序列:

CREATE FUNCTION fill_in_stuff_seq() RETURNS trigger
    LANGUAGE plpgsql
    AS $$
begin
  NEW.seq := nextval('thing_seq_' || NEW.id);
  RETURN NEW;
end
$$;

CREATE TRIGGER fill_in_stuff_seq BEFORE INSERT ON stuff FOR EACH ROW EXECUTE PROCEDURE fill_in_stuff_seq();
这将确保当行进入
stuff
时,
id
列用于找到调用
nextval
的正确序列

下面是一个演示:

test=# insert into things (name) values ('Joe');
INSERT 0 1
test=# insert into things (name) values ('Bob');
INSERT 0 1
test=# select * from things;
 id | name
----+------
  1 | Joe
  2 | Bob
(2 rows)

test=# \d
              List of relations
 Schema |     Name      |   Type   |  Owner
--------+---------------+----------+----------
 public | stuff         | table    | jkominek
 public | thing_seq_1   | sequence | jkominek
 public | thing_seq_2   | sequence | jkominek
 public | things        | table    | jkominek
 public | things_id_seq | sequence | jkominek
(5 rows)

test=# insert into stuff (id, notes) values (1, 'Keychain');
INSERT 0 1
test=# insert into stuff (id, notes) values (1, 'Pet goat');
INSERT 0 1
test=# insert into stuff (id, notes) values (2, 'Family photo');
INSERT 0 1
test=# insert into stuff (id, notes) values (1, 'Redundant lawnmower');
INSERT 0 1
test=# select * from stuff;
 id | seq |        notes
----+-----+---------------------
  1 |   1 | Keychain
  1 |   2 | Pet goat
  2 |   1 | Family photo
  1 |   3 | Redundant lawnmower
(4 rows)

test=#

如果
seq
反映(或应该反映)插入行的顺序,我宁愿使用
时间戳
,在使用
row_number()
选择行时自动填充并动态生成序列号:

要获取
seq
列,您可以执行以下操作:

select id,  
       row_number() over (partition by id order by inserted_at) as seq,
       data
from some_table
order by id, seq;
但是,与使用持久化的
seq
列(尤其是使用
id,seq
上的索引)相比,select会稍微慢一些

如果这成为一个问题,您可以使用物化视图,或者添加
seq
列,然后定期更新它(出于性能原因,我不会在触发器中这样做)


SQLFiddle示例:

以下是使用标准SQL的简单方法:

INSERT INTO mytable (id, seq, data)
SELECT << your desired ID >>,
       COUNT(*) + 1,
       'Quick brown fox, lorem ipsum, lazy dog, etc etc.'
FROM mytable
WHERE id = << your desired ID (same as above) >>;
插入mytable(id、序列、数据)
选择>,
计数(*)加1,
“敏捷的褐狐、欧瑞姆益普生、懒狗等”
从mytable
其中id=>;


(如果你想更聪明一点,你可以考虑用插入后立即使用相同的方法来创建一个行)。

< P>我同样需要动态存储一个树状结构,而不是同时添加所有的ID。br>我不希望每个组都使用序列表,因为可能有数千个序列表
它在密集的多处理环境中运行,因此它必须是无竞争条件的
这里是第一级的插入功能。其他级别遵循相同的原则

每个组作为独立的、不可重用的顺序ID,函数将接收组名和子组名,为您提供现有ID或创建现有ID并返回新ID。
我尝试了一个循环以进行单个选择,但代码同样长且更难阅读

CREATE OR REPLACE FUNCTION getOrInsert(myGroupName TEXT, mySubGroupName TEXT)
  RETURNS INT AS
$BODY$
DECLARE
   myId INT;
BEGIN -- 1st try to get it if it already exists
   SELECT id INTO myId FROM myTable
      WHERE groupName=myGroupName AND subGroupName=mySubGroupName;
   IF NOT FOUND THEN
      -- Only 1 session can get it but others can read
      LOCK TABLE myTable IN SHARE ROW EXCLUSIVE MODE; 
      -- 2nd try in case of race condition
      SELECT id INTO myId FROM myTable
         WHERE groupName=myGroupName AND subGroupName=mySubGroupName;
      IF NOT FOUND THEN -- Doesn't exist. Get next ID for this group.
         SELECT COALESCE(MAX(id), 0)+1 INTO myId FROM myTable
            WHERE groupName=myGroupName;
         INSERT INTO myTable (groupName, id, subGroupName)
            VALUES (myGroupName, myId, mySubGroupName);
      END IF;
   END IF;
   RETURN myId;
END;
$BODY$
  LANGUAGE plpgsql VOLATILE COST 100;
要尝试它:

CREATE TABLE myTable (GroupName TEXT, SubGroupName TEXT, id INT);
SELECT getOrInsert('groupA', 'subgroupX'); -- Returns 1
...
SELECT * FROM myTable;
 groupname | subgroupname | id 
-----------+--------------+----
 groupA    | subgroupX    |  1
 groupA    | subgroupY    |  2
 groupA    | subgroupZ    |  3
 groupB    | subgroupY    |  1

唯一的部分不是我主要关心的,它是以这种方式获取数据输入,就像它是一个子序列一样。有趣的方法。。。(多年来一直在寻找类似的东西!)使用分区/行号会有什么后果,安全吗?什么时候它可能不工作?窗口函数非常常见,您可能不会遇到“它可能不工作”的情况。查看Joe答案中的链接。一旦您开始使用它们,它将在您的SQL语句中打开一个新的可能性世界。如果您要删除除最后一条记录以外的任何记录,则此操作将失败。在这种情况下,行号()将与最后一条记录冲突。换句话说,在很多非常常见的情况下,这种语法在某个时候会失败。这种语法是无效的SQL。对于
insert
没有
SET
实际上这个语法是特定于MySQL的,但主要思想是如果
seq
反映(或应该反映)插入行的顺序,我宁愿使用自动填充的
时间戳
,并在选择行时动态生成
seq
编号。删除记录时会发生什么?(或更新)我同意@joop,任何删除都可能使
seq
不可靠,如果它是动态生成的。你想用这个结构解决什么问题?(例如,如果你的唯一目标是使
id,seq
对唯一,那么一个序列就可以做到这一点——事实上它会使
seq
唯一,但这意味着
id,seq
对唯一)@fthiella,很好奇,这样的
seq
列的实际用途是什么?根据其预期用途,可以有不同的方法。这里的一个重要问题是:序列中存在间隙(由于删除的行或不完整的回滚事务)可以吗?如果间隙不正常,则如果序列被持久化,则重新计算该序列的成本将很高,这意味着在需要时动态生成该序列可能更好。如果间隙正常,那么单个全局序列(标准的自动增量列)就足够了。@fthiella:简单的
串行
是一种方法。这基本上是Joe的答案,只是效率不如正确工作所需的表独占锁?如果像这样的两个插入同时运行会怎么样?我认为这些max+1解决方案可能被证明是不可靠的。为了在Postgresql上测试,我创建了
tbl
并插入了一行:
id=1
。然后我打开了两个连接,并在每个连接上启动了一个事务。我执行了
插入tbl从tbl中选择MAX(id)+1
。第一次插入完成,第二次插入按预期等待第一次插入。我立即提交了第一笔交易和第二笔交易
CREATE TABLE myTable (GroupName TEXT, SubGroupName TEXT, id INT);
SELECT getOrInsert('groupA', 'subgroupX'); -- Returns 1
...
SELECT * FROM myTable;
 groupname | subgroupname | id 
-----------+--------------+----
 groupA    | subgroupX    |  1
 groupA    | subgroupY    |  2
 groupA    | subgroupZ    |  3
 groupB    | subgroupY    |  1