jueves, 4 de agosto de 2016

Si de contar filas se trata

Resulta que esa página del manual de MySQL nunca la he visitado. O no le he hecho mucho caso.

En una aplicación PHP quería limitar el número de filas que devuelve una consulta pero, al mismo tiempo, saber cuántas filas obtendría sin esa limitación. Esto es

select ... limit 100

El problema es contar. Si hago la consulta dos veces, una sin límite para contar y otra limitada para mostrar el resultado, el cliente debe esperar a recibir todas las filas de la tabla y después otras 100.

Sí, para eso está el select count() ... Pero ocurre una cosa curiosa. Por la naturaleza de mi programa no puedo saber cuál es esa consulta así que lo primero que piensas es en convertirla en subconsulta:

select count(*) from (select ...) tt;

Esto funciona casi siempre salvo si la consulta original es select * from t1,t2 where t1.c = t2.c, por ejemplo. Si ejecuto esto, pues eso, me aparecen las filas correspondientes. Pero si trato de ejecutar esto:

select count(*) from (select * from t1,t2 where t1.c = t2.c) tt

MySQL me lo niega, y no muy amablemente. Error.

En realidad, el problema reside en que esas dos tablas tienen columnas que se llaman igual. Tampoco me voy a poner a pensar en por qué, es así y punto. Como digo, no puedo preparar la consulta original —sustituir el * por las columnas y renombrarlas—, mi programa debe funcionar con ella tal cual. Lo siguiente es programar del lado del cliente más o menos complejas variantes de la anterior con la única intención de conseguir la cuenta de filas. Algo así como 

select count(*) from (select 1 from t1,t2 where t1.c = t2.c) tt

Pues sí, pero no. ¿Y si la consulta fuera una UNIÓN? Estaría introduciendo un error de concordancia de columnas entre la primera y la segunda consulta. Vale, que puedo hacer un proceso más complejo, encontrar todas las ocurrencias del item select y transformarlas. Pero, ¿y si hay subconsultas?

Por fin di con las palabras adecuadas buscando en Google —no sé qué intentaba pero me costó llegar a este resultado— y llegúe a "MySQL con clase", un curso online que lleva ya bastante tiempo accesible en Internet. Siendo precisos, como cualquier curso de esta naturaleza, lo que consiguen es hacer algo más amigable el manual oficial del producto. En cualquier caso, de ellos obtuve una solución elegante y su correspondiente entrada del manual: found-rows()


select SQL_CALC_FOUND_ROWS * from t1,t2 where t1.c = t2.c limit 1;
select FOUND_ROWS();


En la select original introducimos esa modificación y la limitamos a solo 1 fila. Esto hace que MySQL solo envie una fila al cliente. La segunda select obtiene el número real de filas de la select original sin limitar. Sigue teniendo algún problemilla con la UNION: si usamos UNION ALL la cuenta es exacta, si solo usamos UNION, que elimina duplicados, solo obtendremos una cantidad aproximada.

Sí, ya sé que no he descubierto América ni por tercera vez pero, oye, a mí me ha hecho ilusión.