Particionamento de Tabelas no postgres – automatizando

Já falamos um bocado sobre particionamento de tabelas por aqui:

Para encerrar a série, vamos juntar tudo numa única função para tornar o dia-a-dia mais simples. Claro, se você tem poucas tabelas particionadas e se criar novas partições é algo raro, então você não deve se preocupar com isso. Assim sendo, sei que estou escrevendo para um público bem pequeno, mas que vem crescendo conforme o postgres sai do cantinho dos fundos dos grandes CPDs para ocupar cada vez mais uma posição de destaque. Bom, uma função genérica para qualquer tipo de particionamento daria muito trabalho para implementar, portanto a função que vou apresentar apresenta algumas características particulares que você pode adaptar para necessidades específicas se quiser:

  • Eu utilizo uma tabela para cadastrar o nome das tabelas que serão particionadas e algumas informações específicas sobre o particionamento de cada uma destas tabelas. Assim a nossa função vai sempre varrer esta tabela e criar todas as partições cadastradas de uma vez só;
  • A chave será sempre num campo do tipo INTEGER e cada partição utilizará um valor único na chave e não uma série de valores. Então vamos ter coisas como CHAVE = VALOR;
  • A função está preparada para trabalhar com versões a partir do 8.3 do postgres. Se você utiliza versões anteriores a 8.3… realmente está na hora de você migrar. E veja se vai logo para o 9.2, pois as vantagens são realmente recompensadoras. Faremos algumas checagens dentro da função para saber com qual versão estamos lidando e fazer os ajustes necessários;
  • Não iremos apenas criar as partições. Vamos aproveitar para já fazer algumas manutenções de acordo com as informações cadastradas na nossa tabela de partições.

Ao executar a função vamos executar as seguintes tarefas nesta ordem:

  • Verifica se alguma FK de tabela não particionada aponta para uma tabela particionada;
  • Carrega dados no cadastro de particionamento;
  • Verifica se a tabela mãe cadastrada existe?
  • Verifica se o tablespace existe;
  • Verifica se a partição a ser criada já existe;
  • Cria a partição da tabela;
  • Ajusta parâmetros de STORAGE na partição criada;
  • Remove restrição NO INHERIT da tabela filha;
  • Importa os dados da tabela mãe para a partição;
  • Cria CHECK CONSTRAINT para a chave da partição;
  • Ajusta o dono da partição criada;
  • Ajusta GRANTs para a partição criada;
  • Cria ou substitui função do gatilho;
  • Ajusta o dono da função;
  • Verifica se o gatilho ja existe;
  • Cria constraints para tabelas não particionadas;
  • Arquiva partição antiga;
  • Apaga partição arquivada antiga;
  • Apaga partição antiga;

Agora vejamos a nossa tabela para cadastrar os parâmetros de todas tabelas que serão particionadas numa tacada só.

DROP TABLE IF EXISTS parametro_particao;

CREATE TABLE parametro_particao
(
  nome_tabela_esquema varchar(64) NOT NULL,
  nome_tabela         varchar(64) NOT NULL,
  nome_coluna_chave   varchar(64) NOT NULL,
  nome_tablespace     varchar(64) NOT NULL DEFAULT 'pg_default',
  qt_chave_ativa      integer              DEFAULT 5,
  qt_chave_excluir    integer,
  qt_chave_desfilhar  integer,
  ordem_criacao       integer,
  CONSTRAINT parametro_particao_pk PRIMARY KEY (nome_tabela_esquema , nome_tabela ),
  CONSTRAINT parametro_particao_ck1 CHECK (qt_chave_ativa > 1),
  CONSTRAINT parametro_particao_ck2 CHECK (qt_chave_excluir   > qt_chave_ativa AND qt_chave_excluir   < 100),
  CONSTRAINT parametro_particao_ck3 CHECK (qt_chave_desfilhar > qt_chave_ativa AND qt_chave_desfilhar < 100)
);
ALTER TABLE parametro_particao OWNER TO postgres;
COMMENT ON TABLE parametro_particao
  IS 'Cadastro de tabelas particionadas e parâmetros de ajuste de manutenção das mesmas';
