web-dev-qa-db-ja.com

シリアル列の個別のシーケンスでテーブルをコピーする

列管理ツールを作成しているときに、PostgreSQLでテーブルをすばやく複製する必要に出会ったので、非テストテーブルで新しいツールをテストしませんでした。テーブルで最終的に使用する予定の新しい列ツールを効果的にテストするためにpartsこの新しいツールを作成してpartsを複製し、parts1テーブルを作成しました。最終的にすべての問題が解決されたと思ったときに、列ツールがテーブルを削除するときに次のエラーが発生しました。

エラー:他のオブジェクトがテーブルパーツに依存しているため、テーブルパーツを削除できません詳細:テーブルパーツ1の列IDのデフォルトは、シーケンスparts_id_seq1に依存しています

私は1日の大半を このソリューション に向けて費やしてきたので、簡単に言えば、文字列関数を使用してSEQUENCE_NAME変数の名前を変更し、partsテーブルの関連付けを解除できますpartsテーブルまたはそれよりも複雑な問題ですか?これがクエリです:

DO $$
  DECLARE
    SEQUENCE_NAME VARCHAR;
  BEGIN
    SELECT s.relname INTO SEQUENCE_NAME
    FROM pg_class AS s JOIN pg_depend d ON d.objid = s.oid 
    INNER JOIN pg_class AS t ON d.objid = s.oid AND d.refobjid = t.oid 
    INNER JOIN pg_attribute AS a ON (d.refobjid, d.refobjsubid) = (a.attrelid, a.attnum) 
    INNER JOIN pg_namespace AS n ON n.oid = s.relnamespace 
    WHERE s.relkind = 'S' 
    AND n.nspname = 'public' 
    AND t.relname='parts';

    LOCK TABLE parts;
    CREATE TABLE parts1 (LIKE parts INCLUDING ALL);
    INSERT INTO parts1 SELECT * FROM parts;
    PERFORM setval(SEQUENCE_NAME::regclass, (SELECT max(id) FROM parts)+1);
  END;
$$ LANGUAGE plpgsql;
2
John

できるだけ近くにコピーを作成するには、必須_INCLUDING ALL_CREATE TABLE .. (LIKE ..)デフォルトでコピーしたい列はいくつでもある可能性があるためです。

serial列に独自の独立したシーケンスを取得するだけで十分です。これは、多くの意味があり、おそらくデフォルトの動作になるはずです。今のところ、これでうまくいくはずです:

任意のテーブルをコピーする関数

新しい名前と独立したserial(存在する場合)を使用して、指定されたテーブル(存在する必要があります)をコピーします。
データは含まれていません。それをコピーするのも簡単です。

_CREATE OR REPLACE FUNCTION f_copy_tbl(_tbl regclass, _newtbl text)
  RETURNS void AS
$func$
DECLARE
   _sql text;
BEGIN

-- Copy table
EXECUTE format('CREATE TABLE %I (LIKE %s INCLUDING ALL);', _newtbl, _tbl);

-- Fix serial columns, if any    
SELECT INTO _sql
       string_agg('CREATE SEQUENCE ' || seq, E';\n') || E';\n'
    || string_agg(format('ALTER SEQUENCE %s OWNED BY %I.%I'
                        , seq, _newtbl, a.attname), E';\n') || E';\n'
    || 'ALTER TABLE ' || quote_ident(_newtbl) || E'\n  '
    || string_agg(format($$ALTER %I SET DEFAULT nextval('%s'::regclass)$$
                                 , a.attname, seq), E'\n, ')
FROM   pg_attribute  a
JOIN   pg_attrdef    ad ON ad.adrelid = a.attrelid
                       AND ad.adnum   = a.attnum
     , quote_ident(_newtbl || '_' || a.attname || '_seq') AS seq  -- new seq name
