更改列位置
许多 PostgreSQL 新手经常会问,PostgreSQL 是否支持更改表中列的位置。 目前还不支持;如果要更改列位置,则必须重新创建表,或添加新列并移动数据。 允许重新排序列位置的想法并非 PostgreSQL 开发人员反对,更多的是没有人挺身而出做这项工作。 本文档的其余部分旨在解释解决方法,并跟踪在有人希望实现此功能时必须处理的问题。
能够在 PostgreSQL 中更改列位置有两个主要原因
- 通过将固定大小的列放在表的开头,可以优化物理布局
- 排序列可以使使用表更轻松,无论是通过以视觉上吸引人的顺序放置结果集,还是通过根据表中相似的功能对列进行分组。
PostgreSQL 当前根据 pg_attribute 表的 attnum 列定义列顺序。 更改列顺序的唯一方法是重新创建表,或添加列并旋转数据,直到达到所需的布局。
更改列的解决方法
重新创建表
在我们的第一个示例中,我们有一个名为 film_actor 的表,其中包含三列,actor_id、film_id 和 last_update 列。 我们注意到许多开发人员经常会混淆哪个 id 是电影,哪个是演员,因此为了消除这种情况,我们想要更改列的顺序。
pagila=# \d film_actor Table "public.film_actor" Column | Type | Modifiers -------------+-----------------------------+------------------------ actor_id | smallint | not null film_id | smallint | not null last_update | timestamp without time zone | not null default now() Indexes: "film_actor_pkey" PRIMARY KEY, btree (actor_id, film_id) "idx_fk_film_id" btree (film_id) Foreign-key constraints: "film_actor_actor_id_fkey" FOREIGN KEY (actor_id) REFERENCES actor(actor_id) ON UPDATE CASCADE ON DELETE RESTRICT "film_actor_film_id_fkey" FOREIGN KEY (film_id) REFERENCES film(film_id) ON UPDATE CASCADE ON DELETE RESTRICT Triggers: last_updated BEFORE UPDATE ON film_actor FOR EACH ROW EXECUTE PROCEDURE last_updated()
为此,我们将创建一个具有所需列顺序的新表,使用数据更新它,然后删除旧表,解决所有依赖关系。
pagila=# begin; BEGIN pagila=# create table film_actor_new ( pagila-# film_id smallint references film, pagila-# actor_id smallint references actor, pagila-# last_update timestamp not null default now(), pagila-# primary key (actor_id,film_id)); NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "film_actor_new_pkey" for table "film_actor_new" CREATE TABLE pagila=# insert into film_actor_new select film_id, actor_id, last_update from film_actor; INSERT 0 5462 pagila=# drop table film_actor cascade; NOTICE: drop cascades to rule _RETURN on view nicer_but_slower_film_list NOTICE: drop cascades to view nicer_but_slower_film_list NOTICE: drop cascades to rule _RETURN on view film_list NOTICE: drop cascades to view film_list NOTICE: drop cascades to rule _RETURN on view actor_info NOTICE: drop cascades to view actor_info DROP TABLE pagila=# alter table film_actor_new RENAME to film_actor; ALTER TABLE pagila=# \d film_actor Table "public.film_actor" Column | Type | Modifiers -------------+-----------------------------+------------------------ film_id | smallint | not null actor_id | smallint | not null last_update | timestamp without time zone | not null default now() Indexes: "film_actor_new_pkey" PRIMARY KEY, btree (actor_id, film_id) Foreign-key constraints: "film_actor_new_actor_id_fkey" FOREIGN KEY (actor_id) REFERENCES actor(actor_id) "film_actor_new_film_id_fkey" FOREIGN KEY (film_id) REFERENCES film(film_id)
如果我们要继续,我们需要重新创建上面引用的视图以引用我们新版本的表。 此外,请不要忘记,我们还需要重新创建新表上缺少的索引,并重新创建触发器,然后重命名所有表/索引/等... 确保最终架构与之前的架构匹配。 虽然你可以在事务中完成所有这些工作,但重要的是要彻底完成这些工作,因为你可能会遇到很多依赖问题。
添加列并移动数据
有时重新创建表太麻烦了,因此你可以使用添加列和移动数据的方法。 在此示例中,我们有一个演员,其中包含 id、first 和 last name 列,以及 alast_updated 列。 我们将在表中添加一个 middle_name 列,并且我们希望将它添加到 first_name 和 last_name 列之间。
pagila=# \d actor Table "public.actor" Column | Type | Modifiers -------------+-----------------------------+---------------------------------------------------------- actor_id | integer | not null default nextval('actor_actor_id_seq'::regclass) first_name | character varying(45) | not null last_name | character varying(45) | not null last_update | timestamp without time zone | not null default now() Indexes: "actor_pkey" PRIMARY KEY, btree (actor_id) "idx_actor_last_name" btree (last_name) Triggers: last_updated BEFORE UPDATE ON actor FOR EACH ROW EXECUTE PROCEDURE last_updated()
为此,我们将首先添加中间名称列(它将添加到末尾),然后添加一个新的 last_name 列,以及另一个新的 last_update 列,更新相应的列,然后删除旧列。
pagila=# begin; BEGIN pagila=# alter table actor add column middle_name text, add column lname text, add column lupdate text; ALTER TABLE pagila=# update actor set lname=last_name, lupdate=last_update; UPDATE 200 pagila=# alter table actor drop column last_name cascade, drop column last_update cascade; NOTICE: drop cascades to rule _RETURN on view nicer_but_slower_film_list NOTICE: drop cascades to view nicer_but_slower_film_list NOTICE: drop cascades to rule _RETURN on view film_list NOTICE: drop cascades to view film_list NOTICE: drop cascades to rule _RETURN on view actor_info NOTICE: drop cascades to view actor_info ALTER TABLE pagila=# alter table actor rename column lname to last_name; ALTER TABLE pagila=# alter table actor rename column lupdate to last_update; ALTER TABLE pagila=# \d actor Table "public.actor" Column | Type | Modifiers -------------+-----------------------+---------------------------------------------------------- actor_id | integer | not null default nextval('actor_actor_id_seq'::regclass) first_name | character varying(45) | not null middle_name | text | last_name | text | last_update | text | Indexes: "actor_pkey" PRIMARY KEY, btree (actor_id) Triggers: last_updated BEFORE UPDATE ON actor FOR EACH ROW EXECUTE PROCEDURE last_updated()
同样,在我们完成此更改之前,我们需要重新创建上面的视图,并处理可能出现的任何索引、触发器或其他依赖关系。 同样重要的是,更新将导致表中的所有行被重写,这将需要维护(最有可能根据你的表进行某种形式的集群/重新索引)。
使用视图隐藏差异
为了完整性,还有另一种更改表顺序的方法,即使用视图隐藏更改。 基本思想是添加任何新列(如果添加新列),重命名表,并创建一个具有旧表名称的视图,该视图包含你所需的列顺序。 从 PostgreSQL 9.3 开始,此视图在写入方面应该与普通表一样。 虽然这适用于逻辑列重新排序,但如果要重新排序以优化物理布局,则仍需使用上述原因之一。
将更改列语法添加到 PostgreSQL
由于上述方法存在一些问题,因此人们经常表达希望添加 PostgreSQL 功能以允许重新排序列。 另一方面,对于 PostgreSQL 来说,无论它们在逻辑上的顺序如何,自动按最佳布局物理排序列也是可取的。 实现此问题的当前问题在于,PostgreSQL 当前使用相同的标识符来标识表中的逻辑位置和物理位置。 解决此问题的当前热门计划是将系统更改为引用三个标识符... 列的永久标识符,以及单独的逻辑和物理标识符。 这将允许需要在逻辑级别(即 select *)专门处理列顺序的地方引用逻辑编号,而与磁盘系统交互的地方可以访问物理编号,所有其他地方只使用列的永久 ID。
- 上述计划取自以下电子邮件 http://archives.postgresql.org/pgsql-hackers/2006-12/msg00983.php.
- TODO 列表引用了这封电子邮件,http://archives.postgresql.org/pgsql-hackers/2006-12/msg00782.php,如果你想了解更多背景故事,这是一个有趣的阅读。
另一方面,我们还需要确定用户如何与这项新功能进行交互的语法。 最常见的实现通常使用一组 BEFORE/AFTER 关键字,后跟现有列名。