Select count no Oracle

Figura 1: Select count no Oracle

Veja nesse artigo algumas maneiras de fazer o select count. Falaremos em mitos, performance e algumas maneiras mais eficazes de se fazer o select count, na realidade, de se obter quantidade de registros de uma tabela.

A primeira abordagem será sempre olhar para a DBA_TABLES ou ALL_TABLES (coluna num_rows), pois se a tabela estiver com as estatísticas atualizadas teremos a quantidade de registros na tabela sem muito esforço.

Alguns itens que temos de levar em consideração são:

O select count irá contar apenas registros not null;

Quando fazemos um select count(*), o Oracle irá validar todas as colunas da tabela e irá dar o valor de todos os registros not null de todas as colunas (maior quantidade). Se necessitarmos saber a quantidade de registros de uma coluna especifica e usarmos o select count(*) poderemos ter uma quantidade errada de registros, ou seja, o Oracle irá ignorar os null. Imaginemos uma tabela com duas colunas (Col1 e Col2) a Col1 é not null e na Col2 há registros nulos, queremos agora saber quantos registros existem nessa coluna. Se fizermos select count(*) tabela, o Oracle irá devolver a quantidade total de registros da tabela, tendo em consideração a Col1 (pois são todos not null) porém se fizermos select count (Col2) from tabela, teremos a quantidade exata de registros not null dessa coluna da tabela.

Procurar sempre verificar se temos índices na tabela;

Caso tenhamos uma coluna indexada, devemos usar essa coluna no select count(), pois o Oracle irá fazer um Fast Full Scan, que é melhor que o Full Table Scan.

Se tivermos uma PK ou Índice único na tabela, o Oracle sempre irá utilizar esse índice para fazer o count, caso não especifique o nome da coluna.

Outras formas de obter a quantidade estimada de registros;

Se precisamos obter a quantidade de registros estimada de uma tabela e a mesma não possui índices e nem estatísticas atualizadas e a tabela é grande, podemos usar o “sample” que nos dará um valor estimado de registros utilizando apenas uma estimativa da tabela.

Veremos agora mais abaixo os cenários descritos acima e alguns exemplos:

Caracterização do cenário:

Foi criada uma tabela com diferentes tipos de dados e alguns índices. Podem ver nos scripts abaixo:

Listagem 1: Script para criar uma tabela com índice


CREATE TABLE table1
(
   name      VARCHAR2 (255),
   id        NUMBER,
   column2   VARCHAR2 (50),
   column3   VARCHAR2 (2000)
);

CREATE INDEX H_ID
   ON SYSTEM.table1 (ID);

Listagem 2: Estatísticas da tabela


BEGIN
   DBMS_STATS.gather_table_stats (ownname            => 'SYSTEM',
                                  tabname            => 'TABLE1',
                                  estimate_percent   => 100,
                                  granularity        => 'ALL',
                                  cascade            => TRUE);
END;

Listagem 3: Script to populate table


DECLARE
BEGIN
   FOR i IN 1 .. 10000
   LOOP
      INSERT INTO table1
         SELECT DBMS_RANDOM.string ('U', 254) str,
                DBMS_RANDOM.VALUE (100000000000, 999999999999),
                DBMS_RANDOM.string ('U', 49) str,
                DBMS_RANDOM.string ('U', 1998) str
           FROM DUAL;

      COMMIT;
   END LOOP;

   COMMIT;
END;

Observação: Atenção que aleatoriamente foram inseridos 10 mil registros NULL, menos para a coluna ID.

Caso de teste 1

Precisamos saber quantidade de registros de uma tabela, onde não temos estatísticas, não tem PK e nem um unique index.

Nesse caso, se fizermos um select count(*) from tabela, iremos fazer um TAF (Table Access Full), se a tabela for pequena não teremos problema, porém se a tabela for de alguns Gigabytes e alguns acessos desse, iremos com certeza provocar uma grande utilização de recurso no servidor (I/O) e com isso podemos fazer a base de dados ficar mais lenta.

Sendo assim, temos que procurar uma coluna indexada para fazermos o select count (col Indexada) from table e com isso passamos de TAF para FFS (Fast Full Scan) no Índice.

Vejamos o exemplo abaixo:

Listagem 4: Select count(*)


select count(*) from system.table1; 

Podemos observar nesse exemplo que a nossa tabela tem 41000 linhas e faz um TAF (Table Access FULL) na tabela TABLE1, mais abaixo podem ver a quantidade de Physical reads feita.

Listagem 5: Physical reads do primeiro exemplo


SQL> set autotrace on explain statistics 

SQL> ALTER SYSTEM FLUSH SHARED_POOL; --Limpar a memoria 

System altered. 

Elapsed: 00:00:00.06 
SQL> select count(*) from system.table1; 

  COUNT(*) 
---------- 
     41000 

Elapsed: 00:00:00.06 

Execution Plan 
---------------------------------------------------------- 
Plan hash value: 3207558960 

