Juntando-se a uma subconsulta limitada?
-
05-07-2019 - |
Pergunta
Eu tenho esta tabela releases
em um banco de dados SQLite3, listando cada versão lançada de um aplicativo:
|release_id|release_date|app_id|
|==========|============|======|
| 1001| 2009-01-01 | 1|
| 1003| 2009-01-01 | 1|
| 1004| 2009-02-02 | 2|
| 1005| 2009-01-15 | 1|
Assim, para cada app_id, haverá várias linhas. Eu tenho outra tabela, apps
:
|app_id|name |
|======|========|
| 1|Everest |
| 2|Fuji |
Eu quero mostrar o nome da aplicação e a versão mais recente, onde os meios "mais novos" (a) mais novo release_date, e se houver duplicatas, (b) maior release_id.
Eu posso fazer isso para uma aplicação individual:
SELECT apps.name,releases.release_id,releases.release_date
FROM apps
INNER JOIN releases
ON apps.app_id = releases.app_id
WHERE releases.release_id = 1003
ORDER BY releases.release_date,releases.release_id
LIMIT 1
mas é claro que ORDER BY se aplica a toda a consulta SELECT, e se eu deixar de fora a cláusula WHERE, ele ainda retorna apenas uma linha.
É uma consulta one-shot em um pequeno banco de dados, consultas tão lento, tabelas temporárias, etc. são muito bem -. Eu simplesmente não pode obter o meu cérebro em torno da maneira SQL para fazer isso
Solução
Isso é fácil de fazer com o ROW_NUMBER função analítica (), que eu acho que sqlite3 não suporta. Mas você pode fazê-lo de uma forma que é um pouco mais flexível do que o que é dado nas respostas anteriores:
SELECT
apps.name,
releases.release_id,
releases.release_date
FROM apps INNER JOIN releases
ON apps.app_id = releases.app_id
WHERE NOT EXISTS (
-- // where there doesn't exist a more recent release for the same app
SELECT * FROM releases AS R
WHERE R.app_id = apps.app_id
AND R.release_data > releases.release_data
)
Por exemplo, se você tiver várias colunas de ordenação que definem "mais recente," MAX não iria funcionar para você, mas você pode modificar a subconsulta EXISTS para capturar o mais complicado significado de "mais recente".
Outras dicas
Este é o "maior N por grupo" problema. Vem-se várias vezes por semana em StackOverflow.
Eu costumo usar uma solução como a do @ Steve Kass' , mas fazê-lo sem subconsultas (eu entrei dos anos hábito atrás com o MySQL 4.0, que não apoiaram subconsultas):
SELECT a.name, r1.release_id, r1.release_date
FROM apps a
INNER JOIN releases r1
LEFT OUTER JOIN releases r2 ON (r1.app_id = r2.app_id
AND (r1.release_date < r2.release_date
OR r1.release_date = r2.release_date AND r1.release_id < r2.release_id))
WHERE r2.release_id IS NULL;
Internamente, isso provavelmente otimiza idêntica à sintaxe NOT EXISTS
. Você pode analisar a consulta com EXPLAIN
para certificar-se.
Re seu comentário, você poderia simplesmente ignorar o teste para release_date
porque release_id
é tão útil para estabelecer a ordem cronológica de lançamentos, e eu suponho que está garantido para ser original, assim que isso simplifica a consulta:
SELECT a.name, r1.release_id, r1.release_date
FROM apps a
INNER JOIN releases r1
LEFT OUTER JOIN releases r2 ON (r1.app_id = r2.app_id
AND r1.release_id < r2.release_id)
WHERE r2.release_id IS NULL;
É feio, mas eu acho que ele vai trabalhar
select apps.name, (select releases.release_id from releases where releases.app_id=apps.app_id order by releases.release_date, releases.release_id), (select releases.release_date from releases where releases.app_id=apps.app_id order by releases.release_date, releases.release_id) from apps order by apps.app_id
Espero que haja alguma maneira de obter essas duas colunas em um incorporado select, mas eu não conhecê-lo.
Tente:
SELECT a.name,
t.max_release_id,
t.max_date
FROM APPS a
JOIN (SELECT t.app_id,
MAX(t.release_id) 'max_release_id',
t.max_date
FROM (SELECT r.app_id,
r.release_id,
MAX(r.release_date) 'max_date'
FROM RELEASES r
GROUP BY r.app_id, r.release_id)
GROUP BY t.app_id, t.max_date) t
Err segunda tentativa. Assumindo que IDs são monótona crescente e estouro não é uma ocorrência provável, você pode ignorar a data e apenas fazer:
SELECT apps.name, releases.release_id, releases.release_date
FROM apps INNER JOIN releases on apps.app_id = releases.app_id
WHERE releases.release_id IN
(SELECT Max(release_id) FROM releases
GROUP BY app_id);