J'ai une table InnoDB 'idtimes' (MySQL 5.0.22-log) avec des colonnes
`id` int(11) NOT NULL,
`time` int(20) NOT NULL, [...]
avec une clé unique composée
UNIQUE KEY `id_time` (`id`,`time`)
il peut donc y avoir plusieurs horodatages par identifiant et plusieurs identifiants par horodatage.
J'essaye de mettre en place une requête où j'obtiens toutes les entrées plus la prochaine fois plus grande pour chaque entrée, si elle existe, donc elle devrait retourner par exemple:
+-----+------------+------------+
| id | time | nexttime |
+-----+------------+------------+
| 155 | 1300000000 | 1311111111 |
| 155 | 1311111111 | 1322222222 |
| 155 | 1322222222 | NULL |
| 156 | 1312345678 | 1318765432 |
| 156 | 1318765432 | NULL |
+-----+------------+------------+
En ce moment je suis si loin:
SELECT l.id, l.time, r.time FROM
idtimes AS l LEFT JOIN idtimes AS r ON l.id = r.id
WHERE l.time < r.time ORDER BY l.id ASC, l.time ASC;
mais bien sûr, cela renvoie toutes les lignes avec r.time> l.time et pas seulement la première ...
Je suppose que j'aurai besoin d'une sous-sélection comme
SELECT outer.id, outer.time,
(SELECT time FROM idtimes WHERE id = outer.id AND time > outer.time
ORDER BY time ASC LIMIT 1)
FROM idtimes AS outer ORDER BY outer.id ASC, outer.time ASC;
mais je ne sais pas comment me référer à l'heure actuelle (je sais que ce qui précède n'est pas un SQL valide).
Comment puis-je faire cela avec une seule requête (et je préférerais ne pas utiliser des variables @ qui dépendent de la progression dans le tableau une ligne à la fois et de la mémorisation de la dernière valeur)?
Faire un JOIN est une chose dont vous pourriez avoir besoin.
SELECT l.id, l.time, r.time FROM
idtimes AS l LEFT JOIN idtimes AS r ON l.id = r.id
Je suppose que la jointure externe est délibérée et que vous voulez obtenir des valeurs nulles. Plus sur cela plus tard.
WHERE l.time < r.time ORDER BY l.id ASC, l.time ASC;
Vous ne voulez que le r. ligne dont le temps (MIN) le plus bas est supérieur au temps l. C'est l'endroit où vous avez besoin de sous-interroger.
WHERE r.time = (SELECT MIN(time) FROM idtimes r2 where r2.id = l.id AND r2.time > l.time)
Passons maintenant aux null. Si "il n'y a pas de temps supérieur suivant", alors SELECT MIN () sera évalué à null (ou pire), et cela lui-même ne se compare jamais à rien, donc votre clause WHERE ne sera jamais satisfaite, et le "temps le plus élevé" pour chaque ID, n'a jamais pu apparaître dans le jeu de résultats.
Vous le résolvez en éliminant votre JOIN et en déplaçant la sous-requête scalaire dans la liste SELECT:
SELECT id, time,
(SELECT MIN(time) FROM idtimes sub
WHERE sub.id = main.id AND sub.time > main.time) as nxttime
FROM idtimes AS main
J'évite toujours d'utiliser des sous-requêtes soit dans le bloc SELECT
soit dans le bloc FROM
, car cela rend le code "plus sale" et parfois moins efficace.
Je pense qu'une façon plus élégante de le faire est de:
Vous pouvez le faire avec une table JOIN
entre idtimes avec elle-même, contraignant la jointure à la même id et jusqu'à fois supérieur à fois de la ligne actuelle.
Tu devrais utiliser LEFT JOIN
pour éviter d'exclure des lignes où il n'y a pas fois supérieur à celui de la ligne actuelle.
SELECT
i1.id,
i1.time AS time,
i2.time AS greater_time
FROM
idtimes AS i1
LEFT JOIN idtimes AS i2 ON i1.id = i2.id AND i2.time > i1.time
Le problème, comme vous l'avez mentionné, est que vous avez plusieurs lignes où next_time est supérieur à time .
+-----+------------+--------------+
| id | time | greater_time |
+-----+------------+--------------+
| 155 | 1300000000 | 1311111111 |
| 155 | 1300000000 | 1322222222 |
| 155 | 1311111111 | 1322222222 |
| 155 | 1322222222 | NULL |
| 156 | 1312345678 | 1318765432 |
| 156 | 1318765432 | NULL |
+-----+------------+--------------+
La meilleure façon de filtrer toutes ces lignes inutiles est de savoir s'il y a fois entre fois (supérieur à) et plus grand temps (inférieur à) pour cet id .
SELECT
i1.id,
i1.time AS time,
i2.time AS next_time,
i3.time AS intrudor_time
FROM
idtimes AS i1
LEFT JOIN idtimes AS i2 ON i1.id = i2.id AND i2.time > i1.time
LEFT JOIN idtimes AS i3 ON i2.id = i3.id AND i3.time > i1.time AND i3.time < i2.time
ops, nous avons encore un false next_time!
+-----+------------+--------------+---------------+
| id | time | next_time | intrudor_time |
+-----+------------+--------------+---------------+
| 155 | 1300000000 | 1311111111 | NULL |
| 155 | 1300000000 | 1322222222 | 1311111111 |
| 155 | 1311111111 | 1322222222 | NULL |
| 155 | 1322222222 | NULL | NULL |
| 156 | 1312345678 | 1318765432 | NULL |
| 156 | 1318765432 | NULL | NULL |
+-----+------------+--------------+---------------+
Il suffit de filtrer les lignes où cet événement se produit, en ajoutant la contrainte WHERE
ci-dessous
WHERE
i3.time IS NULL
Voilà, nous avons ce qu'il nous faut!
+-----+------------+--------------+---------------+
| id | time | next_time | intrudor_time |
+-----+------------+--------------+---------------+
| 155 | 1300000000 | 1311111111 | NULL |
| 155 | 1311111111 | 1322222222 | NULL |
| 155 | 1322222222 | NULL | NULL |
| 156 | 1312345678 | 1318765432 | NULL |
| 156 | 1318765432 | NULL | NULL |
+-----+------------+--------------+---------------+
J'espère que vous avez toujours besoin d'une réponse après 4 ans!
Vous pouvez également obtenir ce que vous voulez à partir d'une min()
et GROUP BY
sans sélection interne:
SELECT l.id, l.time, min(r.time)
FROM idtimes l
LEFT JOIN idtimes r on (r.id = l.id and r.time > l.time)
GROUP BY l.id, l.time;
Je voudrais presque parier une grosse somme d'argent que l'optimiseur transforme cela en la même chose que la réponse d'Erwin Smout de toute façon, et il est discutable que ce soit plus clair, mais là c'est pour être complet ...
Avant de présenter la solution, je dois noter qu'elle n'est pas jolie. Ce serait beaucoup plus facile si vous aviez une colonne AUTO_INCREMENT
Sur votre table (pensez-vous?)
SELECT
l.id, l.time,
SUBSTRING_INDEX(GROUP_CONCAT(r.time ORDER BY r.time), ',', 1)
FROM
idtimes AS l
LEFT JOIN idtimes AS r ON (l.id = r.id)
WHERE
l.time < r.time
GROUP BY
l.id, l.time
Explication:
(id, time)
(Qui sont également connues pour être uniques).(l.id, l.time)
, Obtenez le premierr.time
Qui est supérieur à l.time
. Cela se produit lors de la première commande des r.time
S via GROUP_CONCAT(r.time ORDER BY r.time)
, puis en coupant le premier jeton via SUBSTRING_INDEX
.Bonne chance et ne vous attendez pas à de bonnes performances si cette table est grande.