Mysql
 sql >> Database >  >> RDS >> Mysql

Ordine delle query MySQL in base alla maggior parte dei campi completati

MySQL non ha alcuna funzione per contare il numero di campi non NULL su una riga, per quanto ne so.

Quindi l'unico modo che mi viene in mente è usare una condizione esplicita:

SELECT * FROM mytable
    ORDER BY (IF( column1 IS NULL, 0, 1)
             +IF( column2 IS NULL, 0, 1)
             ...
             +IF( column45 IS NULL, 0, 1)) DESC;

...è brutto come il peccato, ma dovrebbe fare il trucco.

Potresti anche ideare un TRIGGER per incrementare una colonna extra "fields_filled". L'attivazione ti costa su UPDATE , i 45 IF ti feriscono su SELECT; dovrai modellare ciò che è più conveniente.

Nota che l'indicizzazione di tutti i campi per velocizzare SELECT ti costerà durante l'aggiornamento (e 45 diversi indici probabilmente costano quanto una scansione di una tabella su select, per non dire che il campo indicizzato è un VARCHAR ). Esegui alcuni test, ma credo che la soluzione 45-IF sia probabilmente la migliore in assoluto.

AGGIORNAMENTO :Se puoi rielaborare la struttura della tua tabella per normalizzarla in qualche modo, puoi inserire i campi in un my_values tavolo. Quindi avresti una "tabella di intestazione" (forse con solo un ID univoco) e una "tabella dati". I campi vuoti non esisterebbero affatto e quindi potresti ordinare in base al numero di campi compilati utilizzando un RIGHT JOIN , contando i campi compilati con COUNT() . Ciò accelererebbe anche notevolmente UPDATE operazioni e ti permetterebbe di utilizzare in modo efficiente gli indici.

ESEMPIO (dall'impostazione della tabella all'impostazione di due tabelle normalizzate) :

Diciamo che abbiamo un insieme di Customer record. Avremo un breve sottoinsieme di dati "obbligatori" come ID, nome utente, password, email, ecc.; quindi avremo un sottoinsieme forse molto più ampio di dati "opzionali" come nickname, avatar, data di nascita e così via. Come primo passo, assumiamo che tutti questi dati siano varchar (questo, a prima vista, sembra una limitazione rispetto alla soluzione a tabella singola in cui ogni colonna può avere il proprio tipo di dati).

Quindi abbiamo un tavolo come,

ID   username    ....
1    jdoe        etc.
2    jqaverage   etc.
3    jkilroy     etc.

Quindi abbiamo la tabella dei dati opzionali. Qui John Doe ha compilato tutti i campi, Joe Q. In media solo due e Kilroy nessuno (anche se era qui).

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

Per riprodurre l'output della "tabella singola" in MySQL, dobbiamo creare una VIEW piuttosto complessa con un sacco di LEFT JOIN S. Questa visualizzazione sarà comunque molto veloce se abbiamo un indice basato su (userid, var) (ancora meglio se utilizziamo una costante numerica o un SET invece di un varchar per il tipo di dati di 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)
;

Ogni campo nel nostro modello logico, ad esempio "name", sarà contenuto in una tupla ( id, 'name', value ) nella tabella dati opzionale.

E produrrà una riga della forma <FIELDNAME>s.val AS <FIELDNAME> nella sezione (1) della query precedente, facendo riferimento a una riga del modulo LEFT JOIN userdata AS <FIELDNAME>s ON ( users.id = <FIELDNAME>s.id AND <FIELDNAME>s.var = '<FIELDNAME>') nella sezione (2). Quindi possiamo costruire la query in modo dinamico concatenando la prima riga di testo della query precedente con una Sezione 1 dinamica, il testo "DAgli utenti" e una Sezione 2 costruita dinamicamente.

Una volta eseguita questa operazione, i SELECT nella vista sono esattamente identici a prima, ma ora recuperano i dati da due tabelle normalizzate tramite JOIN.

EXPLAIN SELECT * FROM usertable;

ci dirà che l'aggiunta di colonne a questa configurazione non rallenta notevolmente le operazioni, ovvero che questa soluzione si adatta abbastanza bene.

Dovranno essere modificati gli INSERTI (inseriamo solo i dati obbligatori, e solo nella prima tabella) e anche gli AGGIORNAMENTI:AGGIORNIAMO la tabella dei dati obbligatori o una singola riga della tabella dei dati facoltativi. Ma se la riga di destinazione non è presente, deve essere INSERT.

Quindi dobbiamo sostituire

UPDATE usertable SET name = 'John Doe', born = 'New York' WHERE id = 1;

con un 'upsert', in questo caso

INSERT INTO userdata VALUES
        ( 1, 'name', 'John Doe' ),
        ( 1, 'born', 'New York' )
    ON DUPLICATE KEY UPDATE val = VALUES(val);

(Abbiamo bisogno di un UNIQUE INDEX on userdata(id, var) per ON DUPLICATE KEY lavorare).

A seconda delle dimensioni della riga e dei problemi del disco, questa modifica potrebbe produrre un apprezzabile miglioramento delle prestazioni.

Tieni presente che se questa modifica non viene eseguita, le query esistenti non genereranno errori:falliranno silenziosamente .

Qui ad esempio modifichiamo i nomi di due utenti; uno ha un nome registrato, l'altro ha NULL. Il primo è modificato, il secondo 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)

Per conoscere il rango di ogni riga, per quegli utenti che hanno un rango, recuperiamo semplicemente il conteggio delle righe di dati utente per ID:

SELECT id, COUNT(*) AS rank FROM userdata GROUP BY id

Ora per estrarre le righe in ordine di "stato riempito", facciamo:

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;

Il LEFT JOIN assicura che anche le persone senza rango vengano recuperate e l'ordine aggiuntivo tramite id assicura che le persone con rango identico escano sempre nello stesso ordine.