COMMENT ON COLUMN parametro_particao.nome_tabela_esquema IS 'Nome do esquema da tabela particionada';
COMMENT ON COLUMN parametro_particao.nome_tabela         IS 'Nome da tabela particionada';
COMMENT ON COLUMN parametro_particao.nome_coluna_chave   IS 'Nome do campo da tabela que será utilizado como chave para o particionamento';
COMMENT ON COLUMN parametro_particao.nome_tablespace     IS 'Nome do tablespace onde a partição será criada';
COMMENT ON COLUMN parametro_particao.qt_chave_ativa      IS 'Quantidade de chaves ativas no gatilho de INSERT da tabela mãe';
COMMENT ON COLUMN parametro_particao.qt_chave_excluir    IS 'Quantidade de partições a serem mantidas. Serão mantidas "qt_chaves_apagar" com "no_coluna_chave" maiores, as demais serão excluídas. O valor NULL desativa a esclusão de partições';
COMMENT ON COLUMN parametro_particao.qt_chave_desfilhar  IS 'Quantidade de partições a serem desfilhadas (deixar de ser uma partição filha da tabela mãe). Serão mantidas "qt_chaves_desfilhar" com "no_coluna_chave" maiores, as demais serão desfilhadas. O valor NULL desativa a desfilhação.';
COMMENT ON COLUMN parametro_particao.ordem_criacao       IS 'Órdem de processamento das tabelas. Utilizado para respeitar as FKs das partições.';

Para testar, vamos utilizar as tabelas DETALHE e DETALHE_PEDIDO criadas nos posts anteriores:

INSERT INTO parametro_particao VALUES ('app', 'pedido', 'ano_pedido', 'pedido', 5, 99, 50, 1);
INSERT INTO parametro_particao VALUES ('app', 'pedido_detalhe', 'ano_pedido', 'pedido_detalhe', 5, 99, 50, 2);

E la vai a pedrada… segue o código a função

-- Function: public.cria_particao_tabela(integer, boolean, boolean)

DROP FUNCTION IF EXISTS public.cria_particao_tabela(integer, boolean, boolean);

CREATE OR REPLACE FUNCTION public.cria_particao_tabela(
    IN p_next_partition_key integer, --Número da nova partição
    IN p_archive boolean,            --Colocar como NO INHERIT tabelas marcadas com 'qt_chaves_desfiliar'
    IN p_drop boolean,               --Apagar partições mais antigas que 'qt_chaves_apagar'
    OUT v_cod_erro text,             --Mensagem de erro
    OUT v_pos_erro integer)          --Posição do erro no código
  RETURNS record AS
