2010-07-01 9 views
5

Digamos que tengo un juego donde se formula una pregunta, las personas publican las respuestas que se puntúan y las 10 respuestas principales ganan. Tengo una base de datos SQL que almacena toda esta información, por lo que podría tener tablas como Usuarios, Preguntas y Respuestas. La tabla de respuestas tiene foreign_keys user_id y question_id, y atributo total_score.Seleccione los artículos que son los mejores N resultados para una tabla relacionada

Obviamente, para una pregunta en particular que pueda recuperar los 10 Respuestas superior con un orden y límite:

SELECT * FROM Responses WHERE question_id=? ORDER BY total_score DESC LIMIT 10; 

Lo que estoy buscando es una manera de que pueda determinar, para un usuario particular, una lista de todas sus respuestas que son ganadores (en el top 10 para su pregunta particular). Es simple programar paso a paso cada Respuesta y ver si está incluida entre las 10 principales para su Pregunta, pero me gustaría optimizar esto, así que no estoy haciendo N + 1 consultas donde N es el número de Respuestas que el Usuario ha enviado .

+1

StackOverflow 2.0? Lo siento, no pude evitarlo ... –

+0

lol! Lo curioso es que no me di cuenta de que mi ejemplo describe StackOverflow hasta que lo señalaste. Así es como mi cerebro está trabajando hoy. – MikeJ

Respuesta

3

Si utiliza Oracle, Microsoft SQL Server, DB2 o PostgreSQL, estas bases de datos admiten funciones de ventana. Únase a las respuestas del usuario a otras respuestas a la misma pregunta. Luego particione por pregunta y orden por puntaje descendente. Use el número de fila dentro de cada partición para restringir el conjunto a los que están en el top 10. También transfiera el user_id del usuario dado para que pueda seleccionarlos del top 10, ya que solo está interesado en las respuestas del usuario dado.

SELECT * 
FROM (
    SELECT r1.user_id AS given_user, r2.*, 
    ROW_NUMBER() OVER (PARTITION BY r2.question_id ORDER BY r2.total_score DESC) AS rownum 
    FROM Responses r1 JOIN Responses r2 ON r1.question_id = r2.question_id 
    WHERE r1.user_id = ? 
) t 
WHERE rownum <= 10 AND user_id = given_user; 

Sin embargo, si utiliza MySQL o SQLite u otras bases de datos que no son compatibles de ventanas funciones, puede utilizar esta solución diferente:

de consulta para las respuestas del usuario, y el uso de una combinación para que coincida con otra respuestas a las preguntas respectivas con mayor puntaje (o PK más temprano en el caso de los vínculos). Agrupe por pregunta y cuente el número de respuestas que tienen puntaje más alto. Si el recuento es menor a 10, la respuesta del usuario se encuentra entre las 10 principales por pregunta.

SELECT r1.* 
FROM Responses r1 
LEFT OUTER JOIN Responses r2 ON r1.question_id = r2.question_id 
    AND (r1.total_score < r2.total_score 
    OR r1.total_score = r2.total_score AND r1.response_id > r2.response_id) 
WHERE r1.user_id = ? 
GROUP BY r1.question_id 
HAVING COUNT(*) < 10; 
+0

Esto es ridículamente inteligente Bill ... mi única pregunta es (y lo intentaré mañana cuando tengo tiempo) ¿Será esto más rápido que una subconsulta repetida? Hay muchos cálculos pasando. – MikeJ

+0

El rendimiento varía mucho según la marca de la base de datos. Cada RDBMS es más eficiente en diferentes tipos de consultas. Y uno podría ser mejor para su colección específica de datos. No sé qué base de datos está usando, así que todo lo que puedo aconsejar es que pruebe ambos métodos y vea cuál le gusta más. –

+0

Estoy escribiendo una aplicación de Rails, así que estoy usando principalmente MySQL y SQLite por el momento, pero tratando de ser lo más independiente de la base de datos posible para comenzar, las optimizaciones específicas de la base de datos pueden venir más tarde. Me gusta mucho su segundo ejemplo porque parece que funcionaría en casi cualquier RDBMS sin soluciones provisionales. – MikeJ

2

Pruebe una instrucción de selección incrustada. No tengo acceso a una herramienta de DB hoy, así que no puedo confirmar la sintaxis/salida. Simplemente realice los cambios apropiados para capturar todas las columnas que necesita. También puede agregar preguntas a la consulta principal y unir respuestas.

select * 
    from users 
    , responses 
where users.user_id=responses.user_id 
    and responses.response_id in (SELECT z.response_id 
            FROM Responses z 
            WHERE z.user_id = users.user_id 
           ORDER BY total_score DESC 
           LIMIT 10) 
+0

Eso no funcionará, porque las filas del candidato de Respuesta deben agruparse por question_id antes de poder determinar cuáles son los 10 ganadores * de esa pregunta *. – Confusion

+0

eso es lo que quiero decir al incluir las preguntas. Mi ejemplo te dará los 10 mejores ganadores de estas personas, no los 10 primeros por pregunta. – northpole

+0

Te dará las 10 mejores respuestas de puntaje más altas para este usuario. Sin embargo, podría ser que ninguno de ellos sea ganador, ya que cada pregunta a la que pertenece cada una de esas respuestas tiene 10 respuestas de puntaje más altas por parte de otras 10 personas. – Confusion

1

O realmente puede optimizarlo agregando otro campo como "IsTopPost". Usted tiene que actualizar los altos cargos cuando alguien vota, pero la consulta sería simple:

SELECT * FROM Responses WHERE user_id=? and IsTopPost = 1 
+0

Esto funcionaría, pero podría ser arrojado a una espada por su DBA haciéndolo de esta manera. En mi experiencia, los DBA prefieren que obtengas valores en lugar de almacenarlos, si es posible. Por ejemplo, no almacene el recuento de filas en una tabla, sino más bien cuéntelas cuando las necesite. – northpole

+0

Estoy de acuerdo en cierta medida, pero también depende de la escala y el patrón de acceso. Si hay miles de millones de respuestas y necesitabas calcular la respuesta a menudo, la cardinalidad de agregar ese campo ahorraría toneladas de lecturas y bloqueos, lo que haría feliz a tu DBA. Si se trata de un informe único, calcúlelo sobre la marcha y tome el golpe. – GalacticJello

+0

GalacticJello, tienes toda la razón aquí. Hay momentos en que esto podría estar bien, y tu ejemplo es uno de ellos. – northpole

1

creo que algo como esto debe hacer el truco:

SELECT 
    user_id, question_id, response_id 
FROM 
    Responses AS r1 
WHERE 
    user_id = ? 
AND 
    response_id IN (SELECT response_id 
        FROM Responses AS r2 
        WHERE r2.question_id = r1.question_id 
        ORDER BY total_score DESC LIMIT 10) 

Efectivamente, para cada question_id, una se realiza una subconsulta que determina las 10 respuestas principales para ese question_id.

Es posible que desee considerar agregar una columna que marque ciertas Respuestas como 'ganadores'. De esta manera, puede simplemente seleccionar esas filas y guardar la base de datos de tener que calcular los 10 mejores una y otra vez.

+0

Como noté en la primera respuesta, el único problema es que LIMIT en una subconsulta no lo hará trabajo en MySQL pero funciona muy bien aparte de eso. – MikeJ

Cuestiones relacionadas