sql >> Base de Datos >  >> RDS >> Mysql

Orden de consulta de MySQL por campos más completados

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.