Проблема с Postgres ALTER TABLE - PullRequest
30 голосов
/ 14 июля 2010

У меня есть одна проблема с ALTER TABLE в postgre. Я хочу изменить размер столбца varchar. Когда я пытаюсь сделать это, он говорит, что представление зависит от этого столбца. Я не могу отказаться от представления, потому что от этого зависит что-то еще. Есть ли другой способ, кроме как бросить все и воссоздать его снова?

Я только что нашел одну опцию, которая заключается в удалении таблицы, присоединяющейся к представлению, когда я не буду изменять возвращаемые столбцы, я могу это сделать. Но все же, есть еще мнения, которые мне нужно изменить. Разве нет ничего, как я могу сказать, что это следует отложить и проверить с помощью commit?

Ответы [ 4 ]

22 голосов
/ 14 июля 2010

Я столкнулся с этой проблемой и не смог ее обойти. К сожалению, насколько я могу судить, нужно отбросить представления, изменить тип столбца в базовой таблице, а затем воссоздать представления. Это может произойти полностью в одной транзакции.

Отсрочка ограничения не относится к этой проблеме. Другими словами, даже SET CONSTRAINTS ALL DEFERRED не влияет на это ограничение. В частности, отсрочка ограничения не применяется к проверке согласованности, которая печатает ERROR: cannot alter type of a column used by a view or rule, когда кто-то пытается изменить тип столбца, лежащего в основе представления.

11 голосов
/ 23 марта 2012

Если вам не нужно изменять тип поля, а только его размер, этот подход должен работать:

Начиная с этих таблиц:

CREATE TABLE foo (id integer primary key, names varchar(10));
CREATE VIEW voo AS (SELECT id, names FROM foo);

\d foo и \d voo показывают длину как 10:

id     | integer               | not null
names  | character varying(10) | 

Теперь измените длину на 20 в таблице pg_attribute:

UPDATE pg_attribute SET atttypmod = 20+4
WHERE attrelid IN ('foo'::regclass, 'voo'::regclass)
AND attname = 'names';

(примечание: 20 + 4 - это какое-то сумасшедшее наследие postgresql, +4 является обязательным.)

Теперь \d foo показывает:

id     | integer               | not null
names  | character varying(20) | 

Бонус: это было намного быстрее, чем делать:

ALTER TABLE foo ALTER COLUMN names TYPE varchar(20);

Технически вы можете изменить размер столбца таблицы, не изменяя размер столбца представления, но не гарантируя, какие побочные эффекты будут иметь место;вероятно, лучше изменить их одновременно.

источник и более полное объяснение: http://sniptools.com/databases/resize-a-column-in-a-postgresql-table-without-changing-data

9 голосов
/ 27 февраля 2018

Я немного опоздал на вечеринку, но спустя годы после того, как этот вопрос был опубликован, блестящее решение было опубликовано в статье, на которую есть ссылка ниже (не моя - я просто благодарный бенефициар его блеска).

Я только что проверил это на объекте, на который ссылаются (на первом уровне) в 136 отдельных представлениях, и на каждое из этих представлений ссылаются в других представлениях.Решение прошло всего за несколько секунд.

Итак, прочитайте эту статью, скопируйте и вставьте таблицу и две перечисленные функции:

http://mwenus.blogspot.com/2014/04/postgresql-how-to-handle-table-and-view.html

Пример реализации:

alter table mdm.global_item_master_swap
alter column prod_id type varchar(128),
alter column prod_nme type varchar(512);

ОШИБКА: невозможно изменить тип столбца, используемого представлением или правилом. ДЕТАЛИ: правило _RETURN для представления toolbox_reporting. "Average_setcost" зависит от столбца "prod_id" **********Ошибка **********

ОШИБКА: невозможно изменить тип столбца, используемого представлением или правилом

А теперь для магии ниндзя PostgreSQL:

select util.deps_save_and_drop_dependencies('mdm', 'global_item_master_swap');


alter table mdm.global_item_master_swap
alter column prod_id type varchar(128),
alter column prod_nme type varchar(512);


select util.deps_restore_dependencies('mdm', 'global_item_master_swap');

