2009-12-07 7 views
8

Voici un exemple simplifié de ce dont je parle:tables Joining en fonction de la valeur maximale

Table: students  exam_results 
_____________  ____________________________________ 
| id | name |  | id | student_id | score | date | 
|----+------|  |----+------------+-------+--------| 
| 1 | Jim |  | 1 |   1 | 73 | 8/1/09 | 
| 2 | Joe |  | 2 |   1 | 67 | 9/2/09 | 
| 3 | Jay |  | 3 |   1 | 93 | 1/3/09 | 
|____|______|  | 4 |   2 | 27 | 4/9/09 | 
        | 5 |   2 | 17 | 8/9/09 | 
        | 6 |   3 | 100 | 1/6/09 | 
        |____|____________|_______|________| 

Supposons que, pour le bien de cette question, que chaque élève a au moins un résultat d'examen enregistré.

Comment choisiriez-vous chaque élève avec son meilleur score?Modifier: ... ET les autres champs de cet enregistrement?

Résultats escomptés:

_________________________ 
| name | score | date | 
|------+-------|--------| 
| Jim | 93 | 1/3/09 | 
| Joe | 27 | 4/9/09 | 
| Jay | 100 | 1/6/09 | 
|______|_______|________| 

Réponses à l'aide de tous les types de SGBD sont les bienvenus.

+2

Comment voulez-vous résoudre les liens?Dans votre exemple, quel record devrait être sélectionné dans Jim a marqué 93 deux fois? – Sparky

+1

