【问题标题】:How to add a new Column in a table after the 2nd or 3rd column in the Table using postgres?如何使用 postgres 在表中的第 2 或第 3 列之后在表中添加新列?
【发布时间】:2010-11-17 15:08:18
【问题描述】:

如何使用 postgres 在表格中的第 2 或第 3 列之后添加新列?

我的代码如下所示

ALTER TABLE n_domains ADD COLUMN contract_nr int after owner_id

【问题讨论】:

  • 列的顺序在关系数据库中是完全不相关的——这只是在工具中显示它们的问题。数据库表没有任何列的排序。
  • @marc_s,不正确。物理顺序与性能相关。和当前的 PostgreSQL 使用相同的物理和逻辑顺序。 wiki.postgresql.org/wiki/Alter_column_position

标签: postgresql


【解决方案1】:

不,没有直接的方法可以做到这一点。这是有原因的 - 每个查询都应该以它需要的任何顺序(和格式等)列出它需要的所有字段,从而使一个表中列的顺序无关紧要。

如果你真的需要这样做,我可以想到一种解决方法:

  • 转储并保存相关表的描述(使用pg_dump --schema-only --table=<schema.table> ...
  • 在保存的定义中添加所需的列
  • 重命名已保存定义中的表,以免在尝试创建旧表时与旧表的名称发生冲突
  • 使用此定义创建新表
  • 使用'INSERT INTO <new_table> SELECT field1, field2, <default_for_new_field>, field3,... FROM <old_table>' 将旧表中的数据填充到新表中;
  • 重命名旧表
  • 将新表重命名为原名
  • 在确保一切正常后,最终删除旧的重命名表

【讨论】:

  • “每个查询都应该以任何顺序列出它需要的所有字段” → 这很容易,只要您不必与其他人一起工作。
  • 虽然列的顺序可能与使用数据库的应用程序无关。如果在调查表结构或数据时按顺序列出类似的列,对于 DBA 来说会更好看。我个人觉得很烦人,因为我有几个时间列没有在视觉上分组,因为它们是后来添加的。仅仅因为 Postgres 不支持在某个位置添加列而重新创建数据库对我来说感觉很奇怪,特别是如果它背后的原因是强迫人们在他们的查询中明确列出列。明确一点:我认为这不是原因。
  • “不,没有直接的方法可以做到这一点。这是有原因的”。 这不正确,根据wiki.postgresql.org/wiki/Alter_column_position。 “允许重新排序列位置的想法并不是 postgresql 开发人员所反对的,它更多的是没有人站出来做这项工作的情况......能够改变列位置的主要原因有两个在 postgres 中会很有用:可以通过在表格的开头放置固定大小的列来优化物理布局...排序列可以更轻松地使用表格...”此外,67k 视图 :)
  • 如果旧表有serval列被其他表引用怎么办?
【解决方案2】:

列的顺序并非无关紧要,将固定宽度的列放在表格的前面可以优化数据的存储布局,还可以让您在应用程序代码之外更轻松地处理数据。

PostgreSQL 不支持更改列顺序(参见 PostgreSQL wiki 上的 Alter column position);如果表相对孤立,最好的办法是重新创建表:

CREATE TABLE foobar_new ( ... );
INSERT INTO foobar_new SELECT ... FROM foobar;
DROP TABLE foobar CASCADE;
ALTER TABLE foobar_new RENAME TO foobar;

如果您对表定义了很多视图或约束,您可以在新列之后重新添加所有列并删除原始列(有关示例,请参见 PostgreSQL wiki)。

