sql >> Databasteknik >  >> RDS >> Mysql

MySQL-frågeordning efter de flesta ifyllda fälten

MySQL har ingen funktion för att räkna antalet icke-NULL-fält på en rad, så vitt jag vet.

Så det enda sättet jag kan komma på är att använda ett explicit villkor:

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

...den är ful som synd, men borde göra susen.

Du kan också skapa en TRIGGER för att öka en extra kolumn "fields_filled". Utlösaren kostar dig UPDATE , de 45 IF:erna skadar dig på SELECT; du måste modellera vad som är bekvämare.

Observera att indexering av alla fält för att påskynda SELECT kommer att kosta dig när du uppdaterar (och 45 olika index kostar förmodligen lika mycket som en tabellskanning på select, för att inte säga att det indexerade fältet är en VARCHAR ). Kör några tester, men jag tror att 45-IF-lösningen sannolikt är den bästa totalt sett.

UPPDATERA :Om du kan omarbeta din tabellstruktur för att normalisera den något, du kan lägga fälten i en my_values tabell. Då skulle du ha en "huvudtabell" (kanske med bara ett unikt ID) och en "datatabell". Tomma fält skulle inte existera alls, och sedan kan du sortera efter hur många ifyllda fält som finns genom att använda en RIGHT JOIN , räknar de ifyllda fälten med COUNT() . Detta skulle också avsevärt påskynda UPDATE operationer och skulle tillåta dig att effektivt använda index.

EXEMPEL (från tabellinställningar till två normaliserade tabeller) :

Låt oss säga att vi har en uppsättning Customer uppgifter. Vi kommer att ha en kort delmängd av "obligatoriska" data såsom ID, användarnamn, lösenord, e-post, etc.; då kommer vi att ha en kanske mycket större delmängd av "valfri" data som smeknamn, avatar, födelsedatum och så vidare. Låt oss som ett första steg anta att alla dessa data är varchar (det här ser vid första anblicken ut som en begränsning jämfört med lösningen med en enda tabell där varje kolumn kan ha sin egen datatyp).

Så vi har en tabell som,

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

Sedan har vi den valfria datatabellen. Här har John Doe fyllt i alla fält, Joe Q. I genomsnitt bara två och Kilroy ingen (även om han var här).

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

För att kunna reproducera "single table"-utgången i MySQL måste vi skapa en ganska komplex VIEW med massor av LEFT JOIN s. Denna vy kommer ändå att vara väldigt snabb om vi har ett index baserat på (userid, var) (Ännu bättre om vi använder en numerisk konstant eller en SET istället för en varchar för datatypen 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)
;

Varje fält i vår logiska modell, t.ex. "namn", kommer att finnas i en tupel ( id, 'namn', värde ) i den valfria datatabellen.

Och det kommer att ge en rad med formen <FIELDNAME>s.val AS <FIELDNAME> i avsnittet (1) av ovanstående fråga, med hänvisning till en rad i formen LEFT JOIN userdata AS <FIELDNAME>s ON ( users.id = <FIELDNAME>s.id AND <FIELDNAME>s.var = '<FIELDNAME>') i 2 §. Så vi kan konstruera frågan dynamiskt genom att sammanfoga den första textraden i ovanstående fråga med ett dynamiskt avsnitt 1, texten 'FRÅN användare' och ett dynamiskt byggt avsnitt 2.

När vi väl gör detta är SELECTs i vyn exakt identiska med tidigare -- men nu hämtar de data från två normaliserade tabeller via JOINs.

EXPLAIN SELECT * FROM usertable;

kommer att berätta för oss att att lägga till kolumner i den här inställningen inte saktar ner verksamheten nämnvärt, dvs. den här lösningen skalar ganska bra.

INSERT kommer att behöva modifieras (vi infogar bara obligatoriska data, och bara i den första tabellen) och UPPDATERINGAR likaså:vi UPPDATERAR antingen den obligatoriska datatabellen eller en enda rad i den valfria datatabellen. Men om målraden inte finns där, måste den infogas.

Så vi måste byta ut

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

med en "upsert", i det här fallet

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

(Vi behöver ett UNIQUE INDEX on userdata(id, var) för ON DUPLICATE KEY att arbeta).

Beroende på radstorlek och diskproblem kan denna förändring ge en avsevärd prestandavinst.

Observera att om den här ändringen inte utförs kommer de befintliga frågorna inte att ge fel - de kommer att misslyckas i tysthet .

Här ändrar vi till exempel namnen på två användare; den ena har ett namn registrerat, den andra har NULL. Den första är modifierad, den andra inte.

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)

För att veta rankningen för varje rad, för de användare som har en rankning, hämtar vi helt enkelt antalet användardatarader per id:

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

Nu för att extrahera rader i "fylld status"-ordning gör vi:

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;

LEFT JOIN säkerställer att ranglösa individer också hämtas, och den ytterligare ordningen efter id ser till att personer med identisk rang alltid kommer ut i samma ordning.




  1. Närmaste match, del 1

  2. Infoga dataram i postgresql sqlalchemy med idx autoincrement

  3. Långsam fråga:hitta skillnaden mellan värden baserat på min och max i en annan kolumn för varje grupp

  4. Varför kan jag inte infoga i MySQL?