miércoles, 21 de febrero de 2018

A vueltas con UPDATE

Más pronto o más tarde nos encontraremos con esta situación. Aprovecho la base de datos EJEMPLO y su tabla PROFESORES de la asignatura —y de paso enlazo FBDdocs—. Si quieres seguir la historia que voy a contar, nada más fácil que usar tu base de datos propia y hacer

create table profesores (select * from ejemplo.profesores)

Ahora tienes una copia de esa tabla y puedes modificar sus filas.


El caso que quiero plantear es típico, te pasan una lista de modificaciones que debes incorporar a tu base de datos. En esta ocasión, actualizaciones de la categoría de cada profesor. Te puede venir en un archivo CSV, en texto tabulado, yo qué se... A nosotros nos han mandado esto que ya tenemos en una tabla auxiliar PROF2.



El ejemplo solo tiene 3 filas pero estamos hablando de cuando hay 1000, 2000, 100000 filas que actualizar. No te vas a poner a

update profesores set categoría='TU' where dni='21111222'

Y así hasta terminar con los 1000, 2000... Ni hablar, lo que yo quiero es una única instrucción que los actualice todos de golpe. El problema es que update solo trabaja con una fila y unicamente conoce los valores de la fila que filtremos. Debo conseguir que update disponga de toda la información correlacionada para cada fila de PROFESOR.

Llegará el típico programador que solo piensa en bucles e iteraciones y dirá !cursores!. Que no, que SQL no es iterativo, ni falta que le hace. Para eso está el JOIN. ¿Verdad que si concateno las filas dispongo de toda la información necesaria?

select * from profesores p join prof2 p2 on (p.dni=p2.dni)


Si la tabla se llamara RESULTADO sería tan simple como

update RESULTADO set categoria = nueva

En cada fila, reemplazaría el valor de "categoría" por el de "nueva". Fenómeno, pero no es lo que queremos, la tabla PROFESORES no es la tabla RESULTADO. Pero casi, vamos a aprovechar el JOIN:

update 
profesores p join prof2 p2 on (p.dni=p2.dni)
set p.categoria=p2.categoria

Y a continuación select * from profesores:


Parece magia pero no lo es. El servidor sabe en todo momento qué columnas está manejando —las de profesores y las de prof2— y simplemente usa un resultado intermedio —el join— para disponer de todos los datos necesarios antes de actualizar los valores de una columna. Finalmente, como la columna PROFESORES.categoria es la que se actualiza, es la tabla PROFESORES la que recibe dicha modificación. Fijate que si hubiéramos hecho set p2.categoria=p.categoria, o sea, al revés, habría sido PROF2 la tabla actualizada.

Dice MySQL que soporta UPDATE multitabla, esto es, lo de antes o bien su equivalente:

update profesores p, prof2 p2
set p.categoria=p2.categoria
where p.dni=p2.dni

Ya depende de con qué estés más cómodo. Lo cierto es que la sintaxis JOIN permite el uso de LEFT JOIN en un UPDATE, cosa que no podrías hacer con justo lo anterior. Asegúrate, eso sí, de que tu servidor de base de datos admite la una y la otra y en qué condiciones. Por lo general, el UPDATE-JOIN funciona.

Amantes de los bucles, no lloréis.

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:

miércoles, 6 de mayo de 2015

NULO es NULO

Hablemos del NULL, del "nulo", pero en MySQL. Cosas que si alguna vez las he sabido, se me habían olvidado.

Parece ser que la necesidad del nulo surgió del propio día a día que quería representar y gestionar el modelo relacional. Es muy habitual encontrarse con formularios donde falta algún dato, bien por ignorancia o por poca relevancia. Vamos, que casi da igual que lo pongas que no, no es importante. En otros casos incluso podría ser inaplicable, pongamos "color de pelo" si resulta que antes has elegido "piedra". Para eso es el nulo.

NULL es un, llamémosle, valor de difícil implementación. De hecho, no es un valor, es un estado, y un nulo no es igual a otro nulo. En clase solemos decir "no sabemos si tiene valor y, si tiene, no sabemos cuál es". En SQL se nota porque no se puede preguntar x = NULL. El nulo no se puede utilizar en comparaciones con "igual", "menor que" y todos los demás. Tenemos que utilizar un operador específico, x IS NULL o x IS NOT NULL.

Para liarla más autores hay que dicen que, precisamente el nulo, es el mayor error de Codd al definir el modelo relacional (1). Y Codd dice que sin los nulos no hay modelo relacional. Lo cierto es que su aplicación más clara es en las claves ajenas donde la existencia de un nulo se interpreta como ausencia de relación. Por eso la mayoría de los sistemas de gestión de bases de datos lo tratan como "ausencia de valor" o "sin valor". Esto hace las cosas más fáciles ya que el nulo solo puede trabajarse con el mencionado operador IS.

Pues bien, MySQL trata el nulo como lo que es en la teoría.

Supongamos un conjunto, típicamente un resultado de una consulta que contiene uno o varios nulos:

C1 = select a from t1

a
NULL
1
2
3



Otra consulta nos da los siguientes resultados:

C2 = select b from t2

b
 1 
1
4

Y ahora, la prueba. Queremos saber qué valores de la consulta C2 no están en el resultado de la consulta C1.

C3 = select b from t2 where b NOT IN (select a from t1)

 b 
(vacío, sin filas)

Uno espera que el resultado sea "4". Pues no, resultado vacío de filas. Todos los valores, "1" y "4", están en la tabla t1. ¿Cómo  puede ser esto?

Hay que irse a al manual y comprobar las tablas del nulo. Resumiendo, si utilizamos el x IS NULL podremos obtener un valor verdadero o falso —1 o 0— pero si comparamos como toda la vida se ha hecho

x = NULL, x <> NULL, x < NULL 

el resultado es  es siempre NULL, falso, 0. En MySQL dicen que NULL y 0 significan falso. Cualquier otra cosa, cierto. Incluso NULL = NULL da falso.

Simplifiquemos el ejemplo: select 1 not in (2, 3) es lo mismo que

select (1<>2) and (1<>3) = 1 and 1 = cierto AND cierto = CIERTO

Pero select 1 not in (null, 3) es lo mismo que

select (1<>null) and (1<>3) = 0 AND 1 = falso AND cierto = FALSO.


Fácil. Nulo es nulo en MySQL. O sea, que como hay un nulo en el resultado de la subconsulta (select a from t1), ese nulo podría ser cualquier cosa, concretamente podría ser un "4". 

Para más información, la fuente habitual: http://dev.mysql.com/doc/refman/5.0/en/working-with-null.html.

Avisados quedamos.


(1) C.J. Date, An Introduction to Database Systems, ISBN: 0-321-19784-4.

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:

domingo, 15 de junio de 2014

Analizando junio 2014

Planteado el qué, vayamos al cómo.

Durante el curso, como ya se ha mencionado, se han realizado 13 pruebas. Son muchas, así que en alguna se puede fallar, no ir, no tener ganas de ir... En cualquier caso, la gran mayoría, alrededor del 85 % del alumnado ha hecho 11 o más de esas pruebas. Hay un poco de trampa, el examen final, solo hay uno, hay que hacerlo si se quiere aprobar. Pero más o menos: el 22 % no se presentó el miércoles 11 de junio y eso se distribuye entre todas las categoría por debajo de 13. Los números de la imagen anterior solo reflejan haberse presentado a los controles, no que se haya obtenido más o menos nota –no se aplica el concepto de aprobado-suspenso hasta el final del todo, en la suma de todas las notas.