【讨论】:

    【解决方案3】:

    这里真正的问题是它还没有完成。目前 PostgreSQL 的逻辑排序与物理排序相同。这是有问题的,因为您无法获得不同的逻辑顺序,但更糟糕的是,因为表不是物理自动打包的,因此通过移动列可以获得不同的性能特征。

    争辩说它是那种设计意图是没有意义的。当提交可接受的补丁时,它可能会在某些时候发生变化。

    综上所述,依赖列的序号定位是一个好主意,逻辑的还是物理的?一定不行。在生产代码中,您永远不应该使用隐式排序或*。为什么让代码比它需要的更脆弱?正确性应始终比保存几次击键更重要。

    作为一种解决方法,您实际上可以通过 recreating the table, or through the "add and reorder" game 修改列排序

    另见,

    【讨论】:

      【解决方案4】:

      列顺序与我有关,所以我创建了这个函数。看看有没有帮助。它适用于索引、主键和触发器。缺少视图和外键以及其他功能。

      例子:

      SELECT xaddcolumn('table', 'col3 int NOT NULL DEFAULT 0', 'col2');
      

      源代码:

      CREATE OR REPLACE FUNCTION xaddcolumn(ptable text, pcol text, pafter text)  RETURNS void AS $BODY$
      DECLARE
          rcol RECORD;
          rkey RECORD;
          ridx RECORD;
          rtgr RECORD;
          vsql text;
          vkey text;
          vidx text;
          cidx text;
          vtgr text;
          ctgr text;
          etgr text;
          vseq text;
          vtype text;
          vcols text;
      BEGIN
          EXECUTE 'CREATE TABLE zzz_' || ptable || ' AS SELECT * FROM ' || ptable;
          --colunas
          vseq = '';
          vcols = '';
          vsql = 'CREATE TABLE ' || ptable || '(';
          FOR rcol IN SELECT column_name as col, udt_name as coltype, column_default as coldef,
              is_nullable as is_null, character_maximum_length as len,
              numeric_precision as num_prec, numeric_scale as num_scale
              FROM information_schema.columns
              WHERE table_name = ptable
              ORDER BY ordinal_position
          LOOP
              vtype = rcol.coltype;
              IF (substr(rcol.coldef,1,7) = 'nextval') THEN
                  vtype = 'serial';
                  vseq = vseq || 'SELECT setval(''' || ptable || '_' || rcol.col || '_seq'''
                      || ', max(' || rcol.col || ')) FROM ' || ptable || ';';
              ELSIF (vtype = 'bpchar') THEN
                  vtype = 'char';
              END IF;
              vsql = vsql || E'\n' || rcol.col || ' ' || vtype;
              IF (vtype in ('varchar', 'char')) THEN
                  vsql = vsql || '(' || rcol.len || ')';
              ELSIF (vtype = 'numeric') THEN
                  vsql = vsql || '(' || rcol.num_prec || ',' || rcol.num_scale || ')';
              END IF;
              IF (rcol.is_null = 'NO') THEN
                  vsql = vsql || ' NOT NULL';
              END IF;
              IF (rcol.coldef <> '' AND vtype <> 'serial') THEN
                  vsql = vsql || ' DEFAULT ' || rcol.coldef;
              END IF;
              vsql = vsql || E',';
              vcols = vcols || rcol.col || ',';
              --
              IF (rcol.col = pafter) THEN
                  vsql = vsql || E'\n' || pcol || ',';
              END IF;
          END LOOP;
          vcols = substr(vcols,1,length(vcols)-1);
          --keys
          vkey = '';
          FOR rkey IN SELECT constraint_name as name, column_name as col
              FROM information_schema.key_column_usage
              WHERE table_name = ptable
          LOOP
              IF (vkey = '') THEN
                  vkey = E'\nCONSTRAINT ' || rkey.name || ' PRIMARY KEY (';
              END IF;
              vkey = vkey || rkey.col || ',';
          END LOOP;
          IF (vkey <> '') THEN
              vsql = vsql || substr(vkey,1,length(vkey)-1) || ') ';
          END IF;
          vsql = substr(vsql,1,length(vsql)-1) || ') WITHOUT OIDS';
          --index
          vidx = '';
          cidx = '';
          FOR ridx IN SELECT s.indexrelname as nome, a.attname as col
              FROM pg_index i LEFT JOIN pg_class c ON c.oid = i.indrelid
              LEFT JOIN pg_attribute a ON a.attrelid = c.oid AND a.attnum = ANY(i.indkey)
              LEFT JOIN pg_stat_user_indexes s USING (indexrelid)
              WHERE c.relname = ptable AND i.indisunique != 't' AND i.indisprimary != 't'
              ORDER BY s.indexrelname
          LOOP
              IF (ridx.nome <> cidx) THEN
                  IF (vidx <> '') THEN
                      vidx = substr(vidx,1,length(vidx)-1) || ');';
                  END IF;
                  cidx = ridx.nome;
                  vidx = vidx || E'\nCREATE INDEX ' || cidx || ' ON ' || ptable || ' (';
              END IF;
              vidx = vidx || ridx.col || ',';
          END LOOP;
          IF (vidx <> '') THEN
              vidx = substr(vidx,1,length(vidx)-1) || ')';
          END IF;
          --trigger
          vtgr = '';
          ctgr = '';
          etgr = '';
          FOR rtgr IN SELECT trigger_name as nome, event_manipulation as eve,
              action_statement as act, condition_timing as cond
              FROM information_schema.triggers
              WHERE event_object_table = ptable
          LOOP
              IF (rtgr.nome <> ctgr) THEN
                  IF (vtgr <> '') THEN
                      vtgr = replace(vtgr, '_@eve_', substr(etgr,1,length(etgr)-3));
                  END IF;
                  etgr = '';
                  ctgr = rtgr.nome;
                  vtgr = vtgr || 'CREATE TRIGGER ' || ctgr || ' ' || rtgr.cond || ' _@eve_ '
                      || 'ON ' || ptable || ' FOR EACH ROW ' || rtgr.act || ';';
              END IF;
              etgr = etgr || rtgr.eve || ' OR ';
          END LOOP;
          IF (vtgr <> '') THEN
              vtgr = replace(vtgr, '_@eve_', substr(etgr,1,length(etgr)-3));
          END IF;
          --exclui velha e cria nova
          EXECUTE 'DROP TABLE ' || ptable;
          IF (EXISTS (SELECT sequence_name FROM information_schema.sequences
              WHERE sequence_name = ptable||'_id_seq'))
          THEN
              EXECUTE 'DROP SEQUENCE '||ptable||'_id_seq';
          END IF;
          EXECUTE vsql;
          --dados na nova
          EXECUTE 'INSERT INTO ' || ptable || '(' || vcols || ')' ||
              E'\nSELECT ' || vcols || ' FROM zzz_' || ptable;
          EXECUTE vseq;
          EXECUTE vidx;
          EXECUTE vtgr;
          EXECUTE 'DROP TABLE zzz_' || ptable;
      END;
      $BODY$ LANGUAGE plpgsql VOLATILE COST 100;
      

      【讨论】:

      • 我不得不稍微修改一下,但不幸的是它不能处理像“placeId”这样的列名......我已经从rtgr 和`|| 中删除了, condition_timing as cond。 ' ' ||来自vtgr 的rtgr.cond` 添加了` || 'CASCADE'` 来处理EXECUTE 'DROP TABLE ' || ptable; btw 上的链接表,这个不错的函数没有用,因为它似乎只处理小写的列名......
      • 无论如何@Samuel Cunha 感谢分享,但如果您能解决列名问题,那就太好了。我自己在 postgresql 方面不够熟练。
      【解决方案5】:

      @Jeremy Gustie 上面的解决方案几乎可以工作,但是如果序数关闭(或者如果重新排序的序数使不兼容的类型匹配,则完全失败)。试试看:

      CREATE TABLE test1 (one varchar, two varchar, three varchar);
      CREATE TABLE test2 (three varchar, two varchar, one varchar);
      INSERT INTO test1 (one, two, three) VALUES ('one', 'two', 'three');
      INSERT INTO test2 SELECT * FROM test1;
      SELECT * FROM test2;
      

      结果说明问题:

      testdb=> select * from test2;
       three | two |  one
      -------+-----+-------
       one   | two | three
      (1 row)
      

      您可以通过在插入中指定列名来解决此问题:

      INSERT INTO test2 (one, two, three) SELECT * FROM test1;
      

      这给了你真正想要的:

      testdb=> select * from test2;
       three | two | one
      -------+-----+-----
       three | two | one
      (1 row)
      

      正如我在上面对 peufeu 回复的评论中指出的那样,当您的遗产不这样做时,问题就出现了。

      更新: 我突然想到,您可以通过在 SELECT 子句中指定列名来对 INSERT 子句中的列名执行相同的操作。您只需重新排序它们以匹配目标表中的序数:

      INSERT INTO test2 SELECT three, two, one FROM test1;
      

      当然你也可以做到非常明确:

      INSERT INTO test2 (one, two, three) SELECT one, two, three FROM test1;
      

      这会给您与上面相同的结果,并且列值正确匹配。

      【讨论】:

        【解决方案6】:

        在关系数据库中,列的顺序是完全不相关的

        是的。

        例如,如果你使用 Python,你会这样做:

        cursor.execute( "SELECT id, name FROM users" )
        for id, name in cursor:
            print id, name
        

        或者你会这样做:

        cursor.execute( "SELECT * FROM users" )
        for row in cursor:
            print row['id'], row['name']
        

        但是没有理智的人会使用这样的位置结果:

        cursor.execute( "SELECT * FROM users" )
        for id, name in cursor:
           print id, name
        

        【讨论】:

        • 这完全不真实。这适用于选择您显示的方式,但在没有指定列名的情况下看到插入并不少见,例如插入表值(1、2、3、4)。如果表列序号发生变化,那么这种插入查询样式就会失败。还值得注意的是,并非每个开发人员都是理智的,如果您碰巧从其中一位开发人员那里继承代码......
        • @SpankyQuigman 这样做简直是疯了,peufeu 就是这么说的。
        【解决方案7】:

        嗯,这对 DBA 来说是一种视觉上的好东西,并且可以在引擎中实现,而性能损失很小。将列顺序表添加到pg_catalog 或最适合的位置。将其保存在内存中并在某些查询之前使用它。为什么要考虑这么小的眼睛糖果。

        【讨论】:

          【解决方案8】:

          @Milen A. Radev

          具有一组列顺序的无关需求并不总是由拉取它们的查询定义。在来自pg_fetch_row 的值中不包括关联的列名,因此需要由 SQL 语句定义列。

          一个简单的select * from 需要先天了解表结构,如果列的顺序发生变化,有时会导致问题。

          使用pg_fetch_assoc 是一种更可靠的方法,因为您可以引用列名,因此使用简单的select * from

          【讨论】:

          • 这种需求并非无关紧要。当我手动检查数据时,我只想使用SELECT *,但我希望更有趣的列排在第一位。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-11-24
          • 1970-01-01
          • 2019-09-12
          • 2019-11-09
          • 1970-01-01
          • 2022-10-31
          相关资源
          最近更新 更多