2011-04-25 8 views
12

Estoy creando una consulta que realiza algunos filtros en los datos de calificación.Subconsulta de MySQL para referirse al campo en la consulta primaria

Supongamos que tengo una tabla simple llamada ratings como el siguiente, el almacenamiento de datos de una herramienta de calificación en línea:

 
+----------------+----------------+--------+ 
| page_title  | timestamp  | rating | 
+----------------+----------------+--------+ 
| Abc   | 20110417092134 | 1  | 
| Abc   | 20110418110831 | 2  | 
| Def   | 20110417092205 | 3  | 
+----------------+----------------+--------+ 

necesito para extraer páginas con una alta frecuencia de valores bajos en los últimos 10 notas, y limite esta consulta a páginas que produjeron un volumen de al menos 20 calificaciones en la semana anterior. Esta es la consulta ridículamente largo me ocurrió:

SELECT a1.page_title, COUNT(*) AS rvol, AVG(a1.rating) AS theavg, 
(
    SELECT COUNT(*) FROM 
    (
     SELECT * FROM ratings a2 WHERE a2.page_title = a1.page_title 
     AND DATE(timestamp) <= '2011-04-24' ORDER BY timestamp DESC LIMIT 10 
    ) 
    AS latest WHERE rating >=1 AND rating <=2 ORDER BY timestamp DESC 
) 
AS lowest FROM ratings a1 
WHERE DATE(a1.timestamp) <= "2011-04-24" AND DATE(a1.timestamp) >= "2011-04-17" 
GROUP BY a1.page_title HAVING COUNT(*) > 20 

la consulta de alto nivel busca páginas con más de 20 notas en la semana que termina en 2011-04-24, la subconsulta debe devolver el número de clasificaciones con valores entre [1,2] de las últimas 10 calificaciones de cada artículo de la consulta de nivel superior.

MySQL se queja de que a1.page_title en la cláusula WHERE de la subsubquery es una columna desconocida, sospecho que es porque a1 no está definido como un alias en la consulta de segundo nivel, sino solo en la consulta de nivel superior, pero no tengo ni idea de cómo arreglar esto.

(editado)

Estoy añadiendo como una explicación de mi de sospecha respecto a nivel de referencias cruzadas otra consulta que funciona absolutamente bien, tenga en cuenta que aquí A1 no está definido en la sub consulta, pero es en el precursor inmediato :

SELECT a1.page_title, COUNT(*) AS rvol, AVG(a1.rating) AS theavg, 
(
    SELECT COUNT(*) FROM ratings a2 WHERE DATE(timestamp) <= '2011-04-24' 
    AND DATE(timestamp) >= '2011-04-17' AND rating >=1 
    AND rating <=2 AND a2.page_title = a1.page_title 
) AS lowest FROM ratings a1 
WHERE DATE(a1.timestamp) <= '2011-04-17' AND DATE(a1.aa_timestamp) >= '2011-04-11' 
GROUP BY a1.page_title HAVING COUNT(*) > 20 
+3

FWIW, esta no es una consulta ridículamente larga. Para mí, parece algo corto, de hecho. – MJB

+0

es justo, edité el texto anterior :) – radrat

Respuesta

5

creo que podría considerar unirse a las dos de la línea vistas que podría hacer que las cosas eaiser.

SELECT * 
FROM (SELECT COUNT(*), 
       a2.page_title 
     FROM ratings a2 
     WHERE DATE(timestamp) <= '2011-04-24' 
       AND DATE(timestamp) >= '2011-04-17' 
       AND rating >= 1 
       AND rating <= 2 

     GROUP BY a2.page_title) current 
     JOIN 
     (SELECT a1.page_title, 
        COUNT(*)  AS rvol, 
        AVG(a1.rating) AS theavg 
      FROM ratings a1 
      WHERE DATE(a1.timestamp) <= '2011-04-17' 
        AND DATE(a1.a_timestamp) >= '2011-04-11' 
      GROUP BY a1.page_title 
      HAVING COUNT(*) > 20) morethan20 
     ON current .page_title = morethan20.page_title 
+0

Hola Conrad, este no funciona como a1 en la primera subconsulta no está definido en la consulta principal (SQL falla con el error: columna desconocida 'a1.page_title' en 'where clause') – radrat

+0

