web-dev-qa-db-fra.com

Comment joindre la ligne la plus récente d'une table à une autre table?

J'ai des données qui ressemblent à ceci:

entities
id         name
1          Apple
2          Orange
3          Banana

Périodiquement, un processus se déroulera et attribuera un score à chaque entité. Le processus génère les données et les ajoute à une table de scores comme suit:

scores 
id  entity_id    score   date_added
1    1            10       1/2/09
2    2            10       1/2/09
3    1            15       1/3/09
4    2            10       1/03/09
5    1            15       1/4/09
6    2            15       1/4/09
7    3            22       1/4/09

Je veux pouvoir sélectionner toutes les entités ainsi que le score enregistré le plus récent pour chacune, ce qui donne des données comme celle-ci:

entities
id name     score  date_added
1  Apple     15     1/4/09
2  Orange    15     1/4/09
3  Banana    15     1/4/09

Je peux obtenir les données d'une seule entité en utilisant cette requête:

SELECT entities.*, 
       scores.score, 
       scores.date_added 
FROM entities

INNER  JOIN scores
ON entities.id = scores.entity_id

WHERE entities.id = ?

ORDER BY scores.date_added DESC
LIMIT 1

Mais je ne sais pas comment sélectionner la même chose pour toutes les entités. Peut-être que ça me regarde en face?

Merci de bien vouloir prendre le temps.

Merci pour les bonnes réponses. Je vais lui donner quelques jours pour voir si une solution préférée bouillonne, puis je sélectionnerai la réponse.

MISE À JOUR: J'ai essayé plusieurs des solutions proposées, le principal problème auquel je suis confronté maintenant est que si une entité n'a pas encore de score généré, elles n'apparaissent pas dans la liste.

À quoi ressemblerait le SQL pour garantir que toutes les entités sont retournées, même si aucun score n'a encore été publié?

MISE À JOUR: réponse sélectionnée. Merci tout le monde!

50
GloryFish

Je le fais de cette façon:

SELECT e.*, s1.score, s1.date_added 
FROM entities e
  INNER JOIN scores s1
    ON (e.id = s1.entity_id)
  LEFT OUTER JOIN scores s2
    ON (e.id = s2.entity_id AND s1.id < s2.id)
WHERE s2.id IS NULL;
63
Bill Karwin

Juste pour ajouter ma variation à ce sujet:

SELECT e.*, s1.score
FROM entities e
INNER JOIN score s1 ON e.id = s1.entity_id
WHERE NOT EXISTS (
    SELECT 1 FROM score s2 WHERE s2.id > s1.id
)
9
Ray Hidayat

approche 1

SELECT entities.*, 
       scores.score, 
       scores.date_added 
FROM entities

INNER  JOIN scores
ON entities.id = scores.entity_id

WHERE scores.date_added = 
  (SELECT max(date_added) FROM scores where entity_id = entities.id)
5
Michael Buen

approche 2

coût de la requête par rapport au lot:


SELECT entities.*, 
       scores.score, 
       scores.date_added 
FROM entities

INNER  JOIN scores
ON entities.id = scores.entity_id

inner join 
    (
    SELECT 
           entity_id, max(date_added) as recent_date
    FROM scores
    group by entity_id
    ) as y on entities.id = y.entity_id and scores.date_added = y.recent_date
3
Michael Buen

Je sais que c'est une vieille question, je pensais simplement ajouter une approche que personne n'a encore mentionnée, Cross Apply ou Outer Apply. Ceux-ci sont disponibles dans SQL Server 2005 (le type de base de données n'est pas balisé dans cette question) Ou supérieur

Utilisation des tables temporaires

DECLARE @Entities TABLE(Id INT PRIMARY KEY, name NVARCHAR(MAX))
INSERT INTO @Entities
VALUES (1, 'Apple'), (2, 'Orange'), (3, 'Banana'), (4, 'Cherry')

DECLARE @Scores TABLE(Id INT PRIMARY KEY, Entity_Id INT, Score INT, Date_Added DATE)
INSERT INTO @Scores
VALUES (1,1,10,'2009-02-01'),
(2,2,10,'2009-02-01'),
(3,1,15,'2009-02-01'),
(4,2,10,'2009-03-01'),
(5,1,15,'2009-04-01'),
(6,2,15,'2009-04-01'),
(7,3,22,'2009-04-01')

Vous pourriez utiliser

SELECT E.Id, E.name, S.Score, S.Date_Added 
FROM @Entities E
CROSS APPLY
(
    SELECT TOP 1 * 
    FROM @Scores Sc 
    WHERE Sc.Entity_Id = E.Id  
    ORDER BY sc.Score DESC
) AS S

pour obtenir les résultats souhaités. L'équivilent à autoriser des entités sans scores serait

SELECT E.Id, E.name, S.Score, S.Date_Added 
FROM @Entities E
OUTER APPLY
(
    SELECT TOP 1 * 
    FROM @Scores Sc 
    WHERE Sc.Entity_Id = E.Id  
    ORDER BY sc.Score DESC
) AS S
3
Manatherin
SELECT entities.*, 
       scores.score, 
       scores.date_added 
FROM entities

INNER  JOIN scores
ON entities.id = scores.entity_id

WHERE entities.id in 
(select id from scores s2 where date_added = max(date_added) and s2.id = entities.id)

ORDER BY scores.date_added DESC
LIMIT 1
1
Otávio Décio

Vous pouvez également le faire aujourd'hui dans la plupart des SGBDR (Oracle, PostgreSQL, SQL Server) avec une requête naturelle en utilisant des fonctions de fenêtre telles que ROW_NUMBER:

SELECT id, name, score, date_added FROM (
 SELECT e.id, e.name, s.score, s.date_added,
 ROW_NUMBER() OVER (PARTITION BY e.id ORDER BY s.date_added DESC) rn
 FROM Entities e INNER JOIN Scores s ON e.id = s.entity_id
) tmp WHERE rn = 1;

SQL Fiddle

1
Cristi S.