要理解的关键是 SQL 表没有顺序。当您SELECT 没有ORDER BY 时,您看到的行顺序仅保持不变,因为数据库按该顺序获取它们比其他顺序更快。当您对表进行顺序扫描时,PostgreSQL 只会按此顺序返回行;如果它可以使用索引进行查询,那么您通常会以其他顺序获取行。
您可能会发现 this answer I wrote earlier 信息丰富。
在 PostgreSQL 中,UPDATEs 到行可以将它们移动到表中的不同位置,从而更改它们返回的顺序。后台 autovacuum 进程和各种其他操作(如 VACUUM 和 CLUSTER)也可以.
因此,您必须从不依赖“默认”排序来处理任何事情。如果你想给行某种顺序,它们必须有一个你可以对它们进行排序的键。
如果您创建了一个没有键的表,但现在意识到它应该有一个键,您可以使用ctid 系统列从这种情况中恢复过来。 不要将此用于生产用途,它是系统内部的列,用户仅可出于紧急恢复和诊断目的而看到它。首先,看看物理上的磁盘顺序是否真的是你想要的顺序:
SELECT row_number() OVER () AS mytable_id, *
FROM mytable
ORDER BY ctid;
如果是,您可以添加一个新的键列,该列已预先设置为按磁盘行顺序应用的自动递增键。有两种方法可以做到这一点。最安全的是:
BEGIN;
LOCK TABLE mytable IN ACCESS EXCLUSIVE MODE;
ALTER TABLE mytable RENAME TO mytable_old;
CREATE TABLE mytable (id SERIAL PRIMARY KEY, LIKE mytable_old INCLUDING ALL);
INSERT INTO mytable
SELECT row_number() OVER () AS id, *
FROM mytable_old
ORDER BY ctid;
SELECT setval('mytable_id_seq', (SELECT max(id)+1 FROM mytable));
COMMIT;
然后,一旦您确定对结果感到满意,DROP TABLE mytable_old;。看这个演示:http://sqlfiddle.com/#!12/2cb99/2
一种快速简单但不太安全的方法是只创建列并依赖 PostgreSQL 从头到尾重写表:
ALTER TABLE mytable ADD COLUMN mytable_id SERIAL PRIMARY KEY;
绝对不能保证 PostgreSQL 会按顺序分配 ID,尽管实际上它会这样做。见this SQLFiddle demo。
请注意,当您使用 SEQUENCE(这是 SERIAL 列创建的内容)时,可能会出现一些您意想不到的行为。当您一次插入多行时,这些行可能不一定按照您期望的确切顺序获得分配的 ID,并且它们可能以与分配 ID 和插入的顺序不同的顺序“出现”(变得可见)此外,如果事务回滚,生成的 ID 将被永远丢弃,因此您会在编号中出现空白。如果您希望数据库速度很快,这非常好,但如果您想要无间隙编号,这并不理想。如果这是您需要的,请搜索“postgresql gapless sequence”。