Banco de Dados

4 jun, 2013

Scripts e tabelas de catálogo 01: contando registros

Publicidade

Todo SGBD usa um conjunto de tabelas para registrar dados sobre os objetos que fazem parte dos bancos de dados. Estes dados são muitas vezes chamados de metadados, ou seja, são dados que descrevem detalhadamente os dados (leia-se ‘objetos’) do ambiente: tabelas, visões, índices, chaves estrangeiras, chaves primárias, procedimentos armazenados, gatilhos, etc.

As tabelas onde se gravam estes metadados costumam ser chamadas de tabelas de sistema ou catálogo. No DB2, quando se cria um novo banco de dados, automaticamente é criado um esquema chamado SYSCAT e mais uma série de tabelas de catálogo (são 151 no total). Para listar todas elas, execute o comando:

db2 list tables for schema syscat

Caso você precise identificar os campos de uma tabela, use o comando:

db2 describe table syscat.NOME_DA_TABELA

De qualquer modo, não precisamos conhecer muitas tabelas do catálogo. A grosso modo, as principais são:

CATÁLOGO DESCRIÇÃO
syscat.tables tabelas do banco, incluindo de usuários e catálogo
syscat.columns colunas de cada tabela e seus detalhes
syscat.views visões do banco, incluindo de usuários e catálogo
syscat.indexes índexes do banco incluindo chaves primárias
syscat.schemata esquemas usados no banco
syscat.triggers todos os gatilhos, incluindo seus códigos
syscat.procedures todos os procedimentos armazenados, incluindo seus códigos
syscat.functions todas as funções, incluindo seus códigos
syscat.references definições das chaves estrangeiras

Evidentemente, nunca se deve inserir, apagar ou alterar registros nas tabelas do catálogo. Mas é muito útil ler estes dados, porque eles oferecem muitas possibilidades.

Eu tenho o hábito de consultar o catálogo para criar scripts, principalmente para resolver tarefas repetitivas e tediosas.

Para apresentar a ideia, vou usar um exemplo simples. Preciso obter informações sobre a quantidade de registros de cada uma das tabelas de usuário de um banco de dados. Mas existem vários esquemas de usuário na minha base.

Eu poderia escrever uma consulta de agrupamento para cada uma das tabelas de cada um dos esquemas de usuário e seria algo do tipo:

SELECT ''NOME_SCHEMA''.''NOME_TABELA'' AS TABELA, COUNT(*) 
FROM   NOME_SCHEMA.NOME_TABELA

Isso é fácil de fazer quando se tem meia dúzia de tabelas. Mas num banco de dados real, podemos ter centenas delas. Então a ideia é usar o catálogo para criar a consulta de todas as tabelas ao mesmo tempo, de modo que o resultado mostre uma listagem completa das tabelas e a quantidade de registros.

O truque todo está em entender a declaração SQL que mostrei acima como uma simples cadeia de caracteres. Pensando assim, eu posso então usar uma consulta no catálogo para criar o script que vai fazer a consulta de todas as tabelas ao mesmo tempo.

A ideia parece confusa à primeira vista, mas ela é bem simples. Cada registro da tabela de catálogo syscat.tables informa o nome do esquema e o nome de uma das tabelas do banco. Então, eu faço uma concatenação de cadeias de caracteres de modo a escrever o que mais tarde será usado como uma declaração SQL. Vai ficar algo parecido com a declaração SQL a seguir:

--Exemplo 1
SELECT 
		'select ''' || T.TABSCHEMA || ''' as esquema, ''' || 
T.TABNAME || ''' as tabela, count(*) as registros from  ' || 
T.TABSCHEMA || '.' || T.TABNAME || '  union ' AS SENTENCA
FROM SYSCAT.TABLES T
WHERE TABSCHEMA NOT LIKE 'SYS%'

Vamos analisar esta consulta. O que foi escrito em preto é a consulta que é executada e o que está em azul são os textos complementares para que a cadeia de caracteres gerada tenha a sintaxe correta de uma nova declaração SQL. Note que para escrever uma cadeia de caracteres que contenha uma aspa simples (‘) sou obrigado a escrever duas aspas simples seguidas (‘’) para que o compilador entenda que aquilo é um caractere aspa simples e  não o delimitador da cadeia de caracteres.

Para trazer apenas as tabelas de usuário, eu escrevi a cláusula WHERE que filtra os registros em que o nome do esquema não comece com o prefixo SYS, usado pelas tabelas do catálogo.

Eu adicionei  a palavra UNION no final da cadeia de caracteres para que toda listagem resultante seja entendida como uma única consulta SQL. Mas isso tem um problema: a última linha da listagem terminará com a palavra UNION e isso não pode acontecer com uma declaração SQL. Veja a listagem a seguir:

SENTENCA

—————————————————————————————————————————-

select ‘DB2INST1’ as esquema, ‘ACT’ as tabela, COUNT(*) as registros from  DB2INST1.ACT union                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       

select ‘DB2INST1’ as esquema, ‘ADEFUSR’ as tabela, COUNT(*) as registros from  DB2INST1.ADEFUSR union                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           

select ‘DB2INST1’ as esquema, ‘VSTAFAC2’ as tabela, COUNT(*) as registros from  DB2INST1.VSTAFAC2 union

Observe que, como eu havia dito, eu posso tratar a listagem inteira como uma única declaração SQL, com exceção da palavra UNION no final do último registro. Caso você seja um perfeccionista e não queira ter o trabalho de desprezar esta última palavra quando for copiar a declaração toda, você pode usar a declaração do exemplo 1 como uma subconsulta e depois usar as funções LEFT e LENGTH para descartar os últimos seis caracteres (ou seja a cadeia “ UNION”):

SELECT LEFT(S.sentenca, LENGTH(S.sentenca, 6)) as DECLARACAO FINAL
	FROM (....) AS S

Copiando o resultado do campo SENTENCA, tenho um script pronto para consultar de uma só vez todas as tabelas de usuários do banco de dados. Na minha base de exemplo, havia 50 tabelas e a sentença criada me permite consultar todas as tabelas de uma só vez. Rodando este script, eu finalmente obtenho a lista que eu desejava no início, informando a quantidade de registros existentes em cada uma das tabelas de usuário.

esquema                    tabela                                     registros

————————— —————————    ———–

DB2INST1                  CATALOG                             0

DB2INST1                  MEUTESTE                           0

DB2INST1                  PRODUCTSUPPLIER          2

DB2INST1                  SUPPLIERS                          2

MEUESQUEMA        TABELA1                               2

MEUESQUEMA        TABELA2                               2

DB2INST1                  IN_TRAY                               3

DB2INST1                  INVENTORY                         4

…….

Esta ideia pode ser adaptada para virtualmente qualquer SGBD. Eu mesmo já usei este recurso com DB2, SQL SERVER e SYBASE. Basta conhecer as tabelas de sistema do seu SGBD.