web-dev-qa-db-fra.com

Problème avec Postgres ALTER TABLE

J'ai un problème avec ALTER TABLE dans postgre. Je veux changer la taille de la colonne varchar. Lorsque j'essaie de le faire, il indique que la vue dépend de cette colonne. Je ne peux pas laisser tomber la vue car tout le reste en dépend. Y a-t-il un autre moyen que de tout laisser tomber et de le recréer?

Je viens de trouver une option, qui est de supprimer la table se joignant à la vue, quand je ne changerai pas les colonnes retournées, je peux le faire. Mais encore, il y aura plus de vues que je devrai changer. N'y a-t-il rien comment puis-je dire qu'il doit être différé et vérifié avec commit?

35
martin.malek

J'ai rencontré ce problème et je n'ai trouvé aucun moyen de le contourner. Malheureusement, pour autant que je sache, il faut supprimer les vues, modifier le type de colonne sur la table sous-jacente, puis recréer les vues. Cela peut se produire entièrement en une seule transaction.

Le report de contrainte ne s'applique pas à ce problème. En d'autres termes, même SET CONSTRAINTS ALL DEFERRED n'a aucun impact sur cette limitation. Pour être précis, le report de contrainte ne s'applique pas au contrôle de cohérence qui imprime ERROR: cannot alter type of a column used by a view or rule quand on essaie de modifier le type d'une colonne sous-jacente à une vue.

25
Dan LaRocque

Je suis un peu en retard à la fête, mais des années après la publication de cette question, une solution brillante a été publiée via un article référencé ci-dessous (pas le mien - je suis tout simplement un reconnaissant bénéficiaire de son éclat).

Je viens de tester cela sur un objet qui est référencé (au premier niveau) dans 136 vues distinctes, et chacune de ces vues est référencée dans d'autres vues. La solution a fonctionné en quelques secondes.

Alors, lisez cet article et copiez et collez le tableau et les deux fonctions répertoriées:

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

Exemple d'implémentation:

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

ERREUR: impossible de modifier le type d'une colonne utilisée par une vue ou une règle DÉTAIL: règle _RETURN sur la vue toolbox_reporting. "Average_setcost" dépend de la colonne "prod_id" ********** Erreur ******** **

ERREUR: impossible de modifier le type d'une colonne utilisée par une vue ou une règle

Et maintenant, pour la magie du ninja de 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');

- EDIT 13/11/2018 -

Il semble que le lien ci-dessus soit mort. Voici le code des deux procédures:

Table qui stocke 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)
);

Enregistrer et déposer:

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;

Restaurer:

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;
13
Hambone

Si vous n'avez pas besoin de changer le type du champ, mais juste sa taille, cette approche devrait fonctionner:

En commençant par ces tableaux:

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

\d foo et \d voo les deux affichent la longueur à 10:

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

Modifiez maintenant les longueurs à 20 dans le pg_attribute table:

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

(Remarque: le 20 + 4 est quelque chose d'héritage postgresql fou, le +4 est obligatoire.)

Maintenant \d foo spectacles:

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

Bonus: c'était plus rapide que de le faire:

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

Techniquement, vous pouvez modifier la taille de la colonne du tableau sans modifier la taille de la colonne de vue, mais aucune garantie sur les effets secondaires qui en résulteront; il vaut probablement mieux les changer tous les deux à la fois.

source et explication plus complète: http://sniptools.com/databases/resize-a-column-in-a-postgresql-table-without-changing-data

12
craigds

J'ai rencontré ce problème aujourd'hui et j'ai trouvé une solution pour éviter de supprimer et de recréer la VUE. Je ne peux pas simplement laisser tomber ma VUE car c'est une VUE principale qui a de nombreuses VUES dépendantes construites dessus. À moins d'avoir un script de reconstruction pour DROP CASCADE puis recréer TOUTES mes VUES, c'est un travail autour.

Je change ma VUE principale pour utiliser une valeur fictive pour la colonne incriminée, modifie la colonne dans le tableau et bascule à nouveau ma VUE dans la colonne. En utilisant une configuration comme celle-ci:

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;

Essayer de modifier le type base_table_field1 comme ceci:

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

Vous donnera cette erreur:

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"

Si vous modifiez master_view pour utiliser une valeur fictive pour la colonne comme ceci:

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

Ensuite, exécutez votre alter:

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

Et revenez en arrière:

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

Tout dépend si votre master_view a un type explicite qui ne change pas. Puisque ma VUE utilise '(base_table_field1 * .01) :: numeric AS field1' cela fonctionne, mais 'base_table_field1 AS field1' ne le ferait pas car le type de colonne change. Cette approche pourrait aider dans certains cas comme le mien.

6
bendiy