2009-12-07 28 views
8

Aquí está un ejemplo simplificado de lo que estoy hablando:tablas Joining basado en el valor máximo

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 | 
        |____|____________|_______|________| 

Supongamos, por el bien de esta pregunta, que cada estudiante tiene al menos uno de los resultados del examen grabado.

¿Cómo seleccionarías a cada estudiante junto con su puntaje más alto?Editar: ... Y los otros campos en ese registro?

salida esperada:

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

respuestas utilizando todo tipo de DBMS son bienvenidos.

+2

¿Cómo resolver los lazos?En su ejemplo, ¿qué registro debería seleccionarse en Jim obtuvo 93 puntajes dos veces? – Sparky

+1

en mis propias tablas, (que no tienen nada que ver con estudiantes y exámenes) esto no sucede. ¿O uno debería estar bien? – nickf

Respuesta

10

Respondiendo a la pregunta editada (es decir, para obtener columnas asociadas también).

en SQL Server 2005 +, el mejor enfoque sería utilizar un ranking/window function en conjunción con un CTE, así:

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; 

Para una solución compatible con ANSI-SQL, una sub consulta y auto-unirse a voluntad trabajo, así:

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; 

este último asume que no hay duplicados combinaciones student_id/max_score, si hay, y/o si desea planear para de-duplicarlos, tendrá que utilizar otra subconsulta unirse a algo determinista para decidir qué disco tirar. Por ejemplo, suponiendo que no se puede tener múltiples registros para un estudiante determinado con la misma fecha, si se quería romper un empate en base a la más reciente max_score, que haría algo como lo siguiente:

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: Añadido adecuados de detección de duplicados de consulta gracias al buen partido de Marcos en los comentarios

+1

No creo que el distinto funcione para desduplicar vínculos si las fechas son diferentes. –

+1

Marca de buen punto: necesitaría usar algo determinista en otra subconsulta para deducir correctamente en la consulta ANSI. Voy a editar para reflejar ... – chadhoc

+0

Nombre de columna inválido 'puntuación': Creo que tienes un par de nombres de tabla mezclados. –

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

Esto supone que podría haber estudiantes sin exámenes asociados. –

+0

ahhh ok, parece que hice la pregunta mal. Voy a volver a redactar. – nickf

+0

tiene que agregar una cláusula group by para que esto funcione, es decir, "group by s.name" – chadhoc

2

Prueba de esto,

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

gracias Zinx, sin embargo, rellené la redacción original de la pregunta. De hecho, necesito saber más que solo el puntaje más alto: necesito conocer todos los otros campos en el registro que también tienen su puntaje más alto. – nickf

+0

Zinx, resalte su código y presione Ctrl + K para formatear la sintaxis de la consulta, más fácil de leer – chadhoc

+0

Gracias por eso. – Zinx

0

uso 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) 

Si hay un empate, se devuelve la fecha más antigua (cambiar date ASC a date DESC para volver el más reciente en su lugar).

Salida:

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 

Banco de pruebas:

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) 

En MySQL, creo que se puede cambiar la parte superior (1) con un límite de 1 al final de la declaración. No he probado esto sin embargo.

2

Con funciones analíticas de Oracle esto es fácil:

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

Sé que PostgreSQL no está en la lista de preguntas, pero en caso de que alguien se tropiece a través de esto, tiene [funciones de ventana] (http://www.postgresql.org/docs/9.3/static/functions-window.html) para hacer esto también. – 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 
Cuestiones relacionadas