dans mes propres tables, (qui n'ont rien à voir avec les étudiants et les examens) cela n'arrive pas. L'un ou l'autre devrait aller bien? – nickf

Répondre

10

Répondant à la question ÉDITÉ (à savoir pour obtenir des colonnes associées ainsi).

Dans Sql Server 2005+, la meilleure approche serait d'utiliser un ranking/window function en conjonction avec un CTE, comme ceci:

with exam_data as 
(
    select r.student_id, r.score, r.date, 
      row_number() over(partition by r.student_id order by r.score desc) as rn 
    from exam_results r 
) 
select s.name, d.score, d.date, d.student_id 
from students s 
join exam_data d 
on  s.id = d.student_id 
where d.rn = 1; 

Pour une solution conforme à la norme ANSI-SQL, une sous-requête et l'auto-join travail, comme ceci:

select s.name, r.student_id, r.score, r.date 
from (
      select r.student_id, max(r.score) as max_score 
      from exam_results r 
      group by r.student_id 
     ) d 
join exam_results r 
on  r.student_id = d.student_id 
and  r.score = d.max_score 
join students s 
on  s.id = r.student_id; 

ce dernier suppose qu'il n'y a pas double emploi avec des combinaisons student_id/max_score, s'il y a et/ou si vous voulez planifier les dé-dupliquer, vous devrez utiliser un autre sous-requête joindre à quelque chose de déterministe décider quel disque tirer. Par exemple, en supposant que vous ne pouvez pas avoir plusieurs enregistrements pour un étudiant donné à la même date, si vous vouliez briser une égalité basée sur la plus récente max_score, vous feriez quelque chose comme ce qui suit:

select s.name, r3.student_id, r3.score, r3.date, r3.other_column_a, ... 
from (
      select r2.student_id, r2.score as max_score, max(r2.date) as max_score_max_date 
      from (
         select r1.student_id, max(r1.score) as max_score 
         from exam_results r1 
         group by r1.student_id 
        ) d 
      join exam_results r2 
      on  r2.student_id = d.student_id 
      and  r2.score = d.max_score 
      group by r2.student_id, r2.score 
     ) r 
join exam_results r3 
on  r3.student_id = r.student_id 
and  r3.score = r.max_score 
and  r3.date = r.max_score_max_date 
join students s 
on  s.id = r3.student_id; 

EDIT: Ajout approprié requête de dédoublonnage grâce à une bonne prise de Mark dans les commentaires

+1

Je ne pense pas que les travaux distincts pour dédupliquer les liens si les dates sont différentes. –

+1

Bon point Mark - aurait besoin d'utiliser quelque chose de déterministe dans une autre sous-requête pour dédupliquer correctement dans la requête ANSI. Je vais éditer pour refléter ... – chadhoc

+0

Nom de colonne invalide 'score': Je pense que vous avez mélangé quelques noms de tables. –

3
SELECT s.name, 
    COALESCE(MAX(er.score), 0) AS high_score 
FROM STUDENTS s 
    LEFT JOIN EXAM_RESULTS er ON er.student_id = s.id 
GROUP BY s.name 
+0

Cela suppose qu'il pourrait y avoir des étudiants sans examens associés. –

+0

ahhh ok, il semble que j'ai posé la question mal. Je vais reformuler. – nickf

+0

devez ajouter une clause group by pour que cela fonctionne, c'est-à-dire "group by s.name" – chadhoc

2

Essayez ceci,

Select student.name, max(result.score) As Score from Student 
     INNER JOIN 
    result 
     ON student.ID = result.student_id 
GROUP BY 
    student.name 
+0

merci Zinx, mais j'ai bourré le libellé d'origine de la question. En fait, j'ai besoin de savoir plus que le meilleur score: j'ai besoin de connaître tous les autres domaines de l'album qui ont aussi leur meilleur score. – nickf

+0

Zinx, sélectionnez votre code et appuyez sur Ctrl + K pour formater la syntaxe de la requête, plus facile à lire – chadhoc

+0

Thankx pour cela. – Zinx

0

Utilisation de MS SQL Server:

SELECT name, score, date FROM exam_results 
JOIN students ON student_id = students.id 
JOIN (SELECT DISTINCT student_id FROM exam_results) T1 
ON exam_results.student_id = T1.student_id 
WHERE exam_results.id = (
    SELECT TOP(1) id FROM exam_results T2 
    WHERE exam_results.student_id = T2.student_id 
    ORDER BY score DESC, date ASC) 

S'il y a un score à égalité, la date la plus ancienne est retourné (changer date ASC-date DESC pour revenir le plus récent à la place).

Sortie:

Jim 93 2009-01-03 00:00:00.000 
Joe 27 2009-04-09 00:00:00.000 
Jay 100 2009-01-06 00:00:00.000 

banc d'essai:

CREATE TABLE students(id int , name nvarchar(20)); 

CREATE TABLE exam_results(id int , student_id int , score int, date datetime); 

INSERT INTO students 
VALUES 
(1,'Jim'),(2,'Joe'),(3,'Jay') 

INSERT INTO exam_results VALUES 
(1, 1, 73, '8/1/09'), 
(2, 1, 93, '9/2/09'), 
(3, 1, 93, '1/3/09'), 
(4, 2, 27, '4/9/09'), 
(5, 2, 17, '8/9/09'), 
(6, 3, 100, '1/6/09') 

SELECT name, score, date FROM exam_results 
JOIN students ON student_id = students.id 
JOIN (SELECT DISTINCT student_id FROM exam_results) T1 
ON exam_results.student_id = T1.student_id 
WHERE exam_results.id = (
    SELECT TOP(1) id FROM exam_results T2 
    WHERE exam_results.student_id = T2.student_id 
    ORDER BY score DESC, date ASC) 

sur MySQL, je pense que vous pouvez changer le TOP (1) à une limite 1 à la fin de l'instruction. Je n'ai pas testé cela cependant.

2

Avec des fonctions analytiques d'Oracle cela est facile:

SELECT DISTINCT 
     students.name 
     ,FIRST_VALUE(exam_results.score) 
     OVER (PARTITION BY students.id 
      ORDER BY exam_results.score DESC) AS score 
     ,FIRST_VALUE(exam_results.date) 
     OVER (PARTITION BY students.id 
      ORDER BY exam_results.score DESC) AS date 
FROM students, exam_results 
WHERE students.id = exam_results.student_id; 
+0

Je sais que PostgreSQL n'est pas dans la liste des questions, mais au cas où quelqu'un trébucherait à travers cela, il a [fonctions de fenêtre] (http://www.postgresql.org/docs/9.3/static/functions-window.html) pour ce faire aussi. – jpmc26

0
Select Name, T.Score, er. date 
from Students S inner join 
      (Select Student_ID,Max(Score) as Score from Exam_Results 
      Group by Student_ID) T 
On S.id=T.Student_ID inner join Exam_Result er 
On er.Student_ID = T.Student_ID And er.Score=T.Score 
Questions connexes