--------------------------------------------------------------------- 
| Id  | Operation          | Name   | Rows  | Cost (%CPU)| Time     | 
--------------------------------------------------------------------- 
|   0 | SELECT STATEMENT   |        |     1 |  2934   (1)| 00:00:36 | 
|   1 |  SORT AGGREGATE    |        |     1 |            |          | 
|   2 |   TABLE ACCESS FULL| TABLE1 | 41000 |  2934   (1)| 00:00:36 | 
--------------------------------------------------------------------- 


Statistics 
---------------------------------------------------------- 
       1208  recursive calls 
         18  db block gets 
      11018  consistent gets 
      10814  physical reads 
          0  redo size 
        527  bytes sent via SQL*Net to client 
        524  bytes received via SQL*Net from client 
          2  SQL*Net roundtrips to/from client 
         17  sorts (memory) 
          0  sorts (disk) 
          1  rows processed 

Caso de teste 2

Temos que obter o total de registros da tabela, temos uma coluna (ID) que possui índice e sabemos que essa coluna não tem nulos. Esse cenário resolve o nosso problema lindamente, ou seja, teremos a quantidade exata de registros na tabela (Coluna ID não tem nulos) e não iremos fazer um TAF e sim um FFS no índice, diminuindo assim brutalmente o tempo de execução do select count como também utilizaremos muito menos recursos no servidor.

Listagem 6: Select count sobre a coluna not null

select count(ID) from system.table1; 

Podemos ver abaixo que temos um INDEX FAST FULL SCAN no índice da coluna ID (índice H_ID) e com isso já temos uma redução drástica de consumo de recursos do servidor. A query é resolvida em 1/3 do tempo, mas o mais importante aqui é mesmo a redução do consumo de recursos para executar a mesma query. Imaginem 100 pessoas em simultâneo a fazer a mesma query, teríamos que ter grandes servidores.

Listagem 7: Execução do select count sobre a coluna not null


SQL> ALTER SYSTEM FLUSH SHARED_POOL;     

System altered. 

Elapsed: 00:00:00.02 

SQL> select count(ID) from system.table1; 

 COUNT(ID) 
---------- 
     41000 

Elapsed: 00:00:00.02 

Execution Plan 
---------------------------------------------------------- 
Plan hash value: 866143595 

------------------------------------------------------------------------------ 
| Id  | Operation             | Name | Rows  | Bytes | Cost (%CPU)| Time     | 
------------------------------------------------------------------------------ 
|   0 | SELECT STATEMENT      |      |     1 |    22 |    53   (0)| 00:00:01 | 
|   1 |  SORT AGGREGATE       |      |     1 |    22 |            |          | 
|   2 |   INDEX FAST FULL SCAN| H_ID | 41000 |   880K|    53   (0)| 00:00:01 | 
------------------------------------------------------------------------------ 


Statistics 
---------------------------------------------------------- 
        303  recursive calls 
          0  db block gets 
        241  consistent gets 
        194  physical reads 
          0  redo size 
        528  bytes sent via SQL*Net to client 
        524  bytes received via SQL*Net from client 
          2  SQL*Net roundtrips to/from client 
          6  sorts (memory) 
          0  sorts (disk) 
          1  rows processed 

Caso de teste 3

Esse cenário é um cenário onde não conseguimos ter nenhum dos cenários acima, ou seja, a tabela é muito grande e não há nenhum índice, não tem estatísticas atualizadas. Porém precisamos ter apenas uma estimativa de quantidade de registros que essa tabela possui, uma contagem não tão precisa.

Listagem 8: Count sem muita precisão


SELECT COUNT(1) *100 FROM system.table1 SAMPLE (1); --retornará 1% da tabela e se fizermos  a multiplicação por 100 , teremos a estimativa da quantidade total de registros.

Listagem 9: Execução da contagem não precisa


SQL> SELECT COUNT(1) *100 FROM system.table1 SAMPLE (1); 

COUNT(1)*100 
------------ 
       52700 

Elapsed: 00:00:00.07 

Execution Plan 
---------------------------------------------------------- 
Plan hash value: 2121025407 

------------------------------------------------------------------------------- 
| Id  | Operation            | Name   | Rows  | Bytes | Cost (%CPU)| Time     | 
------------------------------------------------------------------------------- 
|   0 | SELECT STATEMENT     |        |     1 |    12 |  2934   (1)| 00:00:36 | 
|   1 |  SORT AGGREGATE      |        |     1 |    12 |            |          | 
|   2 |   TABLE ACCESS SAMPLE| TABLE1 |   410 |  4920 |  2934   (1)| 00:00:36 | 
------------------------------------------------------------------------------- 


Statistics 
---------------------------------------------------------- 
        236  recursive calls 
          0  db block gets 
       1395  consistent gets 
       1982  physical reads 
          0  redo size 
        531  bytes sent via SQL*Net to client 
        524  bytes received via SQL*Net from client 
          2  SQL*Net roundtrips to/from client 
          4  sorts (memory) 
          0  sorts (disk) 
          1  rows processed 

Observação: Nesse exemplo temos a tabela com mais registros e por isso o tempo é diferente do teste inicial, porém se olharmos para as estatísticas de execução da query, veremos um consumo bem menor para a execução da query.

Bom, pessoal, fico por aqui nessa pequena dica. Abraços.