【问题标题】:Problem with Postgres ALTER TABLEPostgres ALTER TABLE 的问题
【发布时间】:2011-03-15 16:55:09
【问题描述】:

我对 postgre 中的 ALTER TABLE 有一个问题。我想更改 varchar 列的大小。当我尝试这样做时,它说视图依赖于该列。我不能放弃这个观点,因为其他事物都依赖于它。除了删除所有内容并重新创建之外,还有其他方法吗?

我刚刚找到了一个选项,即从视图中删除表连接,当我不会更改返回的列时,我可以这样做。但是,我仍然需要改变更多的观点。没有什么我能说它应该被推迟并通过提交检查吗?

【问题讨论】:

  • 复制视图查询,然后删除它并对表进行更改
  • 我刚刚发现EXISTS ( SELECT * FROM ... 的视图将导致真的不必要 依赖于exists 子查询中涉及的所有表的所有列,并且使用EXISTS (SELECT 1 FROM ... 创建视图可能是一个更好的主意。

标签: postgresql dependencies alter-table


【解决方案1】:

我遇到了这个问题,但找不到任何解决方法。不幸的是,据我所知,必须删除视图,更改基础表上的列类型,然后重新创建视图。这可以完全在单个事务中发生。

约束延迟不适用于此问题。换句话说,即使SET CONSTRAINTS ALL DEFERRED 对这个限制也没有影响。具体而言,约束延迟不适用于在尝试更改视图下的列的类型时打印ERROR: cannot alter type of a column used by a view or rule 的一致性检查。

【讨论】:

  • 我们发现了this 代码,它使得依赖视图的删除和重新创建变得更加容易——它将定义存储在一个表中,然后使用该定义重新构建视图。它包括赠款和 cmets,但不包括物化索引。此外,如果运行该函数的角色对视图没有所有权(或同等)权限,您将收到错误消息。
【解决方案2】:

我今天遇到了这个问题,并找到了一种解决方法来避免删除和重新创建 VIEW 。我不能只是放弃我的 VIEW,因为它是一个主 VIEW,在其上构建了许多相关的 VIEW。没有重建脚本来 DROP CASCADE 然后重新创建我的所有视图,这是一种解决方法。

我将我的主视图更改为对有问题的列使用虚拟值,更改了表中的列,并将我的视图切换回该列。使用这样的设置:

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 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' 不会因为列类型发生变化。这种方法在某些情况下可能会有所帮助,例如我的情况。

【讨论】:

  • 这比删除视图、更改表并再次创建视图有什么好处?我发现情况更糟,必须查看视图的 DDL 并查找列的实例。删除时,您只需保留原始视图的 DDL 的副本,以便再次创建它。
  • 这比放弃视图有什么好处?第一行......“它是一个主视图,在其上构建了许多依赖视图。”也就是说,drop 也会级联到那些依赖视图。
【解决方案3】:

如果您不需要更改字段的类型,只需更改其大小,则此方法应该可行:

从这些表格开始:

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) | 

现在将pg_attribute 表中的长度更改为 20:

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

【讨论】:

  • 您应该尽可能避免手动更改目录(例如 pg_attribute)。在您最意想不到的时候,确实存在出错并导致错误(包括崩溃和数据损坏)的风险。仅作为最后的手段,在查阅源代码以确保您没有遗漏任何东西之后。在没有任何免责声明的情况下提出此建议是不负责任的。
  • 这就是数据引擎在幕后的真正运作方式。您不是在更改 TYPE,而是更改了大小。由于它是 VARCHAR,因此不会对数据造成损害。 +1
【解决方案4】:

我参加聚会有点晚了,但在这个问题发布多年后,通过下面引用的一篇文章发布了一个绝妙的解决方案(不是我的——我只是感谢他的才华)。

我刚刚在 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);

错误:无法更改视图或规则使用的列的类型详细信息: 视图 toolbox_reporting 上的规则 _RETURN。“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');

-- 编辑 2018 年 11 月 13 日--

上面的链接似乎已失效。下面是这两个过程的代码:

存储 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)
);

保存并删除:

-- 2020 年 8 月 28 日编辑-- -- 这停止与 Pg12 一起工作。以下修复将 p_view_schema 和 p_view_name 的参数从 varchar 更改为 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$

恢复:

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;

【讨论】:

  • 你是一个伟大的灵魂。
  • @Hambone 能不能分享一下上面两个函数用到的表的结构,链接失效了,只贴了函数代码。
  • 2020 年仍在使用它,尽管我将这一行:select p_view_schema, p_view_name, 'GRANT ' || privilege_type || ' ON ' || table_schema || '.' || table_name || ' TO ' || grantee 更改为 select p_view_schema, p_view_name, 'GRANT ' || privilege_type || ' ON ' || table_schema || '.' || table_name || ' TO "' || grantee || '"',因为我的用户名中有句号。您仍然可以通过您的 psql 用户名进行 SQL 注入,因此请谨慎使用此脚本!
  • 这太棒了 - 我完美地更新了具有数百个依赖视图的表。
  • @RajshriMohanKS - 这似乎是 Postgres 12 的问题。我正在调查它,因为我遇到了同样的错误
【解决方案5】:

我想对第二个答案发表评论,但由于我对 stackoverflow 太陌生,所以不能发表评论,所以我的评论如下: 对于那些对该答案中提到的原始文章感兴趣的人,blogspot 条目不再可用,但回程机器仍然存储它:https://web.archive.org/web/20180323155900/http://mwenus.blogspot.com/2014/04/postgresql-how-to-handle-table-and-view.html 以下是文章本身,以防archive.org在未来某个时间点被关闭: 2014-04-22 PostgreSQL:如何处理表和视图依赖关系 PostgreSQL 在修改现有对象时非常严格。很多时候,当您尝试 ALTER TABLE 或 REPLACE VIEW 时,它会告诉您不能这样做,因为还有另一个对象(通常是视图或实体化视图),这取决于您要修改的对象。似乎唯一的解决方案是 DROP 依赖对象,对目标对象进行所需的更改,然后重新创建已删除的对象。

繁琐繁琐,因为那些依赖的对象可以有进一步的依赖,也可能有其他的依赖等等。我创建了utility functions,它可以在这种情况下提供帮助。

用法很简单——你只需要调用: 选择 deps_save_and_drop_dependencies(p_schema_name, p_object_name); 您必须传递两个参数:模式的名称和该模式中对象的名称。该对象可以是表格、视图或物化视图。该函数将删除依赖于 p_schema_name.p_object_name 的所有视图和实体化视图,并保存 DDL,将它们恢复到帮助表中。

当你想恢复那些被丢弃的对象时(例如当你修改完 p_schema_name.p_object_name 时),你只需要再做一个简单的调用: 选择 deps_restore_dependencies(p_schema_name, p_object_name); 并且丢弃的对象将被重新创建。

这些函数负责: 依赖层次 跨层次结构删除和创建视图/物化视图的正确顺序 在视图/物化视图上恢复 cmets 和授权 Click here for a working sqlfiddle example 或查看 this gist 以获取完整的源代码。

作者:Mateusz Wenus o 19:32

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-12-21
    • 1970-01-01
    • 1970-01-01
    • 2015-11-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-05-05
    相关资源
    最近更新 更多