WHERE  a.attrelid = _tbl
AND    a.attnum > 0
AND    NOT a.attisdropped
AND    a.atttypid = ANY ('{int,int8,int2}'::regtype[])
AND    ad.adsrc = 'nextval('''
         || (pg_get_serial_sequence (a.attrelid::regclass::text, a.attname))::regclass
         || '''::regclass)'
;

IF _sql IS NOT NULL THEN
   EXECUTE _sql;
END IF;

END
$func$  LANGUAGE plpgsql VOLATILE;
_

コール:

_SELECT f_copy_tbl('tbl', 'tbl1');
_

次の形式のSQLコードを生成して実行します。

_CREATE TABLE tbl1 (LIKE tbl INCLUDING ALL);
-- only if there are serial columns:
CREATE SEQUENCE tbl1_tbl_id_seq;     -- one line per serial type ..
CREATE SEQUENCE "tbl1_Odd_COL_seq";  -- .. two in this example
ALTER SEQUENCE tbl1_tbl_id_seq OWNED BY tbl1.tbl_id;
ALTER SEQUENCE "tbl1_Odd_COL_seq" OWNED BY tbl1."Odd_COL";
ALTER TABLE tbl1
  ALTER tbl_id SET DEFAULT nextval('tbl1_tbl_id_seq'::regclass)
, ALTER "Odd_COL" SET DEFAULT nextval('"tbl1_Odd_COL_seq"'::regclass);
_
  • serial列のみが独自のシーケンスを取得します。他の列のデフォルトは変更されずにコピーされます-列が所有していないシーケンスからのnextval()を含むか、serialとは何らかの点で異なります。

  • この関数はSQLインジェクションに対して安全であり、任意のテーブル名と列名で機能するはずです。

SQLフィドル

LIKE機能でserial列に個別のシーケンスを作成すると便利なオプションです。フレンドリーな機能のリクエストを pgsql-generalに投稿してください。

2

少なくとも直接PHP関数(私はビルドしているツールをGithubのようなものにリリースする予定です)をリリースしたかったのですが、他の多くのことをする時間はまだありません少なくともここに投稿するよりもいいでしょう。うまくいけば、他の時間を節約でき、自由に改善を提案できます。コードの安全性はわかりません(これは、開発のためだけに使用しているツールであり、現在、私が使用しているクライアントのためのものです)私はイントラネットのみのWebサイトをセットアップしています)が、Erwin BrandstetterおよびJoishi Bodioによる他の質問/回答からのアドバイスに注意を払うために最善を尽くしました。

URLリクエストの例:

db_table_duplicate.php?db=database_name&table1=parts2&table2=parts3

PHP関数を呼び出すだけで、PostgreSQLツールが実行するアクションを頻繁に繰り返すため、情報を$_GET配列。

function dbtd_04_commit()
{
 echo '<p><strong>Step 4/4:</strong> Duplicating the table `'.$_GET['table1'].'`...</p>'."\n";
 $table2 = pg_escape_string($_SESSION['db'],$_GET['table2']);
 $query1 = "SELECT table_name FROM information_schema.tables WHERE table_schema NOT IN ('pg_catalog', 'information_schema') AND table_name='$table2';";
 $result1 = pg_query($_SESSION['db'],$query1);

 if ($result1)
 {
  $count1 = pg_num_rows($result1);
  $table1 = pg_escape_string($_SESSION['db'],$_GET['table1']);

  if ($count1>0)
  {
   echo '<div><p><strong class="error">Error:</strong> the table '.$table2.' already exists; the table '.$table1.' was <em>not</em> duplicated. Would you like to <a href="tools/'.$GLOBALS['paths']->file.'?db='.$_GET['db'].'&#38;table1='.$_GET['table1'].'">try a different name</a>?</p></div>'."\n";
  }
  else
  {//Check for custom table-structure duplication function.
   $query2 = "SELECT p.proname FROM pg_catalog.pg_namespace AS n JOIN pg_catalog.pg_proc AS p ON p.pronamespace=n.oid WHERE n.nspname='public' AND proname='f_copy_tbl';";
   $result2 = pg_query($_SESSION['db'],$query2);

   if ($result2)
   {
    $count2 = pg_num_rows($result2);

    if ($count2==0)
    {
     $query3 = "CREATE OR REPLACE FUNCTION f_copy_tbl(_tbl regclass, _newtbl text) RETURNS void AS $func$"."\n";
     $query3 .= "DECLARE _sql text;"."\n";
     $query3 .= "BEGIN"."\n";
     //-- Copy table
     $query3 .= "EXECUTE format('CREATE TABLE %I (LIKE %s INCLUDING ALL);', _newtbl, _tbl);"."\n";
     //-- Fix serial columns, if any
     $query3 .= "SELECT INTO _sql"."\n";
     $query3 .= " string_agg('CREATE SEQUENCE ' || seq, E';\n') || E';\n'"."\n";
     $query3 .= " || string_agg(format('ALTER SEQUENCE %s OWNED BY %I.%I'"."\n";
     $query3 .= " , seq, _newtbl, a.attname), E';\n') || E';\n'"."\n";
     $query3 .= " || 'ALTER TABLE ' || quote_ident(_newtbl) || E'\n  '"."\n";
     $query3 .= " || string_agg(format($$ALTER %I SET DEFAULT nextval('%s'::regclass)$$"."\n";
     $query3 .= " , a.attname, seq), E'\n, ')"."\n";
     $query3 .= "FROM pg_attribute a"."\n";
     $query3 .= "INNER JOIN pg_attrdef ad ON ad.adrelid = a.attrelid AND ad.adnum = a.attnum, quote_ident(_newtbl || '_' || a.attname || '_seq') AS seq"."\n";// -- new seq name
     $query3 .= "WHERE a.attrelid = _tbl"."\n";
     $query3 .= "AND a.attnum > 0"."\n";
     $query3 .= "AND NOT a.attisdropped"."\n";
     $query3 .= "AND a.atttypid = ANY ('{int,int8,int2}'::regtype[])"."\n";
     $query3 .= "AND ad.adsrc = 'nextval('''"."\n";
     $query3 .= " || (pg_get_serial_sequence (a.attrelid::regclass::text, a.attname))::regclass"."\n";
     $query3 .= " || '''::regclass)';"."\n";
     $query3 .= "IF _sql IS NOT NULL THEN"."\n";
     $query3 .= " EXECUTE _sql;"."\n";
     $query3 .= "END IF;"."\n";
     $query3 .= "END"."\n";
     $query3 .= "$func$  LANGUAGE plpgsql VOLATILE;"."\n";
     $result3 = pg_query($_SESSION['db'],$query3);

     if ($result3) {}
     else {sql_error_report($query3,pg_last_error($_SESSION['db']),__FUNCTION__);}
    }

    $query4 = "SELECT f_copy_tbl('$table1', '$table2');";
    $result4 = pg_query($_SESSION['db'],$query4);

    if ($result4)
    {
     $query5 = 'DO'."\n";
     $query5 .= '$do$'."\n";
     $query5 .= 'BEGIN'."\n";
     $query5 .= ' INSERT INTO '.$table2.' SELECT * FROM '.$table1.';'."\n";
     $query5 .= ' EXECUTE ('."\n";
     $query5 .= ' SELECT format($$SELECT setval('."'%s'::regclass, max(%I)) FROM %s".'$$, pg_get_serial_sequence(a.attrelid::regclass::text, a.attname), a.attname, a.attrelid::regclass'."\n";
     $query5 .= ' )'."\n";
     $query5 .= ' FROM pg_index i'."\n";
     $query5 .= ' JOIN pg_attribute a ON a.attrelid = i.indrelid'."\n";
     $query5 .= '  AND a.attnum = i.indkey[0]'."\n";
     $query5 .= " WHERE i.indrelid = '".$table2."'::regclass"."\n";
     $query5 .= ' AND i.indisprimary'."\n";
     $query5 .= ' );'."\n";
     $query5 .= 'END'."\n";
     $query5 .= '$do$ LANGUAGE plpgsql;';
     $result5 = pg_query($_SESSION['db'],$query5);

     if ($result5)
     {
      $query6 = "VACUUM $table2;";
      $result6 = pg_query($_SESSION['db'],$query6);

      if ($result6)
      {
       echo '<div><p><strong>SUCCESS!</strong> The table '.$table1.' has been duplicated.</p></div>'."\n";
      }
      else {sql_error_report($query6,pg_last_error($_SESSION['db']),__FUNCTION__);}
     }
     else {sql_error_report($query5,pg_last_error($_SESSION['db']),__FUNCTION__);}
    }
    else {sql_error_report($query4,pg_last_error($_SESSION['db']),__FUNCTION__);}
   }
   else {sql_error_report($query2,pg_last_error($_SESSION['db']),__FUNCTION__);}
  }
 }
 else {sql_error_report($query1,pg_last_error($_SESSION['db']),__FUNCTION__);}
}
1
John

