Poiché non hai fornito lo schema per i results
, suppongo che sia questo o molto simile (forse colonne extra):
create table results (
id int primary key,
user int,
foreign key (user) references <some_other_table>(id),
keyword varchar(<30>)
);
Passaggio 1: aggrega per keyword/user
come nella tua query di esempio, ma per tutte le parole chiave:
create view user_keyword as (
select
keyword,
user,
count(*) as magnitude
from results
group by keyword, user
);
Passaggio 2: classifica ogni utente all'interno di ogni gruppo di parole chiave (nota l'uso della sottoquery per classificare le righe):
create view keyword_user_ranked as (
select
keyword,
user,
magnitude,
(select count(*)
from user_keyword
where l.keyword = keyword and magnitude >= l.magnitude
) as rank
from
user_keyword l
);
Passaggio 3: seleziona solo le righe in cui il rango è inferiore a un numero:
select *
from keyword_user_ranked
where rank <= 3;
Esempio:
Dati di base utilizzati:
mysql> select * from results;
+----+------+---------+
| id | user | keyword |
+----+------+---------+
| 1 | 1 | mysql |
| 2 | 1 | mysql |
| 3 | 2 | mysql |
| 4 | 1 | query |
| 5 | 2 | query |
| 6 | 2 | query |
| 7 | 2 | query |
| 8 | 1 | table |
| 9 | 2 | table |
| 10 | 1 | table |
| 11 | 3 | table |
| 12 | 3 | mysql |
| 13 | 3 | query |
| 14 | 2 | mysql |
| 15 | 1 | mysql |
| 16 | 1 | mysql |
| 17 | 3 | query |
| 18 | 4 | mysql |
| 19 | 4 | mysql |
| 20 | 5 | mysql |
+----+------+---------+
Raggruppati per parola chiave e utente:
mysql> select * from user_keyword order by keyword, magnitude desc;
+---------+------+-----------+
| keyword | user | magnitude |
+---------+------+-----------+
| mysql | 1 | 4 |
| mysql | 2 | 2 |
| mysql | 4 | 2 |
| mysql | 3 | 1 |
| mysql | 5 | 1 |
| query | 2 | 3 |
| query | 3 | 2 |
| query | 1 | 1 |
| table | 1 | 2 |
| table | 2 | 1 |
| table | 3 | 1 |
+---------+------+-----------+
Utenti classificati all'interno di parole chiave:
mysql> select * from keyword_user_ranked order by keyword, rank asc;
+---------+------+-----------+------+
| keyword | user | magnitude | rank |
+---------+------+-----------+------+
| mysql | 1 | 4 | 1 |
| mysql | 2 | 2 | 3 |
| mysql | 4 | 2 | 3 |
| mysql | 3 | 1 | 5 |
| mysql | 5 | 1 | 5 |
| query | 2 | 3 | 1 |
| query | 3 | 2 | 2 |
| query | 1 | 1 | 3 |
| table | 1 | 2 | 1 |
| table | 3 | 1 | 3 |
| table | 2 | 1 | 3 |
+---------+------+-----------+------+
Solo i primi 2 di ogni parola chiave:
mysql> select * from keyword_user_ranked where rank <= 2 order by keyword, rank asc;
+---------+------+-----------+------+
| keyword | user | magnitude | rank |
+---------+------+-----------+------+
| mysql | 1 | 4 | 1 |
| query | 2 | 3 | 1 |
| query | 3 | 2 | 2 |
| table | 1 | 2 | 1 |
+---------+------+-----------+------+
Nota che quando ci sono pareggi -- vedi gli utenti 2 e 4 per la parola chiave "mysql" negli esempi -- tutte le parti in parità ottengono il rango "ultimo", cioè se il 2° e il 3° sono in parità, a entrambi viene assegnato il rango 3.
Rendimento:l'aggiunta di un indice alla parola chiave e alle colonne utente aiuterà. Ho una tabella sottoposta a query in modo simile con 4000 e 1300 valori distinti per le due colonne (in una tabella di 600000 righe). Puoi aggiungere l'indice in questo modo:
alter table results add index keyword_user (keyword, user);
Nel mio caso, il tempo di query è sceso da circa 6 secondi a circa 2 secondi.