Copy schema and create new schema with different name in the same data base

前端 未结 8 848
慢半拍i
慢半拍i 2020-12-14 18:33

I there a way to copy the existing schema and generate new schema with another name in the same database in postgres.

相关标签:
8条回答
  • 2020-12-14 18:55

    This seems to be the best solution I came up to.

    The idea is to use pg_dump with -O (no owner) and -o (oids) options to get plain text output without source schema and owner information.

    Such output i filter through sed replacing the default entry

    SET search_path = source_schema, pg_catalog;
    

    with command to create the new schema and set the default search path to it

    CREATE SCHEMA new_schema;
    SET search_path = new_schema, pg_catalog;
    

    After that I redirect the stream to psql logging to desired user and database to which copy of the schema will be transfered.

    The final command to copy schema 'public' to schema '2016' in the same database 'b1' looks like this:

    pg_dump -U postgres -Oo -n public -d b1 | sed 's/SET search_path = public, pg_catalog;/CREATE SCHEMA "2016";SET search_path = "2016", pg_catalog;/' | psql -U postgres -d b1
    

    Please note that GRANTS are not transfered from the source schema to the new one.

    0 讨论(0)
  • 2020-12-14 18:57

    I ran a few tests and found the result is referencing the source schema. So here's my improved version:

    -- Function: clone_schema(source text, dest text, include_records boolean default true, show_details boolean default false)
    
    -- DROP FUNCTION clone_schema(text, text, boolean, boolean);
    
    CREATE OR REPLACE FUNCTION clone_schema(
      source_schema text,
      dest_schema text,
      include_recs boolean DEFAULT true,
      show_details boolean DEFAULT false)
      RETURNS void AS
    $BODY$
    
    --  This function will clone all sequences, tables, data, views & functions from any existing schema to a new one
    -- SAMPLE CALL:
    -- SELECT clone_schema('public', 'new_schema');
    -- SELECT clone_schema('public', 'new_schema', TRUE);
    -- SELECT clone_schema('public', 'new_schema', TRUE, TRUE);
    
    DECLARE
      src_oid          oid;
      tbl_oid          oid;
      func_oid         oid;
      object           text;
      buffer           text;
      srctbl           text;
      default_         text;
      column_          text;
      qry              text;
      xrec             record;
      dest_qry         text;
      v_def            text;
      seqval           bigint;
      sq_last_value    bigint;
      sq_max_value     bigint;
      sq_start_value   bigint;
      sq_increment_by  bigint;
      sq_min_value     bigint;
      sq_cache_value   bigint;
      sq_log_cnt       bigint;
      sq_is_called     boolean;
      sq_is_cycled     boolean;
      sq_cycled        char(10);
      rec              record;
      source_schema_dot text = source_schema || '.';
      dest_schema_dot text = dest_schema || '.';
    
    BEGIN
    
      -- Check that source_schema exists
      SELECT oid INTO src_oid
      FROM pg_namespace
      WHERE nspname = quote_ident(source_schema);
      IF NOT FOUND
      THEN
        RAISE NOTICE 'source schema % does not exist!', source_schema;
        RETURN ;
      END IF;
    
      -- Check that dest_schema does not yet exist
      PERFORM nspname
      FROM pg_namespace
      WHERE nspname = quote_ident(dest_schema);
      IF FOUND
      THEN
        RAISE NOTICE 'dest schema % already exists!', dest_schema;
        RETURN ;
      END IF;
    
      EXECUTE 'CREATE SCHEMA ' || quote_ident(dest_schema) ;
    
      -- Defaults search_path to destination schema
      PERFORM set_config('search_path', dest_schema, true);
    
      -- Create sequences
      -- TODO: Find a way to make this sequence's owner is the correct table.
      FOR object IN
      SELECT sequence_name::text
      FROM information_schema.sequences
      WHERE sequence_schema = quote_ident(source_schema)
      LOOP
        EXECUTE 'CREATE SEQUENCE ' || quote_ident(dest_schema) || '.' || quote_ident(object);
        srctbl := quote_ident(source_schema) || '.' || quote_ident(object);
    
        EXECUTE 'SELECT last_value, max_value, start_value, increment_by, min_value, cache_value, log_cnt, is_cycled, is_called
                  FROM ' || quote_ident(source_schema) || '.' || quote_ident(object) || ';'
        INTO sq_last_value, sq_max_value, sq_start_value, sq_increment_by, sq_min_value, sq_cache_value, sq_log_cnt, sq_is_cycled, sq_is_called ;
    
        IF sq_is_cycled
        THEN
          sq_cycled := 'CYCLE';
        ELSE
          sq_cycled := 'NO CYCLE';
        END IF;
    
        EXECUTE 'ALTER SEQUENCE '   || quote_ident(dest_schema) || '.' || quote_ident(object)
                || ' INCREMENT BY ' || sq_increment_by
                || ' MINVALUE '     || sq_min_value
                || ' MAXVALUE '     || sq_max_value
                || ' START WITH '   || sq_start_value
                || ' RESTART '      || sq_min_value
                || ' CACHE '        || sq_cache_value
                || sq_cycled || ' ;' ;
    
        buffer := quote_ident(dest_schema) || '.' || quote_ident(object);
        IF include_recs
        THEN
          EXECUTE 'SELECT setval( ''' || buffer || ''', ' || sq_last_value || ', ' || sq_is_called || ');' ;
        ELSE
          EXECUTE 'SELECT setval( ''' || buffer || ''', ' || sq_start_value || ', ' || sq_is_called || ');' ;
        END IF;
        IF show_details THEN RAISE NOTICE 'Sequence created: %', object; END IF;
      END LOOP;
    
      -- Create tables
      FOR object IN
      SELECT TABLE_NAME::text
      FROM information_schema.tables
      WHERE table_schema = quote_ident(source_schema)
            AND table_type = 'BASE TABLE'
    
      LOOP
        buffer := dest_schema || '.' || quote_ident(object);
        EXECUTE 'CREATE TABLE ' || buffer || ' (LIKE ' || quote_ident(source_schema) || '.' || quote_ident(object)
                || ' INCLUDING ALL)';
    
        IF include_recs
        THEN
          -- Insert records from source table
          EXECUTE 'INSERT INTO ' || buffer || ' SELECT * FROM ' || quote_ident(source_schema) || '.' || quote_ident(object) || ';';
        END IF;
    
        FOR column_, default_ IN
        SELECT column_name::text,
          REPLACE(column_default::text, source_schema, dest_schema)
        FROM information_schema.COLUMNS
        WHERE table_schema = dest_schema
              AND TABLE_NAME = object
              AND column_default LIKE 'nextval(%' || quote_ident(source_schema) || '%::regclass)'
        LOOP
          EXECUTE 'ALTER TABLE ' || buffer || ' ALTER COLUMN ' || column_ || ' SET DEFAULT ' || default_;
        END LOOP;
    
        IF show_details THEN RAISE NOTICE 'base table created: %', object; END IF;
    
      END LOOP;
    
      --  add FK constraint
      FOR xrec IN
      SELECT ct.conname as fk_name, rn.relname as tb_name,  'ALTER TABLE ' || quote_ident(dest_schema) || '.' || quote_ident(rn.relname)
             || ' ADD CONSTRAINT ' || quote_ident(ct.conname) || ' ' || replace(pg_get_constraintdef(ct.oid), source_schema_dot, '') || ';' as qry
      FROM pg_constraint ct
        JOIN pg_class rn ON rn.oid = ct.conrelid
      WHERE connamespace = src_oid
            AND rn.relkind = 'r'
            AND ct.contype = 'f'
    
      LOOP
        IF show_details THEN RAISE NOTICE 'Creating FK constraint %.%...', xrec.tb_name, xrec.fk_name; END IF;
        --RAISE NOTICE 'DEF: %', xrec.qry;
        EXECUTE xrec.qry;
      END LOOP;
    
      -- Create functions
      FOR xrec IN
      SELECT proname as func_name, oid as func_oid
      FROM pg_proc
      WHERE pronamespace = src_oid
    
      LOOP
        IF show_details THEN RAISE NOTICE 'Creating function %...', xrec.func_name; END IF;
        SELECT pg_get_functiondef(xrec.func_oid) INTO qry;
        SELECT replace(qry, source_schema_dot, '') INTO dest_qry;
        EXECUTE dest_qry;
      END LOOP;
    
      -- add Table Triggers
      FOR rec IN
      SELECT
        trg.tgname AS trigger_name,
        tbl.relname AS trigger_table,
    
        CASE
        WHEN trg.tgenabled='O' THEN 'ENABLED'
        ELSE 'DISABLED'
        END AS status,
        CASE trg.tgtype::integer & 1
        WHEN 1 THEN 'ROW'::text
        ELSE 'STATEMENT'::text
        END AS trigger_level,
        CASE trg.tgtype::integer & 66
        WHEN 2 THEN 'BEFORE'
        WHEN 64 THEN 'INSTEAD OF'
        ELSE 'AFTER'
        END AS action_timing,
        CASE trg.tgtype::integer & cast(60 AS int2)
        WHEN 16 THEN 'UPDATE'
        WHEN 8 THEN 'DELETE'
        WHEN 4 THEN 'INSERT'
        WHEN 20 THEN 'INSERT OR UPDATE'
        WHEN 28 THEN 'INSERT OR UPDATE OR DELETE'
        WHEN 24 THEN 'UPDATE OR DELETE'
        WHEN 12 THEN 'INSERT OR DELETE'
        WHEN 32 THEN 'TRUNCATE'
        END AS trigger_event,
        'EXECUTE PROCEDURE ' ||  (SELECT nspname FROM pg_namespace where oid = pc.pronamespace )
        || '.' || proname || '('
        || regexp_replace(replace(trim(trailing '\000' from encode(tgargs,'escape')), '\000',','),'{(.+)}','''{\1}''','g')
        || ')' as action_statement
    
      FROM pg_trigger trg
        JOIN pg_class tbl on trg.tgrelid = tbl.oid
        JOIN pg_proc pc ON pc.oid = trg.tgfoid
      WHERE trg.tgname not like 'RI_ConstraintTrigger%'
            AND trg.tgname not like 'pg_sync_pg%'
            AND tbl.relnamespace = (SELECT oid FROM pg_namespace where nspname = quote_ident(source_schema) )
    
      LOOP
        buffer := dest_schema || '.' || quote_ident(rec.trigger_table);
        IF show_details THEN RAISE NOTICE 'Creating trigger % % % ON %...', rec.trigger_name, rec.action_timing, rec.trigger_event, rec.trigger_table; END IF;
        EXECUTE 'CREATE TRIGGER ' || rec.trigger_name || ' ' || rec.action_timing
                || ' ' || rec.trigger_event || ' ON ' || buffer || ' FOR EACH '
                || rec.trigger_level || ' ' || replace(rec.action_statement, source_schema_dot, '');
    
      END LOOP;
    
      -- Create views
      FOR object IN
      SELECT table_name::text,
        view_definition
      FROM information_schema.views
      WHERE table_schema = quote_ident(source_schema)
    
      LOOP
        buffer := dest_schema || '.' || quote_ident(object);
        SELECT replace(view_definition, source_schema_dot, '') INTO v_def
        FROM information_schema.views
        WHERE table_schema = quote_ident(source_schema)
              AND table_name = quote_ident(object);
        IF show_details THEN RAISE NOTICE 'Creating view % AS %', object, regexp_replace(v_def, '[\n\r]+', ' ', 'g'); END IF;
        EXECUTE 'CREATE OR REPLACE VIEW ' || buffer || ' AS ' || v_def || ';' ;
    
      END LOOP;
    
      RETURN;
    
    END;
    
    $BODY$
    LANGUAGE plpgsql VOLATILE
    COST 100;
    
    0 讨论(0)
  • 2020-12-14 19:02

    Use pg_dump to dump your current schema in a SQL-formated file. Open the file, replace the schemaname with the new name and excute this script in your database to create the new schema and all other objects inside this schema.

    0 讨论(0)
  • 2020-12-14 19:09

    In the case you are OK with only tables and columns (without constraints, keys etc.) this simple script could be helpful

    DO LANGUAGE plpgsql
    $body$
    DECLARE
       old_schema NAME = 'src_schema';
       new_schema NAME = 'dst_schema';
       tbl TEXT;
       sql TEXT;
    BEGIN
      EXECUTE format('CREATE SCHEMA IF NOT EXISTS %I', new_schema);
    
      FOR tbl IN
        SELECT table_name
        FROM information_schema.tables
        WHERE table_schema=old_schema
      LOOP
        sql := format(
                'CREATE TABLE IF NOT EXISTS %I.%I '
                'AS '
                'SELECT * FROM %I.%I'
                , new_schema, tbl, old_schema, tbl);
        raise notice 'Sql: %', sql;
    
        EXECUTE sql;
      END LOOP;
    END
    $body$;
    
    0 讨论(0)
  • 2020-12-14 19:10

    You can use this simple script :

    DO LANGUAGE plpgsql
    $body$
    DECLARE
       old_schema NAME = 'src_schema';
       new_schema NAME = 'dst_schema';
       tbl TEXT;
       sql TEXT;
    BEGIN
      EXECUTE format('CREATE SCHEMA IF NOT EXISTS %I', new_schema);
    
      FOR tbl IN
        SELECT table_name
        FROM information_schema.tables
        WHERE table_schema=old_schema
      LOOP
        sql := format(
                'CREATE TABLE IF NOT EXISTS %I.%I '
                '(LIKE %I.%I INCLUDING INDEXES INCLUDING CONSTRAINTS)'
                , new_schema, tbl, old_schema, tbl);
    
        EXECUTE sql;
    
        sql := format(
                'INSERT INTO %I.%I '
                'SELECT * FROM %I.%I'
                , new_schema, tbl, old_schema, tbl);
    
        EXECUTE sql;
      END LOOP;
    END
    $body$;
    
    0 讨论(0)
  • 2020-12-14 19:10

    Using @IdanDavidi's solution, I was able to solve the case where sequences were owned by and referring to the source schema instead of the destination schema.

    -- Function: clone_schema(text, text)
    
    -- DROP FUNCTION clone_schema(text, text);
    
    CREATE OR REPLACE FUNCTION clone_schema(
        source_schema text,
        dest_schema text,
        include_recs boolean)
      RETURNS void AS
    $BODY$
    
    --  This function will clone all sequences, tables, data, views & functions from any existing schema to a new one
    -- SAMPLE CALL:
    -- SELECT clone_schema('public', 'new_schema', TRUE);
    
    DECLARE
      src_oid          oid;
      tbl_oid          oid;
      func_oid         oid;
      table_rec        record;
      seq_rec          record;
      object           text;
      sequence_        text;
      table_           text;
      buffer           text;
      seq_buffer       text;
      table_buffer     text;
      srctbl           text;
      default_         text;
      column_          text;
      qry              text;
      dest_qry         text;
      v_def            text;
      seqval           bigint;
      sq_last_value    bigint;
      sq_max_value     bigint;
      sq_start_value   bigint;
      sq_increment_by  bigint;
      sq_min_value     bigint;
      sq_cache_value   bigint;
      sq_log_cnt       bigint;
      sq_is_called     boolean;
      sq_is_cycled     boolean;
      sq_cycled        char(10);
    
    BEGIN
    
    -- Check that source_schema exists
      SELECT oid INTO src_oid
        FROM pg_namespace
       WHERE nspname = source_schema;
      IF NOT FOUND
        THEN
        RAISE EXCEPTION 'source schema % does not exist!', source_schema;
        RETURN ;
      END IF;
    
    -- Check that dest_schema does not yet exist
      PERFORM nspname
        FROM pg_namespace
       WHERE nspname = dest_schema;
      IF FOUND
        THEN
        RAISE EXCEPTION 'dest schema % already exists!', dest_schema;
        RETURN ;
      END IF;
    
      EXECUTE 'CREATE SCHEMA "' || dest_schema || '"';
    
    -- Create tables
      FOR object IN
        SELECT TABLE_NAME::text
          FROM information_schema.tables
         WHERE table_schema = source_schema
           AND table_type = 'BASE TABLE'
    
      LOOP
        buffer := '"' || dest_schema || '".' || quote_ident(object);
        EXECUTE 'CREATE TABLE ' || buffer || ' (LIKE "' || source_schema || '".' || quote_ident(object)
            || ' INCLUDING ALL);';
    
        IF include_recs
          THEN
          -- Insert records from source table
          EXECUTE 'INSERT INTO ' || buffer || ' SELECT * FROM "' || source_schema || '".' || quote_ident(object) || ';';
        END IF;
    
      END LOOP;
    
    --  add FK constraint
      FOR qry IN
        SELECT 'ALTER TABLE "' || dest_schema || '".' || quote_ident(rn.relname)
                || ' ADD CONSTRAINT ' || quote_ident(ct.conname) || ' ' || pg_get_constraintdef(ct.oid) || ';'
          FROM pg_constraint ct
          JOIN pg_class rn ON rn.oid = ct.conrelid
         WHERE connamespace = src_oid
           AND rn.relkind = 'r'
           AND ct.contype = 'f'
    
        LOOP
          EXECUTE qry;
    
        END LOOP;
    
    -- Create sequences
      FOR seq_rec IN
        SELECT
          s.sequence_name::text,
          table_name,
          column_name
        FROM information_schema.sequences s
        JOIN (
          SELECT
            substring(column_default from E'^nextval\\(''(?:[^"'']?.*["'']?\\.)?([^'']*)''(?:::text|::regclass)?\\)')::text as seq_name,
            table_name,
            column_name
          FROM information_schema.columns
          WHERE column_default LIKE 'nextval%'
            AND table_schema = source_schema
        ) c ON c.seq_name = s.sequence_name
        WHERE sequence_schema = source_schema
      LOOP
        seq_buffer := quote_ident(dest_schema) || '.' || quote_ident(seq_rec.sequence_name);
    
        EXECUTE 'CREATE SEQUENCE ' || seq_buffer || ';';
    
        qry := 'SELECT last_value, max_value, start_value, increment_by, min_value, cache_value, log_cnt, is_cycled, is_called
                  FROM "' || source_schema || '".' || quote_ident(seq_rec.sequence_name) || ';';
        EXECUTE qry INTO sq_last_value, sq_max_value, sq_start_value, sq_increment_by, sq_min_value, sq_cache_value, sq_log_cnt, sq_is_cycled, sq_is_called ;
    
        IF sq_is_cycled
          THEN
            sq_cycled := 'CYCLE';
        ELSE
            sq_cycled := 'NO CYCLE';
        END IF;
    
        EXECUTE 'ALTER SEQUENCE '   || seq_buffer
                || ' INCREMENT BY ' || sq_increment_by
                || ' MINVALUE '     || sq_min_value
                || ' MAXVALUE '     || sq_max_value
                || ' START WITH '   || sq_start_value
                || ' RESTART '      || sq_min_value
                || ' CACHE '        || sq_cache_value
                || ' OWNED BY '     || quote_ident(dest_schema ) || '.'
                                    || quote_ident(seq_rec.table_name) || '.'
                                    || quote_ident(seq_rec.column_name) || ' '
                || sq_cycled || ' ;' ;
    
        IF include_recs
            THEN
                EXECUTE 'SELECT setval( ''' || seq_buffer || ''', ' || sq_last_value || ', ' || sq_is_called || ');' ;
        ELSE
                EXECUTE 'SELECT setval( ''' || seq_buffer || ''', ' || sq_start_value || ', ' || sq_is_called || ');' ;
        END IF;
    
        table_buffer := quote_ident(dest_schema) || '.' || quote_ident(seq_rec.table_name);
    
        FOR table_rec IN
          SELECT column_name::text AS column_,
                 REPLACE(column_default::text, source_schema, quote_ident(dest_schema)) AS default_
            FROM information_schema.COLUMNS
           WHERE table_schema = dest_schema
             AND TABLE_NAME = seq_rec.table_name
             AND column_default LIKE 'nextval(%' || seq_rec.sequence_name || '%::regclass)'
        LOOP
          EXECUTE 'ALTER TABLE ' || table_buffer || ' ALTER COLUMN ' || table_rec.column_ || ' SET DEFAULT nextval(' || quote_literal(seq_buffer) || '::regclass);';
        END LOOP;
    
      END LOOP;
    
    -- Create views
      FOR object IN
        SELECT table_name::text,
               view_definition
          FROM information_schema.views
         WHERE table_schema = source_schema
    
      LOOP
        buffer := '"' || dest_schema || '".' || quote_ident(object);
        SELECT view_definition INTO v_def
          FROM information_schema.views
         WHERE table_schema = source_schema
           AND table_name = quote_ident(object);
    
        EXECUTE 'CREATE OR REPLACE VIEW ' || buffer || ' AS ' || v_def || ';' ;
    
      END LOOP;
    
    -- Create functions
      FOR func_oid IN
        SELECT oid
          FROM pg_proc
         WHERE pronamespace = src_oid
    
      LOOP
        SELECT pg_get_functiondef(func_oid) INTO qry;
        SELECT replace(qry, source_schema, dest_schema) INTO dest_qry;
        EXECUTE dest_qry;
    
      END LOOP;
    
      RETURN;
    
    END;
    
    $BODY$
      LANGUAGE plpgsql VOLATILE
      COST 100;
    
    0 讨论(0)
提交回复
热议问题