$BODY$ 
--
-- Descrição: Cria novas partições nas tabelas cadastradas em 'parametro_particao'
-- Autor: Fábio Telles Rodriguez
--
DECLARE
    v_sql                       text;
    v_tmp                       integer;
    c_pp                        record;
    c_part                      record;

    -- Pegar FKs de tabelas não particionadas que apontam para tabelas particionadas
    c_loop_const_fora           CURSOR FOR SELECT tc.conname AS const
        FROM 
            pg_constraint AS tc,
            pg_class      AS c,
            pg_class      AS r
        WHERE
                tc.contype   = 'f'
            AND tc.conrelid  = c.oid
            AND tc.confrelid = r.oid
            AND c.relname NOT IN (SELECT nome_tabela FROM parametro_particao)
            AND r.relname IN     (SELECT nome_tabela FROM parametro_particao)
            AND NOT EXISTS       (SELECT 1 FROM pg_inherits i WHERE  i.inhrelid = c.oid);

    -- Criar FKs de tabelas particionadas que apontam para outras tabelas particionadas
    c_loop_const_part           CURSOR FOR
        SELECT 'ALTER TABLE ' || pp.nome_tabela_esquema || '.' || pp.nome_tabela || '_' || p_next_partition_key || 
            ' ADD CONSTRAINT ' || conname || '_' || p_next_partition_key || ' ' || 
            replace(pg_get_constraintdef(tc.oid), 
                r.relname, r.relname || '_' || p_next_partition_key) || 
            ';' AS alter_c
        FROM 
            pg_constraint AS tc,
            pg_class      AS c,
            pg_class      AS r,
            pg_namespace  AS nc,
            pg_namespace  AS nr,
            parametro_particao AS pp
        WHERE
                tc.contype   = 'f'
            AND tc.conrelid  = c.oid
            AND nc.oid       = c.relnamespace
            AND c.relname    = pp.nome_tabela
            AND nc.nspname   = pp.nome_tabela_esquema
            AND tc.confrelid = r.oid
            AND nr.oid       = r.relnamespace
            AND EXISTS (SELECT 1 FROM parametro_particao rpp
                                WHERE 
                                    nr.nspname = rpp.nome_tabela_esquema AND
                                    r.relname  = rpp.nome_tabela);

    -- Criar FKs de tabelas particionadas que apontam para tabelas não particionadas
    c_loop_constraint           CURSOR (
                                    p_table_schema varchar, 
                                    p_table_name varchar) FOR
        SELECT 'ALTER TABLE ' || p_table_schema || '.' || p_table_name || '_' || p_next_partition_key || 
            ' ADD CONSTRAINT ' || conname || '_' || p_next_partition_key || ' ' || pg_get_constraintdef(tc.oid) || ';' AS alter_c
        FROM 
            pg_constraint AS tc,
            pg_class      AS c,
            pg_class      AS r,
            pg_namespace  AS nc,
            pg_namespace  AS nr,
            parametro_particao AS pp
        WHERE
                tc.contype   = 'f'
            AND tc.conrelid  = c.oid
            AND nc.nspname   = p_table_schema
            AND nc.nspname   = pp.nome_tabela_esquema
            AND nc.oid       = c.relnamespace
            AND c.relname    = p_table_name
            AND c.relname    = pp.nome_tabela
            AND tc.confrelid = r.oid
            AND nr.oid       = r.relnamespace
            AND NOT EXISTS (SELECT 1 FROM parametro_particao rpp
                                WHERE 
                                    nr.nspname = rpp.nome_tabela_esquema AND
                                    r.relname  = rpp.nome_tabela);

    -- Criar condições na função do gatilho para demais partições já existentes
    c_loop_partitions           CURSOR (
                                    p_part_col     varchar,
                                    p_table_schema varchar,
                                    p_table_name   varchar,
                                    p_key_active   integer ) FOR
        SELECT 'ELSIF ( NEW.' || p_part_col || ' = ' || replace(r.relname, c.relname || '_', '') || ') THEN ' || chr(10)
            || '    INSERT INTO ' || p_table_schema || '.' || r.relname || ' VALUES (NEW.*);' || chr(10) AS sql
            FROM 
                pg_inherits i 
                JOIN pg_class c     ON i.inhparent = c.oid
                JOIN pg_namespace n ON c.relnamespace = n.oid
                JOIN pg_class r     ON i.inhrelid    = r.oid
            WHERE 
                c.relname = p_table_name AND
                n.nspname = p_table_schema AND
                r.relname != (p_table_name || '_' || p_next_partition_key)
            ORDER BY to_number(replace(r.relname, c.relname || '_', ''),'999999') DESC
            LIMIT p_key_active -1;
    c_loop_grant          CURSOR (
                                    p_table_schema varchar,
                                    p_table_name   varchar) FOR
        SELECT 'GRANT ' || privilege_type || ' ON ' || table_schema || '.' || table_name || '_' || p_next_partition_key || ' TO ' || grantee || 
            CASE is_grantable WHEN 'YES' THEN ' WITH GRANT OPTION' ELSE '' END || ';' sql
            FROM information_schema.table_privileges 
            WHERE 
                table_schema = p_table_schema AND
                table_name = p_table_name;

    -- Desfiliar partições com mais de 'qt_chave_desfiliar' além da última
    c_loop_arc            CURSOR (
                                    p_table_schema varchar,
                                    p_table_name   varchar,
                                    p_key_arc     integer) FOR
        SELECT 'ALTER TABLE  ' || p_table_schema || '.' || r.relname || ' NO INHERIT ' || p_table_schema || '.' || p_table_name || ';' AS sql
            FROM 
                pg_inherits i 
                JOIN pg_class c     ON i.inhparent = c.oid
                JOIN pg_namespace n ON c.relnamespace = n.oid
                JOIN pg_class r     ON i.inhrelid    = r.oid
            WHERE 
                c.relname = p_table_name AND
                n.nspname = p_table_schema
            ORDER BY to_number(replace(r.relname, c.relname || '_', ''),'999999') DESC
            OFFSET p_key_arc;

    -- Apagar partições com mais de 'qt_chave_excluir' além da última
    c_loop_drop           CURSOR (
                                    p_table_schema varchar,
                                    p_table_name   varchar,
                                    p_key_drop     integer) FOR
        SELECT 'DROP TABLE IF EXISTS ' || p_table_schema || '.' || r.relname || ';' AS sql
            FROM 
                pg_inherits i 
                JOIN pg_class c     ON i.inhparent = c.oid
                JOIN pg_namespace n ON c.relnamespace = n.oid
                JOIN pg_class r     ON i.inhrelid    = r.oid
            WHERE 
                c.relname = p_table_name AND
                c.relkind = 'r' AND
                n.nspname = p_table_schema
            ORDER BY to_number(replace(r.relname, c.relname || '_', ''),'999999') DESC
            OFFSET p_key_drop;

    -- Carregar dados de parametro_particao
    c_tabelas           CURSOR FOR SELECT
            nome_tabela_esquema  AS tbl_sch,
            nome_tabela          AS tbl_nom,
            nome_coluna_chave    AS par_col, 
            nome_tablespace      AS tbs_def, 
            qt_chave_ativa       AS key_act,
            qt_chave_excluir     AS key_drp,
            qt_chave_desfilhar   AS key_arc,
            ordem_criacao        AS tbl_ord
        FROM parametro_particao
        ORDER BY ordem_criacao;