問題は、parts1テーブルが(独自のシーケンスの代わりに)パーツテーブルからのシーケンスを使用することです。したがって、parts1テーブルはそれに依存しているため、parts1テーブルをドロップできないと不平を言います(いずれかのデフォルト値を介して)。列)... parts1テーブルの作成方法でこの動作が発生しています...

CREATE TABLE -LIKE source_table [ like_option ... ]セクションを参照

必要に応じてテーブルを作成し、新しいシーケンスを作成し、新しいデフォルト値を設定し、各SERIAL列に新しい依存関係を作成することで、目的の結果を得ることができます(LIKEオプションの動作では、独自のシーケンスが作成されないためです。今回)。

上記を達成するための手順は、Deszoの回答 here に記載されています

(便宜上コピーしました...すでにテーブルが作成されているため、ステップ2を変更しました)

CREATE SEQUENCE tablename_colname_seq; -- step 1
ALTER TABLE tablename ALTER COLUMN colname SET DEFAULT nextval('tablename_colname_seq') -- step 2
ALTER SEQUENCE tablename_colname_seq OWNED BY tablename.colname; -- step 3

このSQLを使用して、問題のテーブルのpkeyに属する列を特定するのに役立てることもできますが、これはすべてのシリアル列(pkeyでない場合)の特定には役立ちません。

select
  sch.nspname as schema_name,
  tab.relname as table_name,
  col.attname as column_name,
  pk.conname as pkey
from pg_constraint pk
join pg_class tab on pk.conrelid = tab.oid
join pg_namespace sch on tab.relnamespace = sch.oid
join pg_attribute col on tab.oid = col.attrelid and pk.conkey @> ARRAY[col.attnum]
where pk.contype = 'p' and sch.nspname = '<your schema>' and tab.relname = '<your table>';

より完全な解決策については Erwin Brandstetterの回答 を参照してください(私はそう思います)。

1
Joishi Bodio