J'ai une table qui a 3 champs, je veux classer la colonne sur user_id et game_id.
Voici SQL Fiddle: http://sqlfiddle.com/#!9/883e9d/1
la table déjà j'ai:
user_id | game_id | game_detial_sum |
--------|---------|--------------------|
6 | 10 | 1000 |
6 | 11 | 260 |
7 | 10 | 1200 |
7 | 11 | 500 |
7 | 12 | 360 |
7 | 13 | 50 |
production attendue :
user_id | game_id | game_detial_sum | user_game_rank |
--------|---------|--------------------|------------------|
6 | 10 | 1000 | 1 |
6 | 11 | 260 | 2 |
7 | 10 | 1200 | 1 |
7 | 11 | 500 | 2 |
7 | 12 | 360 | 3 |
7 | 13 | 50 | 4 |
Mes efforts jusqu'à présent:
SET @s := 0;
SELECT user_id,game_id,game_detail,
CASE WHEN user_id = user_id THEN (@s:=@s+1)
ELSE @s = 0
END As user_game_rank
FROM game_logs
Edit: (De OP Commentaires ): la commande est basée sur l'ordre décroissant de game_detail
ordre de game_detail
Dans un Derived Table (sous-requête de la clause FROM
), nous ordonnons nos données de sorte que toutes les lignes ayant les mêmes valeurs user_id
se rejoignent, avec un tri supplémentaire basé sur game_detail
dans l'ordre décroissant.
Nous utilisons maintenant cet ensemble de résultats et utilisons des expressions conditionnelles CASE..WHEN
pour évaluer la numérotation des lignes. Ce sera comme une technique de Looping (que nous utilisons dans le code d'application, par exemple: PHP). Nous stockons les valeurs de la ligne précédente dans les variables définies par l'utilisateur, puis nous vérifions la (les) valeur (s) de la ligne en cours par rapport à la ligne précédente. Finalement, nous attribuerons le numéro de ligne en conséquence.
Edit: Based on MySQL docs et l'observation de @Gordon Linoff:
L'ordre d'évaluation des expressions impliquant des variables utilisateur est indéfini. Par exemple, rien ne garantit que SELECT @a, @a: = @ a + 1 évalue d'abord @a, puis effectue l'assignation.
Nous devrons évaluer le numéro de ligne et attribuer la valeur user_id
à la variable @u
dans la même expression.
SET @r := 0, @u := 0;
SELECT
@r := CASE WHEN @u = dt.user_id
THEN @r + 1
WHEN @u := dt.user_id /* Notice := instead of = */
THEN 1
END AS user_game_rank,
dt.user_id,
dt.game_detail,
dt.game_id
FROM
( SELECT user_id, game_id, game_detail
FROM game_logs
ORDER BY user_id, game_detail DESC
) AS dt
Résultat
| user_game_rank | user_id | game_detail | game_id |
| -------------- | ------- | ----------- | ------- |
| 1 | 6 | 260 | 11 |
| 2 | 6 | 100 | 10 |
| 1 | 7 | 1200 | 10 |
| 2 | 7 | 500 | 11 |
| 3 | 7 | 260 | 12 |
| 4 | 7 | 50 | 13 |
Vue sur la base de données Fiddle
Une note intéressante de MySQL Docs , que j'ai découverte récemment:
Les versions précédentes de MySQL permettaient d’attribuer une valeur à un variable utilisateur dans les instructions autres que SET. Cette fonctionnalité est supporté dans MySQL 8.0 pour la compatibilité ascendante mais est soumis à suppression dans une future version de MySQL.
De plus, grâce à un collègue SO membre, l'équipe de MySQL est tombée sur ce blog: https://mysqlserverteam.com/row-numbering-ranking-ranking-ranking-how-to-use-less-user-variables-in -mysql-queries/
L’observation générale est que l’utilisation de ORDER BY
avec l’évaluation des variables utilisateur dans le même bloc de requête ne garantit pas que les valeurs seront toujours correctes. Comme, l'optimiseur MySQL peut se met en place et modifie notre présumé ordre d'évaluation.
La meilleure approche à ce problème serait de passer à MySQL 8+ et d’utiliser la fonctionnalité Row_Number()
:
Schema (MySQL v8.0)
SELECT user_id,
game_id,
game_detail,
ROW_NUMBER() OVER (PARTITION BY user_id
ORDER BY game_detail DESC) AS user_game_rank
FROM game_logs
ORDER BY user_id, user_game_rank;
Résultat
| user_id | game_id | game_detail | user_game_rank |
| ------- | ------- | ----------- | -------------- |
| 6 | 11 | 260 | 1 |
| 6 | 10 | 100 | 2 |
| 7 | 10 | 1200 | 1 |
| 7 | 11 | 500 | 2 |
| 7 | 12 | 260 | 3 |
| 7 | 13 | 50 | 4 |
La meilleure solution dans MySQL, antérieure à la version 8.0, est la suivante:
select gl.*,
(@rn := if(@lastUserId = user_id, @rn + 1,
if(@lastUserId := user_id, 1, 1)
)
) as user_game_rank
from (select gl.*
from game_logs gl
order by gl.user_id, gl.game_detail desc
) gl cross join
(select @rn := 0, @lastUserId := 0) params;
La commande est effectuée dans une sous-requête. Ceci est requis à partir de MySQL 5.7. Les affectations de variables sont toutes dans une même expression. Par conséquent, l'ordre d'évaluation des expressions n'a pas d'importance (MySQL ne garantit pas l'ordre d'évaluation des expressions).
SELECT user_id, game_id, game_detail,
CASE WHEN user_id = @lastUserId
THEN @rank := @rank + 1
ELSE @rank := 1
END As user_game_rank,
@lastUserId := user_id
FROM game_logs
cross join (select @rank := 0, @lastUserId := 0) r
order by user_id, game_detail desc
Vous pouvez utiliser une sous-requête corrélée très simple:
SELECT *, (
SELECT COUNT(DISTINCT game_detail) + 1
FROM game_logs AS x
WHERE user_id = t.user_id AND game_detail > t.game_detail
) AS user_game_rank
FROM game_logs AS t
ORDER BY user_id, user_game_rank
Il est plus lent mais beaucoup plus fiable que les variables utilisateur. Tout ce qu'il faut, c'est un JOIN pour les casser.