- РЕДАКТИРОВАТЬ 11/13/2018 -

Похоже, что ссылка выше может быть мертвой.Вот код для этих двух процедур:

Таблица, в которой хранится DDL:

CREATE TABLE util.deps_saved_ddl
(
  deps_id serial NOT NULL,
  deps_view_schema character varying(255),
  deps_view_name character varying(255),
  deps_ddl_to_run text,
  CONSTRAINT deps_saved_ddl_pkey PRIMARY KEY (deps_id)
);

Сохранение и удаление:

CREATE OR REPLACE FUNCTION util.deps_save_and_drop_dependencies(
    p_view_schema character varying,
    p_view_name character varying)
  RETURNS void AS
$BODY$
declare
  v_curr record;
begin
for v_curr in 
(
  select obj_schema, obj_name, obj_type from
  (
  with recursive recursive_deps(obj_schema, obj_name, obj_type, depth) as 
  (
    select p_view_schema, p_view_name, null::varchar, 0
    union
    select dep_schema::varchar, dep_name::varchar, dep_type::varchar, recursive_deps.depth + 1 from 
    (
      select ref_nsp.nspname ref_schema, ref_cl.relname ref_name, 
      rwr_cl.relkind dep_type,
      rwr_nsp.nspname dep_schema,
      rwr_cl.relname dep_name
      from pg_depend dep
      join pg_class ref_cl on dep.refobjid = ref_cl.oid
      join pg_namespace ref_nsp on ref_cl.relnamespace = ref_nsp.oid
      join pg_rewrite rwr on dep.objid = rwr.oid
      join pg_class rwr_cl on rwr.ev_class = rwr_cl.oid
      join pg_namespace rwr_nsp on rwr_cl.relnamespace = rwr_nsp.oid
      where dep.deptype = 'n'
      and dep.classid = 'pg_rewrite'::regclass
    ) deps
    join recursive_deps on deps.ref_schema = recursive_deps.obj_schema and deps.ref_name = recursive_deps.obj_name
    where (deps.ref_schema != deps.dep_schema or deps.ref_name != deps.dep_name)
  )
  select obj_schema, obj_name, obj_type, depth
  from recursive_deps 
  where depth > 0
  ) t
  group by obj_schema, obj_name, obj_type
  order by max(depth) desc
) loop

  insert into util.deps_saved_ddl(deps_view_schema, deps_view_name, deps_ddl_to_run)
  select p_view_schema, p_view_name, 'COMMENT ON ' ||
  case
  when c.relkind = 'v' then 'VIEW'
  when c.relkind = 'm' then 'MATERIALIZED VIEW'
  else ''
  end
  || ' ' || n.nspname || '.' || c.relname || ' IS ''' || replace(d.description, '''', '''''') || ''';'
  from pg_class c
  join pg_namespace n on n.oid = c.relnamespace
  join pg_description d on d.objoid = c.oid and d.objsubid = 0
  where n.nspname = v_curr.obj_schema and c.relname = v_curr.obj_name and d.description is not null;

  insert into util.deps_saved_ddl(deps_view_schema, deps_view_name, deps_ddl_to_run)
  select p_view_schema, p_view_name, 'COMMENT ON COLUMN ' || n.nspname || '.' || c.relname || '.' || a.attname || ' IS ''' || replace(d.description, '''', '''''') || ''';'
  from pg_class c
  join pg_attribute a on c.oid = a.attrelid
  join pg_namespace n on n.oid = c.relnamespace
  join pg_description d on d.objoid = c.oid and d.objsubid = a.attnum
  where n.nspname = v_curr.obj_schema and c.relname = v_curr.obj_name and d.description is not null;

  insert into util.deps_saved_ddl(deps_view_schema, deps_view_name, deps_ddl_to_run)
  select p_view_schema, p_view_name, 'GRANT ' || privilege_type || ' ON ' || table_schema || '.' || table_name || ' TO ' || grantee
  from information_schema.role_table_grants
  where table_schema = v_curr.obj_schema and table_name = v_curr.obj_name;

  if v_curr.obj_type = 'v' then
    insert into util.deps_saved_ddl(deps_view_schema, deps_view_name, deps_ddl_to_run)
    select p_view_schema, p_view_name, 'CREATE VIEW ' || v_curr.obj_schema || '.' || v_curr.obj_name || ' AS ' || view_definition
    from information_schema.views
    where table_schema = v_curr.obj_schema and table_name = v_curr.obj_name;
  elsif v_curr.obj_type = 'm' then
    insert into util.deps_saved_ddl(deps_view_schema, deps_view_name, deps_ddl_to_run)
    select p_view_schema, p_view_name, 'CREATE MATERIALIZED VIEW ' || v_curr.obj_schema || '.' || v_curr.obj_name || ' AS ' || definition
    from pg_matviews
    where schemaname = v_curr.obj_schema and matviewname = v_curr.obj_name;
  end if;

  execute 'DROP ' ||
  case 
    when v_curr.obj_type = 'v' then 'VIEW'
    when v_curr.obj_type = 'm' then 'MATERIALIZED VIEW'
  end
  || ' ' || v_curr.obj_schema || '.' || v_curr.obj_name;

end loop;
end;
$BODY$
  LANGUAGE plpgsql VOLATILE
  COST 100;

Восстановление:

CREATE OR REPLACE FUNCTION util.deps_restore_dependencies(
    p_view_schema character varying,
    p_view_name character varying)
  RETURNS void AS
$BODY$
declare
  v_curr record;
begin
for v_curr in 
(
  select deps_ddl_to_run 
  from util.deps_saved_ddl
  where deps_view_schema = p_view_schema and deps_view_name = p_view_name
  order by deps_id desc
) loop
  execute v_curr.deps_ddl_to_run;
end loop;
delete from util.deps_saved_ddl
where deps_view_schema = p_view_schema and deps_view_name = p_view_name;
end;
$BODY$
  LANGUAGE plpgsql VOLATILE
  COST 100;
6 голосов
/ 12 октября 2011

Сегодня я столкнулся с этой проблемой и нашел способ избежать падения и воссоздания VIEW.Я не могу просто отбросить свой VIEW, потому что это главный VIEW, на котором построено много зависимых VIEW.Если не считать сценария перестройки DROP CASCADE, а затем воссоздания ВСЕХ моих ВИДОВ, это обходной путь.

Я изменил свой главный VIEW, чтобы использовать фиктивное значение для ошибочного столбца, изменил столбец в таблице и переключил свой VIEW обратно на столбец.При использовании такой настройки:

CREATE TABLE base_table
(
  base_table_id integer,
  base_table_field1 numeric(10,4)
);

CREATE OR REPLACE VIEW master_view AS 
  SELECT
    base_table_id AS id,
    (base_table_field1 * .01)::numeric AS field1
  FROM base_table;

CREATE OR REPLACE VIEW dependent_view AS 
  SELECT
    id AS dependent_id,
    field1 AS dependent_field1
  FROM master_view;

Попытка изменить тип base_table_field1 следующим образом:

ALTER TABLE base_table ALTER COLUMN base_table_field1 TYPE numeric(10,6);

Даст вам эту ошибку:

ERROR:  cannot alter type of a column used by a view or rule
DETAIL:  rule _RETURN on view master_view depends on column "base_table_field1"

Если вы изменитеmaster_view для использования фиктивного значения для столбца, подобного этому:

CREATE OR REPLACE VIEW master_view AS 
  SELECT
    base_table_id AS id,
    0.9999 AS field1
  FROM base_table;

Затем запустите alter:

ALTER TABLE base_table ALTER COLUMN base_table_field1 TYPE numeric(10,6);

и переключите свой вид назад:

CREATE OR REPLACE VIEW master_view AS 
  SELECT
    base_table_id AS id,
    (base_table_field1 * .01)::numeric AS field1
  FROM base_table;

Все зависит от того, имеет ли ваш master_view явный тип, который не изменяется.Поскольку мой VIEW использует '(base_table_field1 * .01) :: numeric AS field1', он работает, но 'base_table_field1 AS field1' не будет, потому что тип столбца изменяется.Этот подход может помочь в некоторых случаях, таких как мой.

...