-- Chega de declarações, vamos trabalhar!!!
BEGIN
    v_cod_erro := 0;
    v_pos_erro := 0;
    RAISE NOTICE 'cria_particao_tabela|Begin';

    -- Verifica se alguma FK de tabela não particionada aponta para uma tabela particionada
    FOR c_part IN c_loop_const_fora LOOP
        v_pos_erro := 10;
        RAISE EXCEPTION 'cria_particao_tabela| A constraint % aponta para uma tabela particionada mas não será particionada também!!!', c_part.const;
    END LOOP;

    -- Carrega dados no cadastro de particionamento
    v_pos_erro := 20;
    FOR c_pp IN c_tabelas LOOP

        -- A tabela mãe existe?
        v_pos_erro := 30;
        SELECT 1 
            INTO v_tmp
            FROM information_schema.tables 
            WHERE
                table_catalog   = current_database() AND
                table_schema    = c_pp.tbl_sch  AND
                table_name      = c_pp.tbl_nom;
        IF NOT FOUND THEN
            v_pos_erro := 40;
            RAISE EXCEPTION 'cria_particao_tabela|A Tabela Mãe % configurada em tbom_parametro_particao não existe', c_pp.tbl_nom;
        END IF;

        -- Verifica se o tablespace existe
        v_pos_erro := 50;
        SELECT 1 
            INTO v_tmp
            FROM pg_catalog.pg_tablespace
            WHERE spcname = c_pp.tbs_def;
        IF NOT FOUND THEN
            v_pos_erro := 60;
            RAISE EXCEPTION 'cria_particao_tabela|A tablespace para a particao da tabela % não existe', c_pp.tbl_nom ;
        END IF;

        -- Verifica se a particao da tabela ja existe
        v_pos_erro := 70;
        SELECT 1 
            INTO v_tmp
            FROM information_schema.tables
            WHERE 
                table_name = c_pp.tbl_nom || '_' || p_next_partition_key AND
                table_schema = c_pp.tbl_sch;
        IF FOUND THEN
            v_pos_erro := 80;
            RAISE EXCEPTION 'cria_particao_tabela|A partição da tabela % com chave % de valor % já existe', 
                c_pp.tbl_sch, c_pp.par_col, p_next_partition_key ;
        END IF;

        -- Cria a partição da tabela
        v_pos_erro := 90;
        RAISE WARNING 'cria_particao_tabela|Cria particao %', c_pp.tbl_nom || '_' || p_next_partition_key;

        v_sql := 'CREATE TABLE ' || c_pp.tbl_sch || '.' || c_pp.tbl_nom || '_' || p_next_partition_key
            || '(LIKE ' || c_pp.tbl_sch || '.' || c_pp.tbl_nom
            || ' INCLUDING STORAGE INCLUDING INDEXES INCLUDING DEFAULTS INCLUDING CONSTRAINTS ) INHERITS (' 
            || c_pp.tbl_sch || '.' || c_pp.tbl_nom || ') TABLESPACE ' || c_pp.tbs_def;
        v_pos_erro := 100;
        EXECUTE v_sql;

        -- Ajusta parâmetros de STORAGE na partição criada
        v_pos_erro := 110;
        SELECT 'ALTER TABLE '  || c_pp.tbl_sch || '.' || c_pp.tbl_nom || '_' || p_next_partition_key
            || ' SET (' || array_to_string(c.reloptions,',') || ');' AS sql
            INTO v_sql
            FROM 
                pg_class c
                JOIN pg_namespace n ON c.relnamespace = n.oid
            WHERE
                c.relname = c_pp.tbl_nom AND 
                n.nspname = c_pp.tbl_sch;
        IF FOUND AND v_sql IS NOT NULL THEN
            v_pos_erro := 120;
            EXECUTE v_sql;
        END IF;

        -- Remove restrição NO INHERIT da tabela filha;
        -- ### Só funciona a partir da versão 9.2 ###
        v_pos_erro := 130;
        SELECT 'ALTER TABLE '  || c_pp.tbl_sch || '.' || c_pp.tbl_nom || '_' || p_next_partition_key
            || ' DROP CONSTRAINT ' || conname || ';'
            INTO v_sql
            FROM 
                pg_constraint cc 
                JOIN pg_class c ON 
                    cc.conrelid = c.oid  AND 
                    cc.connamespace = c.relnamespace 
                JOIN pg_namespace n ON
                    c.relnamespace = n.oid
            WHERE 
                connoinherit                AND
                contype      = 'c'          AND
                c.relname    = c_pp.tbl_nom AND 
                n.nspname    = c_pp.tbl_sch;

        IF FOUND AND v_sql IS NOT NULL THEN
            v_pos_erro := 140;
            EXECUTE v_sql;
        END IF;

        -- Remove restrição NO INHERIT da tabela filha;
        v_pos_erro := 150;
        v_sql := 'INSERT INTO '  || c_pp.tbl_sch || '.' || c_pp.tbl_nom || '_' || p_next_partition_key ||
            ' SELECT * FROM ' || c_pp.tbl_sch || '.' || c_pp.tbl_nom || ' WHERE ' || 
            c_pp.par_col || ' = ' || p_next_partition_key || ';';

        IF FOUND AND v_sql IS NOT NULL THEN
            v_pos_erro := 160;
            EXECUTE v_sql;
            v_sql := 'DELETE FROM ' || c_pp.tbl_sch || '.' || c_pp.tbl_nom || ' WHERE ' || c_pp.par_col ||
                ' = ' || p_next_partition_key || ';';
            v_pos_erro := 170;
            EXECUTE v_sql;
        END IF;

        -- Cria CHECK CONSTRAINT para a chave da partição
        v_pos_erro := 180;
        v_sql := 'ALTER TABLE ' || c_pp.tbl_sch || '.' || c_pp.tbl_nom || '_' || p_next_partition_key
            || ' ADD CONSTRAINT ' || c_pp.tbl_nom || '_' || p_next_partition_key || '_check_' || p_next_partition_key
            || ' CHECK (' || c_pp.par_col || ' = ' || p_next_partition_key || ');';
            EXECUTE v_sql;

        -- Ajusta o owner da partição criada
        v_pos_erro := 190;
        SELECT 'ALTER TABLE ' || c_pp.tbl_sch || '.' || c_pp.tbl_nom || '_' || p_next_partition_key
            || ' OWNER TO ' || a.rolname || ';'
            INTO v_sql
            FROM 
                pg_class c 
                JOIN pg_namespace n ON c.relnamespace = n.oid
                JOIN pg_authid a ON c.relowner = a.oid
            WHERE
                c.relname = c_pp.tbl_nom AND 
                n.nspname = c_pp.tbl_sch;
        v_pos_erro := 200;
        EXECUTE v_sql;

        -- Ajusta GRANTs para a partição criada
        v_pos_erro := 210;
        FOR c_part IN c_loop_grant(c_pp.tbl_sch, c_pp.tbl_nom) LOOP
            v_pos_erro := 220;
            EXECUTE c_part.sql;
        END LOOP;

        -- Cria ou substitui função do gatilho
        v_pos_erro := 230;

        -- Inicio da funcao
        v_pos_erro := 240;
        v_sql := 'CREATE OR REPLACE FUNCTION ' || c_pp.tbl_sch || '.fc_tg_' || c_pp.tbl_nom || '()' || chr(10)
            || 'RETURNS TRIGGER AS $$' || chr(10)
            || 'BEGIN' || chr(10) || chr(10);

        -- Regra para a nova particao
        v_pos_erro := 250;
        v_sql := v_sql || 'IF ( NEW.' || c_pp.par_col || ' = ' || p_next_partition_key || ') THEN ' || chr(10)
            || '    INSERT INTO ' || c_pp.tbl_sch || '.' || c_pp.tbl_nom || '_' || p_next_partition_key
            || ' VALUES (NEW.*);' || chr(10);

        -- Regra para as particoes ja existentes
        v_pos_erro := 260;
        IF c_pp.key_act IS NULL THEN
          c_pp.key_act = 100;
        END IF;

        v_pos_erro := 270;
        FOR c_part IN  c_loop_partitions (c_pp.par_col, c_pp.tbl_sch, c_pp.tbl_nom, c_pp.key_act) LOOP
            v_pos_erro := 280;
            v_sql := v_sql || c_part.sql;
        END LOOP;

        -- Final da funcao
        v_pos_erro := 290;
        v_sql := v_sql || 'ELSE'  || chr(10)
            || '    RAISE EXCEPTION ''Valor de chave inválida para o gatilho de INSERT na tabela %!'',' || c_pp.tbl_nom || ';'  || chr(10)
            || 'END IF;'  || chr(10)  || chr(10)
            || 'RETURN NULL;' || chr(10)
            || 'END;' || chr(10)
            || '$$' || chr(10)
            || 'LANGUAGE plpgsql;';
        v_pos_erro := 300;
        EXECUTE v_sql;

        -- Ajusta o owner função
        v_pos_erro := 310;
        SELECT 'ALTER FUNCTION ' || c_pp.tbl_sch || '.fc_tg_' || c_pp.tbl_nom || '()' 
            || ' OWNER TO ' || a.rolname || ';'
            INTO v_sql
            FROM 
                pg_class c 
                JOIN pg_namespace n ON c.relnamespace = n.oid
                JOIN pg_authid a ON c.relowner = a.oid
            WHERE
                c.relname = c_pp.tbl_nom AND 
                n.nspname = c_pp.tbl_sch;
        v_pos_erro := 320;
        EXECUTE v_sql;

        -- Verifica se o gatilho ja existe
        v_pos_erro := 330;
        SELECT 1 
            INTO v_tmp
            FROM information_schema.triggers 
            WHERE 
                trigger_name = 'tg_' || c_pp.tbl_nom AND
                trigger_schema = c_pp.tbl_sch;

        IF NOT FOUND THEN
            -- Cria o gatilho na tabela mãe
            v_pos_erro := 340; 
            v_sql := 'CREATE TRIGGER tg_' || c_pp.tbl_nom || chr(10)
                || 'BEFORE INSERT ON ' || c_pp.tbl_sch || '.' || c_pp.tbl_nom || chr(10)
                || '    FOR EACH ROW EXECUTE PROCEDURE ' || c_pp.tbl_sch || '.fc_tg_' || c_pp.tbl_nom  || '();';
            v_pos_erro := 350;
            EXECUTE v_sql;
        END IF;

        -- Cria constraints para tabelas não particionadas
        v_pos_erro := 360; 
        FOR c_part IN c_loop_constraint(c_pp.tbl_sch, c_pp.tbl_nom) LOOP
            v_pos_erro := 360; 
            EXECUTE c_part.alter_c; 
        END LOOP;

        -- Arquiva partição antiga
        v_pos_erro := 370;
        IF p_archive AND c_pp.key_arc IS NOT NULL THEN
            FOR c_part IN c_loop_arc(c_pp.tbl_sch, c_pp.tbl_nom, c_pp.key_arc) LOOP
                v_pos_erro := 380;
                EXECUTE c_part.sql;
            END LOOP;
        END IF;

        -- Apaga partição antiga
        v_pos_erro := 390;
        IF p_drop AND c_pp.key_drp IS NOT NULL THEN 
            FOR c_part IN c_loop_drop(c_pp.tbl_sch, c_pp.tbl_nom, c_pp.key_drp) LOOP
                v_pos_erro := 400; 
                EXECUTE c_part.sql; 
            END LOOP;
        END IF;
    END LOOP;

    v_pos_erro := 410;
    FOR c_part IN c_loop_const_part LOOP
        v_pos_erro := 420;
        EXECUTE c_part.alter_c; 
    END LOOP;

    RAISE WARNING 'cria_particao_tabela|Fim';

