Eu tenho uma tabela InnoDB 'idtimes' (MySQL 5.0.22-log) com colunas
`id` int(11) NOT NULL,
`time` int(20) NOT NULL, [...]
com uma chave única composta
UNIQUE KEY `id_time` (`id`,`time`)
portanto, pode haver vários carimbos de data/hora por id e vários ids por carimbo de data/hora.
Estou tentando configurar uma consulta onde recebo todas as entradas mais o próximo tempo maior para cada entrada, se existir, portanto, deve retornar, por exemplo:
+-----+------------+------------+
| id | time | nexttime |
+-----+------------+------------+
| 155 | 1300000000 | 1311111111 |
| 155 | 1311111111 | 1322222222 |
| 155 | 1322222222 | NULL |
| 156 | 1312345678 | 1318765432 |
| 156 | 1318765432 | NULL |
+-----+------------+------------+
No momento estou até agora:
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;
mas é claro que isso retorna todas as linhas com r.time > l.time e não apenas a primeira ...
Acho que vou precisar de uma subseleção como
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;
mas não sei como me referir à hora atual (sei que o SQL acima não é válido).
Como faço isso com uma única consulta (e prefiro não usar @variables que dependem de percorrer a tabela uma linha por vez e lembrar o último valor)?
Fazer um JOIN é uma coisa que você pode precisar.
Suponho que a junção externa seja deliberada e você queira obter nulos. Mais sobre isso mais tarde.
Você só quer o r. linha que tem o menor tempo (MIN) que é maior que o l.time. Esse é o lugar onde você precisa de subconsultas.
Agora aos nulos. Se "não houver tempo próximo mais alto", o SELECT MIN() será avaliado como nulo (ou pior), e isso nunca será igual a nada, então sua cláusula WHERE nunca será satisfeita e o "tempo mais alto" para cada ID, nunca poderia aparecer no conjunto de resultados.
Você resolve eliminando seu JOIN e movendo a subconsulta escalar para a lista SELECT:
Sempre evito usar subconsultas seja em
SELECT
bloco ou emFROM
bloco, pois isso torna o código "mais sujo" e às vezes menos eficiente.Eu acho que uma maneira mais elegante de fazer isso é:
1. Encontre os tempos maiores que o tempo da linha
Você pode fazer isso com uma tabela
JOIN
entre idtimes consigo mesma, restringindo a junção ao mesmo id e a tempos maiores que o tempo da linha atual.Você deve usar
LEFT JOIN
para evitar a exclusão de linhas onde não há vezes maior que o da linha atual.O problema, como você mencionou, é que você tem várias linhas em que next_time é maior que time .
2. Encontre as linhas em que maior_tempo não é apenas maior, mas próximo_tempo
A melhor maneira de filtrar todas essas linhas inúteis é descobrir se há tempos entre time (maior que) e maior_time (menor que) para esse id .
ops, ainda temos um false next_time !
Basta filtrar as linhas onde este evento acontece, adicionando a
WHERE
restrição abaixoVoilà, temos o que precisamos!
Espero que você ainda precise de uma resposta depois de 4 anos!
Antes de apresentar a solução, devo observar que não é bonita. Seria muito mais fácil se você tivesse alguma
AUTO_INCREMENT
coluna na sua mesa (você tem?)Explicação:
(id, time)
combinações (que também são conhecidas por serem únicas).(l.id, l.time)
, obtenha o primeiror.time
que for maior quel.time
. Isso acontece com a primeira ordenação dor.time
s viaGROUP_CONCAT(r.time ORDER BY r.time)
, o fatiamento do primeiro token viaSUBSTRING_INDEX
.Boa sorte e não espere um bom desempenho se esta tabela for grande.
Você também pode obter o que deseja de um
min()
eGROUP BY
sem seleção interna:Eu quase apostaria uma grande quantia de dinheiro que o otimizador transforma isso na mesma coisa que a resposta de Erwin Smout de qualquer maneira, e é discutível se é mais claro, mas aí está a completude ...