viernes, 14 de septiembre de 2007

Diferencias entre COUNT(1) y COUNT(*) - Parte 1

Miles y miles y miles de veces veo que se arman consultas SQL que contiene el COUNT(1). Este es un viejo mito. Muchos piensan que colocar COUNT(1) en vez del COUNT(*) mejora la performance de la consulta.... pero en realidad mejora la performance?

Veamos...

SQL_9iR2> CREATE TABLE test AS
2 SELECT level id, 'texto_'||level texto
3 FROM dual
4 CONNECT BY level <= 100000 ; Table created. SQL_9iR2> EXEC dbms_stats.GATHER_TABLE_STATS(USER,'TEST') ;

PL/SQL procedure successfully completed.

Bien, ahora ejecutemos las 2 consultas:

SQL_9iR2> ALTER SESSION SET SQL_TRACE = TRUE ;

Session altered.

SQL_9iR2> SELECT COUNT(1)
2 FROM test ;

COUNT(1)
----------
100000

1 row selected.

SQL_9iR2> SELECT COUNT(*)
2 FROM test ;

COUNT(*)
----------
100000

1 row selected.

SQL_9iR2> ALTER SESSION SET SQL_TRACE = FALSE ;

Session altered.

Veamos lo que nos muestra el TKPROF:

select count(1)
from
test


call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.00 0.00 0 0 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 2 0.01 0.01 0 306 0 1
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 4 0.01 0.01 0 306 0 1

select count(*)
from
test


call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.00 0.00 0 0 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 2 0.01 0.01 0 306 0 1
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 4 0.01 0.01 0 306 0 1

Observamos que en las 2 consultas tenemos la misma cantidad de lecturas de bloques (disk, query, current) y el mismo tiempo de elapsed y cpu.
Las 2 consultas son idénticas y no hay un incremento en la performance por utilizar el COUNT(1) en vez del COUNT(*).

Incluso podría poner cualquier cosa en el COUNT... obtendríamos los mismos resultados:

SELECT COUNT(2222222)
FROM
TEST

call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.00 0.00 0 0 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 2 0.01 0.01 0 306 0 1
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 4 0.01 0.01 0 306 0 1

SELECT COUNT('EJEMPLO')
FROM
TEST

call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.00 0.00 0 0 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 2 0.01 0.01 0 306 0 1
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 4 0.01 0.01 0 306 0 1

SELECT COUNT(12345)
FROM
TEST

call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.00 0.00 0 0 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 2 0.01 0.01 0 306 0 1
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 4 0.01 0.01 0 306 0 1

Bien, por último veamos los siguiente. Qué sucede si realizo un COUNT de alguna de las columnas de la tabla? Veamos...

SQL_9iR2> SELECT COUNT(texto)
2 FROM test ;

COUNT(TEXTO)
------------
100000

1 row selected.

La consulta nos devolvió la cantidad de registros totales de la tabla. Ahora veamos qué me devuelve la consulta si la columna TEXTO tiene valores nulos.

SQL_9iR2> UPDATE test
2 SET texto = NULL
3 WHERE id > 50000 ;

50000 rows updated.

SQL_9iR2> EXEC dbms_stats.GATHER_TABLE_STATS(USER,'TEST') ;

PL/SQL procedure successfully completed.

SQL_9iR2> SELECT COUNT(texto)
2 FROM test ;

COUNT(TEXTO)
------------
50000

1 row selected.

Como podemos observar, el COUNT sobre una columna sólo cuenta la cantidad de valores que no son nulos. Por lo tanto, tenemos que tener cuidado a la hora de realizar un COUNT y tener en cuenta estos pequeños detalles.

7 comentarios:

Anónimo dijo...

Hola Leonardo, muy interesante artículo. Te queria consultar algo que lei en Internet y queria tu opinión. Es sobre el tema de comprobar si existen registros para cierta condición, donde usan la siguiente sentencia: SELECT DISTINCT 1 FROM X WHERE xxx; y mencionan que es más efectivo que realizar SELECT COUNT(*) FROM X WHERE xxx. Gracias

Leonardo Horikian dijo...

Hola, no se donde leíste eso, pero es muy raro que SELECT DISTINCT 1 FROM X WHERE xxx; sea más eficiente que hacer un COUNT(*). Sinceramente no le veo sentido el hacer un DISTINCT; por otro lado, el DISTINCT realiza un Sort que consume memoria y/o disco, en cambio el COUNT no realiza ningún tipo de sort. Sería conveniente que hagas una prueba utilizando las 2 alternativas (la que yo propongo y la que vos me mencionas) y veas mediante un AUTOTRACE las diferencias entre ambas.

Anónimo dijo...

Hola Leonardo, primero muchas felicidades por el apoyo y compartir tus conocimientos, la verdad yo tambien tenia esa incognita...pero ahora veo q no hay diferencia entre las dos instrucciones. Pero tenia otra duda espero que me la puedas despejar es algo similar "Cual la diferencia entre una tabla generada con una consulta SQL y una tabla generada con una vista"

Anónimo dijo...

Hola Leonardo, primero muchas felicidades por el apoyo y compartir tus conocimientos, la verdad yo tambien tenia esa incognita...pero ahora veo q no hay diferencia entre las dos instrucciones. Pero tenia otra duda espero que me la puedas despejar es algo similar "Cual la diferencia entre una tabla generada con una consulta SQL y una tabla generada con una vista"

Leonardo Horikian dijo...

Hola, no logro entender tu pregunta. Podrías explicarla más en detalle? Saludos.

Pablo Moreno dijo...

Hola, no se si todavía den soporte a este blog... había escuchado algo de que si en un SP hay una sentencia con un count(*) y se realiza una alteración sobre esta tabla (ejemplo eliminarle una columna)esto causa que se descompile el paquete o el SP...no se si sea cierto, pero de ser así, entonces sería mas eficiente usar el count(1) para que no descompile. Saludos

Leonardo Horikian dijo...

Hola Pablo Moreno,

Lo que mencionás NO es cierto; es sólo un mito muy antiguo.

Voy a mostrarte que esto que mencionás no es cierto con un simple ejemplo:


SQL> CREATE TABLE tbl_prueba (id NUMBER);

Table created.

SQL> CREATE OR REPLACE PROCEDURE prc_prueba
IS
vCount NUMBER;
BEGIN
SELECT COUNT(*) INTO vCount FROM tbl_prueba;
END;
/

Procedure created.

SQL> SELECT status FROM dba_objects WHERE object_name='PRC_PRUEBA';

STATUS
-------
VALID

SQL> ALTER TABLE tbl_prueba ADD nombre VARCHAR2(40);

Table altered.

SQL> SELECT status FROM dba_objects WHERE object_name='PRC_PRUEBA';

STATUS
-------
VALID


En este ejemplo lo que hice fue crear una tabla con una columna y un procedimiento que realiza un count(*) sobre la tabla. Luego procedí a verificar el estado del procedimiento para asegurarme de que se encuentre en estado válido.
Por último agregué una columna más a la tabla y comprobé que el procedimiento siga estando en estado válido.

Saludos