Alter column position/ru
Множество людей, только начавших знакомство с PostgreSQL, часто задают вопрос на счет поддержки изменения порядка следования колонок таблицы. На текущий момент поддержка отсутствует. Чтобы изменить порядок колонок, необходимо либо пересоздать таблицу, либо создать новые колонки с последующим переносом данных. Сама по себе идея перемещения колонок разработчикам PostgreSQL не претит, просто шаги к разработке данной функциональности не предпринимались. Назначение этого документа заключается в разъяснении обходных путей и тонких моментов, которые необходимо учитывать, при желании реализовать эту возможность.
Существует две основные причины, по которым может понадобиться изменить порядок колонок в реалиях PostgreSQL:
- размещение колонок фиксированной длины в начале таблицы влияет на оптимизацию физической структуры
- измененный порядок следования колонок в результатах выборок может быть более привлекательным внешне
На текущий момент PostgreSQL хранит порядок следования колонок таблиц к колонке attnum системной таблицы pg_attribute. Единственный путь изменить порядок колонки - пересоздать таблицу, или добавить колонки, переливая данные между старыми и новыми до тех пор, пока не будет достигнут нужный порядок.
Обходные пути
Воссоздай таблицу
В первом примере приведена таблица 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)
Для полноты процесса необходимо воссоздать представления, ссылающиеся на новую таблицу. Также нужно помнить о необходимости воссоздания необходимых индексов, триггеров и иных объектов базы, чтобы конечная схема соответствовала первоначальной. Все это лучше делать в транзакции, однако, стоит помнить о возможном большом количестве зависимостей и возможных последствиях.
Добавь колонки и перелей данные
Иногда неоправданно идти путем воссоздания таблицы. Тогда может помочь другой - добавление колонок с одновременным перемещением данных. В примере у нас есть актер с идентификатором, его имя и фамилия, дата последнего обновления. Далее нам потребовалось добавить отчество в нашу таблицу, при этом мы хотим добавить новую колонку между именем и фамилией. Поехали!
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()
Сначала мы добавим колонку middle_name, вначале она будет последней по счету. Затем добавим новые колонки 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. Камнем преткновения сейчас является факт одновременного использования идентификаторов логической и физической структур. В ближайших планах разнести ссылки на три идентификатора: постоянный идентификатор, физической и логической структур. Это позволит в запросах вида select * ссылаться на логический номер колонок, в то время, как в задачах, где требуется взаимодействие с дисковой системой - ссылаться на физический номер, а иных - просто обращаться к колонке по ее постоянному идентификатору.
С другой стороны необходимо собрать обратную связь по синтаксису. В основном используется ключевое слово BEFORE/AFTER, за которым следует имя колонки.