Problem with Postgres ALTER TABLE

后端 未结 4 1886
死守一世寂寞
死守一世寂寞 2021-02-02 06:47

I have one problem with the ALTER TABLE in postgre. I want to change size of the varchar column. When I try to do this, It says that the view is dependent on that column. I can\

相关标签:
4条回答
  • 2021-02-02 07:06

    I ran into this problem today and found a work around to avoid dropping and recreating the VIEW . I cannot just drop my VIEW because it is a master VIEW that has many dependent VIEWs built on top of it. Short of having a rebuild script to DROP CASCADE and then recreate ALL of my VIEWs this is a work around.

    I change my master VIEW to use a dummy value for the offending column, altered the column in the table, and switched my VIEW back to the column. Using a setup like this:

    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;
    

    Trying to alter base_table_field1 type like this:

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

    Will give you this error:

    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"
    

    If you change master_view to use a dummy value for the column like this:

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

    Then run your alter:

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

    And switch your view back:

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

    It all depends on if your master_view has an explicit type that does not change. Since my VIEW uses '(base_table_field1 * .01)::numeric AS field1' it works, but 'base_table_field1 AS field1' would not because the column type changes. This approach might help in some cases like mine.

    0 讨论(0)
  • 2021-02-02 07:07

    I have run into this problem and couldn't find any way around it. Unfortunately, as best I can tell, one must drop the views, alter the column type on the underlying table, and then recreate the views. This can happen entirely in a single transaction.

    Constraint deferral doesn't apply to this problem. In other words, even SET CONSTRAINTS ALL DEFERRED has no impact on this limitation. To be specific, constraint deferral does not apply to the consistency check that prints ERROR: cannot alter type of a column used by a view or rule when one tries to alter the type of a column underlying a view.

    0 讨论(0)
  • 2021-02-02 07:17

    I'm a little late to the party, but years after this question was posted, a brilliant solution was posted via an article referenced below (not mine -- I'm simply a thankful beneficiary of his brilliance).

    I just tested this on an object that is referenced (on the first level) in 136 separate views, and each of those views is referenced in other views. The solution ran in mere seconds.

    So, read this article and copy and paste the table and two functions listed:

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

    Implementation example:

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

    ERROR: cannot alter type of a column used by a view or rule DETAIL: rule _RETURN on view toolbox_reporting."Average_setcost" depends on column "prod_id" ********** Error **********

    ERROR: cannot alter type of a column used by a view or rule

    And now for the PostgreSQL ninja's magic:

    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 11/13/2018 --

    It appears the link above might be dead. Here is the code for the two procedures:

    Table that stores 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)
    );
    

    Save and Drop:

    -- Edit 8/28/2020 -- -- This stopped working with Pg12. The fix is below to change the parameters of p_view_schema and p_view_name from varchar to name:

    CREATE OR REPLACE FUNCTION util.deps_save_and_drop_dependencies(
        p_view_schema name, p_view_name name)
        RETURNS void
        LANGUAGE plpgsql
        COST 100
    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$
    

    Restore:

    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;
    
    0 讨论(0)
  • 2021-02-02 07:18

    If you don't need to change the type of the field, but just the size of it, this approach should work:

    Starting with these tables:

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

    \d foo and \d voo both show the length as 10:

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

    Now change the lengths to 20 in the pg_attribute table:

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

    (note: the 20+4 is some crazy postgresql legacy thing, the +4 is compulsory.)

    Now \d foo shows:

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

    Bonus: that was waaay faster than doing:

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

    Technically you can change the size of the table column without changing the size of the view column, but no guarantees on what side effects that will have; it's probably best to change them both at once.

    source and fuller explanation: http://sniptools.com/databases/resize-a-column-in-a-postgresql-table-without-changing-data

    0 讨论(0)
提交回复
热议问题