As tabelas temporárias são um conceito útil presente na maioria dos SGBDs, embora muitas vezes funcionem de forma diferente.
Este blog descreve os recursos técnicos para este tipo de tabelas em bancos de dados PostgreSQL (versão 11) ou Oracle (versão 12c) com alguns exemplos específicos. Embora o objetivo dessas tabelas possa ser o mesmo para todos os SGBDs, suas especificidades, ou a forma de implementação e manipulação, são completamente diferentes.
Esse recurso pode ser usado por desenvolvedores ou administradores de banco de dados para armazenar resultados intermediários que serão necessários para processamento posterior a fim de fornecer boas métricas de desempenho.
Tabelas temporárias no PostgreSQL
No PostgreSQL esses objetos são válidos apenas para a sessão atual:eles são criados, usados e descartados na mesma sessão:a estrutura da tabela e os dados gerenciados são visíveis apenas para a sessão atual, portanto as outras sessões não têm acesso a as tabelas temporárias criadas nas outras sessões.
Abaixo é mostrado um exemplo simples para criar uma tabela temporária:
CREATE TEMPORARY TABLE tt_customer
(
customer_id INTEGER
)
ON COMMIT DELETE ROWS;
As tabelas temporárias são criadas em um esquema temporário:pg_temp_nn e é possível criar índices nessas tabelas:
creation index tt_cusomer_idx_1 on tt_customer(customer_id)
Como as linhas de dados dessas tabelas também podem ser excluídas, é possível liberar o armazenamento ocupado através da execução de vaccum comando:
VACUUM VERBOSE tt_customer
A analisar comando pode ser executado também nas tabelas temporárias para coletar as estatísticas:
ANALYZE VERBOSE tt_customer;
Ambos os comandos podem ser executados para este tipo de tabela como comando SQL, porém, o comando autovaccum daemon que os executa não atua nas tabelas temporárias.
Outro ponto importante a ser considerado está relacionado às tabelas permanentes e temporárias com o mesmo nome:uma vez que isso acontece, a tabela permanente só é levada em consideração quando chamada com seu esquema como prefixo.
web_db=# BEGIN TRANSACTION;
BEGIN
web_db=# SELECT COUNT(*) FROM customers;
count
---------
1030056
(1 row)
web_db=# CREATE TEMPORARY TABLE customers(
web_db(# id INTEGER
web_db(# )
web_db-# ON COMMIT PRESERVE ROWS;
CREATE TABLE
web_db=# INSERT INTO customers(id) VALUES(1023);
INSERT 0 1
web_db=# SELECT COUNT(*) FROM customers;
count
-------
1
(1 row)
web_db=# \dt *customers*
List of relations
Schema | Name | Type | Owner
-----------+----------------------+-------+----------
pg_temp_5 | customers | table | postgres
web_app | customers | table | postgres
web_app | customers_historical | table | postgres
(3 rows)
web_db=# DROP TABLE customers;
DROP TABLE
web_db=# \dt *customers*
List of relations
Schema | Name | Type | Owner
---------+----------------------+-------+----------
web_app | customers | table | postgres
web_app | customers_historical | table | postgres
(2 rows)
web_db=# SELECT COUNT(*) FROM web_app.customers;
count
---------
1030056
(1 row)
web_db=# SELECT COUNT(*) FROM customers;
count
---------
1030056
(1 row)
Do exemplo anterior, enquanto a tabela temporária existe, todas as referências aos clientes refere-se a esta tabela em vez da permanente.
Dicas do desenvolvedor para tabelas temporárias
O objetivo deste exemplo é atribuir um bônus para os clientes que não fizeram compras ou login por mais de um ano, então o script do desenvolvedor ao invés de usar sub-consultas nas consultas como uma possível solução (ou o uso de CTEs declaração) pode usar tabelas temporárias (que geralmente são mais rápidas do que usar subconsultas):
web_db=# BEGIN TRANSACTION;
BEGIN
web_db=# CREATE TEMPORARY TABLE tt_customers(
web_db(# id INTEGER
web_db(# )
web_db-# ON COMMIT DELETE ROWS;
CREATE TABLE
web_db=# SELECT COUNT(*) FROM tt_customers;
count
-------
0
(1 row)
web_db=# INSERT INTO tt_customers(id)
web_db-# SELECT customer_id
web_db-# FROM web_app.orders
web_db-# WHERE order_dt <= NOW()-INTERVAL '6 MONTH';
INSERT 0 1030056
web_db=# SELECT COUNT(*) FROM tt_customers;
count
---------
1030056
(1 row)
web_db=# DELETE FROM tt_customers c
web_db-# WHERE EXISTS(SELECT 1
web_db(# FROM web_app.users u JOIN web_app.login l
web_db(# ON (l.user_id=u.user_id)
web_db(# WHERE u.customer_id=c.id
web_db(# AND l.login_dt > NOW()-INTERVAL '6 MONTH'
web_db(# );
DELETE 194637
web_db=# SELECT COUNT(*) FROM tt_customers;
count
--------
835419
(1 row)
web_db=# UPDATE web_app.customers as c SET BONUS=5
web_db-# FROM tt_customers t
web_db-# WHERE t.id = c.id;
UPDATE 835419
web_db=# SELECT COUNT(*) FROM tt_customers;
count
--------
835419
(1 row)
web_db=# COMMIT TRANSACTION;
COMMIT
web_db=# SELECT COUNT(*) FROM tt_customers;
count
-------
0
(1 row)
Dicas de DBA para tabelas temporárias
Uma tarefa típica para administradores de banco de dados é limpar todas as tabelas enormes que contêm dados que não são mais necessários. Isso precisa ser concluído muito rapidamente e acontece com frequência. A abordagem padrão é mover esses dados para uma tabela histórica em outro esquema ou para um banco de dados acessado com menos frequência.
Portanto, para realizar essa movimentação, devido a problemas de desempenho, a melhor solução pode ser usar tabelas temporárias:
CREATE TEMPORARY TABLE tt_customer
(
customer_id INTEGER
)
ON COMMIT DROP;
Neste exemplo, a tabela temporária foi criada com a opção DROP, portanto significa que será descartada no final do bloco de transação atual.
Aqui estão algumas outras informações importantes sobre as tabelas temporárias do PostgreSQL:
- As tabelas temporárias são descartadas automaticamente no final de uma sessão ou, conforme apresentado no exemplo anterior, no final da transação atual
- As tabelas permanentes com o mesmo nome não são visíveis para a sessão atual enquanto a tabela temporária existir, a menos que sejam referenciadas com nomes qualificados pelo esquema
- Quaisquer índices criados em uma tabela temporária também são automaticamente temporários
- ON COMMIT preserva linhas é o comportamento padrão
- Opcionalmente, GLOBAL ou LOCAL podem ser gravados antes de TEMPORARY ou TEMP. Isso atualmente não faz diferença no PostgreSQL e está obsoleto
- O autovácuo O daemon não pode acessar essas tabelas e, portanto, não pode limpar ou analisar tabelas temporárias, no entanto, conforme mostrado anteriormente, os comandos autovacuum e analyze podem ser usados como comandos SQL.
Tabelas temporárias globais (GTT) no Oracle
Esse tipo de tabela é conhecido no mundo Oracle como Global Temporary Table (ou GTT). Esses objetos são persistentes no banco de dados e podem ser resumidos pelas seguintes características:
- A estrutura é estática e visível para todos os usuários, no entanto, seu conteúdo só é visível para a sessão atual
- Pode ser criado em um esquema específico (por padrão será de propriedade do usuário que emite o comando) e são construídos no tablespace TEMP
- Uma vez criado no banco de dados, ele não pode ser criado novamente em cada sessão, no entanto, os dados gerenciados por uma sessão não são visíveis para as outras sessões
- É possível a criação de índices e geração de estatísticas
- Como a estrutura dessas tabelas também é definida no banco de dados, não é possível atribuir seu nome a uma tabela permanente (no Oracle dois objetos não podem ter o mesmo nome mesmo de tipos diferentes)
- Não gere muitos logs de redo e a sobrecarga de desfazer também é menor em comparação com uma tabela permanente (apenas por esses motivos o uso de GTT é mais rápido) para qualquer versão anterior a 12c. A partir da versão 12c, há um conceito de desfazer temporário, permitindo que o desfazer de um GTT seja gravado no tablespace temporário, reduzindo assim o desfazer e o refazer.
Seguindo o mesmo exemplo apresentado no PostgreSQL, a criação de um GTT é bastante semelhante:
CREATE GLOBAL TEMPORARY TABLE tt_customer
(
customer_id NUMBER
)
ON COMMIT DELETE ROWS;
É possível também a criação de índices.
creation index tt_cusomer_idx_1 on tt_customer(customer_id)
Antes do Oracle 12c a geração de estatísticas para tabelas temporárias globais tinha um comportamento de forma global:as estatísticas geradas em uma sessão específica para um GTT específico eram visíveis e usadas para as demais sessões (somente estatísticas não os dados!), porém, a partir da versão 12c é possível que cada sessão gere suas próprias estatísticas.
Antes de tudo é necessário definir a preferência global_temp_table_stats para sessão :
exec dbms_stats.set_table_prefs(USER,’TT_CUSTOMER’,’GLOBAL_TEMP_TABLE_STATS’,’SESSION’);
e depois a geração de estatísticas:
exec dbms_stats.gather_table_stats(USER,’TT_CUSTOMER’);
A tabela temporária global existente pode ser verificada pela execução da seguinte consulta:
select table_name from all_tables where temporary = 'Y';
Dicas do desenvolvedor para tabelas temporárias globais (GTT)
Seguindo o exemplo da seção PostgreSQL:para atribuir um bônus para os clientes que não efetuaram compras ou login por mais de um ano, o uso de tabelas temporárias globais no Oracle tem o mesmo objetivo que no PostgreSQL:obter melhor desempenho seja no uso do recurso ou na velocidade de execução.
SQL> SELECT COUNT(*) FROM tt_customers;
COUNT(*)
----------
0
SQL>
SQL> INSERT INTO tt_customers(id)
2 SELECT customer_id
3 FROM orders
4 WHERE order_dt <= ADD_MONTHS(SYSDATE,-6);
1030056 rows created.
SQL>
SQL> SELECT COUNT(*) FROM tt_customers;
COUNT(*)
----------
1030056
SQL>
SQL> DELETE FROM tt_customers c
2 WHERE EXISTS(SELECT 1
3 FROM users u JOIN login l
4 ON (l.user_id=u.user_id)
5 WHERE u.customer_id=c.id
6 AND l.login_dt > ADD_MONTHS(SYSDATE,-6)
7 );
194637 rows deleted.
SQL>
SQL> SELECT COUNT(*) FROM tt_customers;
COUNT(*)
----------
835419
SQL>
SQL> UPDATE CUSTOMERS c SET BONUS=5
2 WHERE EXISTS(SELECT 1 FROM tt_customers tc WHERE tc.id=c.id);
835419 rows updated.
SQL>
SQL> SELECT COUNT(*) FROM tt_customers;
COUNT(*)
----------
835419
SQL>
SQL> COMMIT;
Commit complete.
SQL>
SQL> SELECT COUNT(*) FROM tt_customers;
COUNT(*)
----------
0
SQL>
Por padrão, no Oracle, um bloco/instrução SQL/PLSQL inicia implicitamente uma transação.
Dicas de DBA para tabelas temporárias globais (GTT)
Como a declaração deixar não existe para tabelas temporárias globais o comando para criar a tabela é igual ao anterior:
CREATE GLOBAL TEMPORARY TABLE tt_customer
(
customer_id NUMBER
)
ON COMMIT DELETE ROWS;
O trecho de código equivalente no Oracle para limpar o cliente tabela é o seguinte:
SQL> INSERT INTO tt_customers(id)
2 SELECT l.user_id
3 FROM users u JOIN login l
4 ON (l.user_id=u.user_id)
5 WHERE l.login_dt < ADD_MONTHS(SYSDATE,-12);
194637 rows created.
SQL>
SQL> INSERT INTO tt_customers(id)
2 SELECT user_id
3 FROM web_deactive;
2143 rows created.
SQL>
SQL> INSERT INTO tt_customers(id)
2 SELECT user_id
3 FROM web_black_list;
4234 rows created.
SQL>
SQL> INSERT INTO customers_historical(id,name)
2 SELECT c.id,c.name
3 FROM customers c,
4 tt_customers tc
5 WHERE tc.id = c.id;
201014 rows created.
SQL>
SQL> DELETE FROM customers c
2 WHERE EXISTS (SELECT 1 FROM tt_customers tc WHERE tc.id = c.id );
201014 rows deleted.
A biblioteca pg_global_temp_tables
Como mencionado acima, as tabelas temporárias no PostgreSQL não podem ser invocadas usando a notação schema.table , então a biblioteca pg_global_temp_tables (existem algumas bibliotecas semelhantes disponíveis no github) é uma solução muito útil para ser usada em migrações de banco de dados Oracle para PostgreSQL.
Para manter a notação Oracle schema.temporary_table em consultas ou procedimentos armazenados:
SELECT c.id,c.nam
FROM web_app.tt_customers tc,
Web_app.customers c
WHERE c.id = tc.id
Permite manter as tabelas temporárias sobre o código com a notação de esquema.
Basicamente, consiste em uma visualização:web_app.tt_customers criado sob o esquema no qual deveria ter a tabela temporária e essa visualização consultará a tabela temporária tt_customers por meio de uma função chamada web_app.select_tt_customers :
CREATE OR REPLACE VIEW WEB_APP.TT_CUSTOMERS AS
SELECT * FROM WEB_APP.SELECT_TT_CUSTOMERS();
Esta função retorna o conteúdo da tabela temporária:
CREATE OR REPLACE FUNCTION WEB_APP.SELECT_TT_CUSTOMERS() RETURNS TABLE(ID INR, NAME VARCHAR) AS $$
BEGIN
CREATE TEMPORARY TABLE IF NOT EXISTS TT_CUSTOMERS(ID INT, NAME) ON COMMIT DROP;
RETURN QUERY SELECT * FROM TT_CUSTOMERS;
END;
$$ LANGUAGE PLPGSQL;
Resumo
As tabelas temporárias são usadas essencialmente para armazenar resultados intermediários e assim evitar computação complexa e pesada,
A seguir são listadas algumas características das tabelas temporárias tanto no PostgreSQL quanto no Oracle:
- Pode ser usado na visualização
- Pode usar o comando TRUNCATE
- Não pode ser particionado
- A restrição de chave estrangeira em tabelas temporárias não é permitida
- Este tipo de tabelas são uma alternativa para CTEs (Common Table Expressions) também conhecidas pelos profissionais Oracle como cláusula WITH
- Em termos de segurança e privacidade, essas tabelas são um recurso valioso porque os dados são visíveis apenas para uma sessão atual
- As tabelas temporárias são automaticamente descartadas (no PostgreSQL) ou excluídas (no Oracle) assim que a sessão/transação termina.
Para as tabelas temporárias do PostgreSQL é aconselhável não usar o mesmo nome de uma tabela permanente em uma tabela temporária. Do lado da Oracle é uma boa prática a geração de estatísticas para as sessões que incluem volume considerável de dados em GTT para forçar o Cost-Based Optimizer (CBO) a escolher o melhor plano para as consultas que estão usando este tipo de tabelas .