MySQL no tiene ninguna función para contar la cantidad de campos no NULL en una fila, que yo sepa.
Entonces, la única forma que se me ocurre es usar una condición explícita:
SELECT * FROM mytable
ORDER BY (IF( column1 IS NULL, 0, 1)
+IF( column2 IS NULL, 0, 1)
...
+IF( column45 IS NULL, 0, 1)) DESC;
... es feo como el pecado, pero debería funcionar.
También puede diseñar un DISPARADOR para incrementar una columna adicional "campos_llenados". El activador te cuesta en UPDATE
, los 45 IF te hacen daño en SELECT
; tendrás que modelar lo que sea más conveniente.
Tenga en cuenta que indexar todos los campos para acelerar SELECT
le costará al actualizar (y 45 índices diferentes probablemente cuesten tanto como un escaneo de tabla en la selección, sin decir que el campo indexado es un VARCHAR
). Realice algunas pruebas, pero creo que la solución 45-IF probablemente sea la mejor en general.
ACTUALIZAR :Si puede modificar la estructura de su tabla para normalizarla un poco, puede poner los campos en un my_values
mesa. Entonces tendría una "tabla de encabezado" (tal vez con solo una identificación única) y una "tabla de datos". Los campos vacíos no existirían en absoluto, y luego podría ordenar por cuántos campos llenos hay usando RIGHT JOIN
, contando los campos llenos con COUNT()
. Esto también aceleraría enormemente UPDATE
operaciones, y le permitiría emplear índices de manera eficiente.
EJEMPLO (desde la configuración de la mesa hasta la configuración de dos mesas normalizadas) :
Digamos que tenemos un conjunto de Customer
registros. Tendremos un pequeño subconjunto de datos "obligatorios" como ID, nombre de usuario, contraseña, correo electrónico, etc.; entonces tendremos un subconjunto quizás mucho más grande de datos "opcionales" como apodo, avatar, fecha de nacimiento, etc. Como primer paso, supongamos que todos estos datos son varchar
(esto, a primera vista, parece una limitación en comparación con la solución de tabla única donde cada columna puede tener su propio tipo de datos).
Entonces tenemos una tabla como,
ID username ....
1 jdoe etc.
2 jqaverage etc.
3 jkilroy etc.
Luego tenemos la tabla de datos opcionales. Aquí John Doe ha llenado todos los campos, Joe Q. Promedio solo dos y Kilroy ninguno (incluso si él era aquí).
userid var val
1 name John
1 born Stratford-upon-Avon
1 when 11-07-1974
2 name Joe Quentin
2 when 09-04-1962
Para reproducir la salida de "tabla única" en MySQL, tenemos que crear un VIEW
bastante complejo con mucho LEFT JOIN
s. No obstante, esta vista será muy rápida si tenemos un índice basado en (userid, var)
(incluso mejor si usamos una constante numérica o un SET en lugar de un varchar para el tipo de datos de var
:
CREATE OR REPLACE VIEW usertable AS SELECT users.*,
names.val AS name // (1)
FROM users
LEFT JOIN userdata AS names ON ( users.id = names.id AND names.var = 'name') // (2)
;
Cada campo en nuestro modelo lógico, por ejemplo, "nombre", estará contenido en una tupla (id, 'nombre', valor) en la tabla de datos opcional.
Y producirá una línea de la forma <FIELDNAME>s.val AS <FIELDNAME>
en la sección (1) de la consulta anterior, haciendo referencia a una línea del formulario LEFT JOIN userdata AS <FIELDNAME>s ON ( users.id = <FIELDNAME>s.id AND <FIELDNAME>s.var = '<FIELDNAME>')
en la sección (2). Entonces podemos construir la consulta dinámicamente concatenando la primera línea de texto de la consulta anterior con una Sección 1 dinámica, el texto 'DE los usuarios' y una Sección 2 construida dinámicamente.
Una vez que hacemos esto, los SELECT en la vista son exactamente idénticos a los anteriores, pero ahora obtienen datos de dos tablas normalizadas a través de JOIN.
EXPLAIN SELECT * FROM usertable;
nos dirá que agregar columnas a esta configuración no ralentiza considerablemente las operaciones, es decir, esta solución escala razonablemente bien.
Habrá que modificar INSERTAR (solo insertamos datos obligatorios, y solo en la primera tabla) y ACTUALIZAR también:o ACTUALIZAMOS la tabla de datos obligatorios, o una sola fila de la tabla de datos opcionales. Pero si la fila de destino no está allí, debe insertarse.
Así que tenemos que reemplazar
UPDATE usertable SET name = 'John Doe', born = 'New York' WHERE id = 1;
con un 'upsert', en este caso
INSERT INTO userdata VALUES
( 1, 'name', 'John Doe' ),
( 1, 'born', 'New York' )
ON DUPLICATE KEY UPDATE val = VALUES(val);
(Necesitamos un UNIQUE INDEX on userdata(id, var)
para ON DUPLICATE KEY
para trabajar).
Según el tamaño de la fila y los problemas del disco, este cambio podría generar una mejora apreciable del rendimiento.
Tenga en cuenta que si no se realiza esta modificación, las consultas existentes no arrojarán errores; fallarán silenciosamente .
Aquí por ejemplo modificamos los nombres de dos usuarios; uno tiene un nombre registrado, el otro tiene NULL. El primero está modificado, el segundo no.
mysql> SELECT * FROM usertable;
+------+-----------+-------------+------+------+
| id | username | name | born | age |
+------+-----------+-------------+------+------+
| 1 | jdoe | John Doe | NULL | NULL |
| 2 | jqaverage | NULL | NULL | NULL |
| 3 | jtkilroy | NULL | NULL | NULL |
+------+-----------+-------------+------+------+
3 rows in set (0.00 sec)
mysql> UPDATE usertable SET name = 'John Doe II' WHERE username = 'jdoe';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> UPDATE usertable SET name = 'James T. Kilroy' WHERE username = 'jtkilroy';
Query OK, 0 rows affected (0.00 sec)
Rows matched: 0 Changed: 0 Warnings: 0
mysql> select * from usertable;
+------+-----------+-------------+------+------+
| id | username | name | born | age |
+------+-----------+-------------+------+------+
| 1 | jdoe | John Doe II | NULL | NULL |
| 2 | jqaverage | NULL | NULL | NULL |
| 3 | jtkilroy | NULL | NULL | NULL |
+------+-----------+-------------+------+------+
3 rows in set (0.00 sec)
Para conocer el rango de cada fila, para aquellos usuarios que sí tienen un rango, simplemente recuperamos el recuento de filas de datos de usuario por ID:
SELECT id, COUNT(*) AS rank FROM userdata GROUP BY id
Ahora, para extraer filas en orden de "estado completo", hacemos:
SELECT usertable.* FROM usertable
LEFT JOIN ( SELECT id, COUNT(*) AS rank FROM userdata GROUP BY id ) AS ranking
ON (usertable.id = ranking.id)
ORDER BY rank DESC, id;
El LEFT JOIN
asegura que las personas sin rango también se recuperen, y el pedido adicional por id
asegura que las personas con el mismo rango siempre salgan en el mismo orden.