EXCEPTION
   WHEN OTHERS            THEN
      v_cod_erro  := SUBSTR(SQLERRM,1,2000);
      RAISE WARNING 'cria_particao_tabela|ERRO!!! POS: %, COD: %, MSG: %', v_pos_erro, SQLSTATE, v_cod_erro;
END;
$BODY$
  LANGUAGE plpgsql VOLATILE SECURITY DEFINER
  COST 100000;

E por fim o teste no psql:

teste=# set client_min_messages = warning;
SET
teste=# select cria_particao_tabela(2015, true, true);
WARNING:  cria_particao_tabela|Cria particao pedido_2015
WARNING:  cria_particao_tabela|Cria particao pedido_detalhe_2015
WARNING:  cria_particao_tabela|Fim
 cria_particao_tabela 
----------------------
 (0,420)
(1 row)

 

Você pode adaptar este script para trabalhar com intervalos ou outros tipos de dados alem de inteiros. Se tiver alguma sugestão para melhorar… é só comentar aqui.

Bom, acho que chega de particionamento por enquanto.

7 comentários sobre “Particionamento de Tabelas no postgres – automatizando

  1. Olá,

    Primeiro parabéns pelo site e artigo sobre particionamento de tabelas, eu não conhecia e achei muito interessante.

    Tenho uma tabela com a seguinte estrutura.

    CREATE TABLE tabela_exemplo
    (
    id integer NOT NULL DEFAULT nextval(‘tabela_exemplo_id_seq’::regclass),
    uf character varying(30),
    ano character varying(20),
    area numeric(39,15),
    shape geometry,
    CONSTRAINT tabela_exemplo_id PRIMARY KEY (objectid)
    );

    Essa tabela hoje tem 2 Milhões de registros e ela é dada carga a cada 3 meses. Hoje ela é usada para fazendo cruzamento de dados geométricos e sempre que preciso efetuar consultar nela demora para caramba.. Depois que li seu poste sobre particionamento de tabela eu queria saber sua opinião se no caso dessa tabela que tem dados geométricos é valido particionar ela?

    Curtir

  2. Normalmente as consultas feitas na tabela é sempre informando uma data(período) ou fazendo cruzamento entre shape(dados geométricos) com outras tabelas. Por isso que fiz a pergunta se acha valido particionar ela.

    Curtir

  3. Primeiramente, parabéns pelo material. Muito rico!!

    Surgiu uma dúvida: No Postgres há algum meio de fazer com que as tabelas-filhas herdem as alterações feitas na tabela mãe, como por exemplo, ao adicionarmos nova coluna à tabela mãe?

    Ou seja:
    Ao executar o script abaixo apenas a tabela mãe tem a novaColunaAdicionada:
    ALTER TABLE schema.tabela_mae ADD COLUMN novaColunaAdicionada bigint

    Como proliferar esta novaColunaAdicionada para as tabelas-filhas(particionadas)?

    Curtir

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s