@radrat yep tienes razón Lo siento por eso. He actualizado la respuesta. Básicamente, la unión ocurre fuera del contexto de las subconsultas –

+0

gracias por la actualización. Con algunos cambios su solución me indicó la dirección correcta. – radrat

1

Si todo lo que tiene es esta una tabla sencilla, no tengo ni idea de dónde usted está tirando de todos estos otros nombres de tabla de, por ejemplo: A1, A2, clasificaciones. Siento que tu SQL está un poco apagado o que dejas de lado la información.

La razón por la cual tiene el error que usted hace es porque en su sub-sub-consulta no incluye a1 en su declaración "FROM" ... como esa tabla no está incluida, no puede ser referenciada en su WHERE cláusula en esa sub consulta.

SELECT * 
FROM 
    (SELECT * 
     FROM a1 
     WHERE a1.timestamp <= (NOW()-604800) 
      AND a1.timestamp >= (NOW()-1209600) 
     GROUP BY a1.page_title 
     HAVING COUNT(a1.page_title)>20) 
    AS priorWeekCount 
WHERE 
    rating <= 2 
ORDER BY timestamp DESC 
LIMIT 10 

como no tengo una mesa llena de probar esto ... Creo que esto es lo que tu buscas .. pero no se ha probado, y sabiendo mis hábitos de programación, muy rara vez es lo que yo escriba 100% perfecto primera vez;)

+0

¡Bienvenido al desbordamiento de la pila! esto me parece más un comentario, ya que en realidad no responde la pregunta. – colinmarc

+0

"a1" y "a2" son alias de tabla necesarios para campos de referencia cruzada entre consultas de diferentes niveles (ver por ejemplo [este ejemplo] (http: // stackoverflow.com/questions/1973246/how-to-specify-the-parent-query-field-from-within-a-subquery-in-mysql). "calificaciones" es el nombre de la tabla principal, lo omití porque pensé que era trivial, disculpe la confusión. "último" y "más bajo" son alias necesarios debido a los SELECT anidados. – radrat

+0

Hola colinmarc, gracias por la bienvenida! ... Creo que porque tengo puntos bajos y estoy empezando a desbordar la pila, no me deja dejar comentarios sobre las preguntas principales. Como tiene razón, esto es más un comentario ... pero estoy trabajando en el SQL y lo haré en breve. – CenterOrbit

1

Su análisis del error es correcto: lowest se conoce en la subconsulta, a1 no lo es.

Creo que la lógica está al revés. Lo siguiente probablemente no sea el mejor, pero el optimizador puede ser lo suficientemente inteligente como para combinar las dos subconsultas en el SELECT más externo. (Si no lo es, a riesgo de legibilidad se puede introducir otro nivel de sub consulta.)

SELECT r20plus.page_title, 
AVG((SELECT rating 
     FROM ratings r WHERE r.page_title=r20plus.page_title 
     ORDER BY timestamp DESC LIMIT 10)) as av, 
SUM((SELECT CASE WHEN rating BETWEEN 1 AND 2 THEN 1 ELSE 0 END 
     FROM ratings r WHERE r.page_title=r20plus.page_title 
     ORDER BY timestamp DESC LIMIT 10)) as n_low, 
FROM 
(SELECT page_title FROM ratings 
WHERE DATE(a1.timestamp) <= "2011-04-24" AND DATE(a1.timestamp) >= "2011-04-17" 
GROUP BY page_title 
HAVING COUNT(rating) >= 20) AS r20plus; 
+0

Hola Andrew, parece que esta solución no funcionará: las subconsultas incrustadas en AVG y SUM ambas devuelven más de 1 fila y no se puede SELECCIONAR sumas o promedios sobre una serie de resultados devueltos por una subconsulta (el único uso válido de estos funciones está en una consulta con una cláusula GROUP BY explícita). ¿O me estoy perdiendo algo? – radrat

+0

No estoy familiarizado con las limitaciones de MySQL (use Postgres), pero a falta de un GROUP BY, el agregado (SUM; AVG) * debe * tomarse sobre todas las filas devueltas-> solo una fila en la respuesta. ('GROUP BY NULL' también debería ser legal.) También sería posible hacer esto con las funciones de ventana, pero sé que MySQL carece de ellas. –

Cuestiones relacionadas