miércoles, 29 de abril de 2015

Servidor MySQL denuncia a sus usuarios por maltrato

O eso ocurriría si el pobre pudiera hablar. Es recurrente, ocurre todos los años en cuanto llegan los ejercicios que hemos llamado de "aritmética de columna". Hay un ejercicio especialmente gracioso:

T09.017- Cantidad de artículos que no son ni memoria, ni tv, ni objetivo, ni cámara ni pack.
Pongámonos en situación. Los artículos de TiendaOnLine están catalogados en grandes familias como televisores, cámaras, objetivos, etc. Estructuralmente lo que nos encontramos son códigos de artículo que aparecen en una de esas tablas, además de ciertos atributos propios, no comunes a todos los artículos. Todos tienen marca y precio de venta al público (PVP) pero solo los televisores tienen, además, "panel". Es lo que se llama una "generalización": los televisores son artículos, los objetivos son artículos... Por eso hay una tabla general "artículo" y otra, especializada, para cada familia. Estas últimas tienen una clave ajena hacia "artículo" que es, al mismo tiempo, clave primaria de la tabla especializada.

El resultado en nuestra base de datos es cero, todos los artículos están catalogados en alguna de esas familias. Dicho de otra forma, todos los códigos de artículo aparecen en dos de esas tablas, en la de "artículo" —ahí están todos— y en una de las especializadas —o en "tv", o en "objetivo", o en "memoria"...—. Esto es así simplemente por los datos con que hemos cargado la base de datos, con otros datos el resultado podría ser distinto.

Lo gracioso no es esto sino que todos los años MySQL se nos queja de que más de uno intenta

select count(*)
from articulo, camara, objetivo, pack, memoria, tv
where articulo.cod!=camara.cod
and articulo.cod!=tv.cod
and articulo.cod!=pack.cod
and articulo.cod!=objetivo.cod
and articulo.cod!=memoria.cod



Esta barbaridad debería poderse cobrar en forma de sanción administrativo-penal.

Vamos a hacerlo más simple, "cantidad de artículos que no son memoria". Partimos del siguiente estado de base de datos:



ARTÍCULO
cod nombre ...
A1 artículo 1 ...
A2 artículo 2 ...
A3 artículo 3 ...

MEMORIA
cod tipo ...
A1 tipo A ...
A2 tipo A ...
A3 tipo B ...

Démonos cuenta de que lo que dicen estas dos tablas es que tenemos 3 artículos y que todos ellos son "memorias". Vamos, que el resultado que finalmente nos encontraremos es cero. No obstante, probemos lo que nos proponen.

select count(*)
from articulo, memoria
where articulo.cod!=memoria.cod


Es fundamental abordar este tipo de ejercicios desde el producto cartesiano para entender lo que vamos a conseguir. Si solo ejecutáramos select * from articulo, memoria

cod nombre ... cod tipo ...
A1 artículo 1 ... A1 tipo A ...
A2 artículo 2 ... A1 tipo A ...
A3 artículo 3 ... A1 tipo A ...
A1 artículo 1 ... A2 tipo A ...
A2 artículo 2 ... A2 tipo A ...
A3 artículo 3 ... A2 tipo A ...
A1 artículo 1 ... A3 tipo B ...
A2 artículo 2 ... A3 tipo B ...
A3 artículo 3 ... A3 tipo B ...


Ahora, a partir del resultado anterior, filtramos por lo que nos dice el where articulo.cod!=memoria.cod, "quédate con las filas cuyos códigos son distintos"


cod nombre ... cod tipo ...
A2 artículo 2 ... A1 tipo A ...
A3 artículo 3 ... A1 tipo A ...
A1 artículo 1 ... A2 tipo A ...
A3 artículo 3 ... A2 tipo A ...
A1 artículo 1 ... A3 tipo B ...
A2 artículo 2 ... A3 tipo B ...

Y ahora vas, cuentas, y me dices que la solución es 6, que 6 artículos —seis filas, select count(*)— no son de la familia "memoria". ¡Pero si solo hay 3 artículos en mi base de datos!

Evidentemente, tan solo pensar en que esa solución es viable demuestra un paupérrimo conocimiento de cómo funcionan las bases de datos relaciones en general y TiendaOnLine en partícular. Y es evidente que la diferencia de conjuntos es la única manera de solucionar este enunciado en SQL:

select count(*)
from articulo
where articulo.cod NOT IN (select memoria.cod from memoria)


Este sí da 0 (cero) artículos. Claro, está contando aquellos códigos de artículo que no se encuentran en la lista de códigos que obtenemos de la tabla "memoria".

La orden SQL que dio origen a este artículo, la primera que hemos puesto, genera un producto cartesiano de millones de filas —concretamente 13639162377120 filas; pon tú los puntos de millar—. De hecho tenemos configurado el servidor para que detecte estas consultas "lentas" y las aborte automáticamente. También es cierto que, para algunos, que una consulta sobre nuestra base de datos obtenga un par de cientos de miles de filas parece de lo más normal.

Que MySQL nunca adquiera conciencia propia. Que no sea el germen de una futura Skynet. Que Schwarzenegger no haga una sexta.