2011-03-16 11 views
7

Según mi investigación, este es un problema muy común que generalmente tiene una solución bastante simple. Mi tarea es modificar varias consultas de obtener todos los resultados en obtener los primeros 3 por grupo. Al principio, esto iba bien y utilicé varias recomendaciones y respuestas de este sitio para lograr esto (Productos más vistos). Sin embargo, estoy teniendo problemas con mi último "Producto más vendido" debido a las combinaciones múltiples.N superior por grupo con varias combinaciones de tabla

Básicamente, necesito obtener todos los productos en orden por # ventas más altas por producto en el que el máximo de productos por proveedor es 3 Tengo varias tablas unidas para crear la consulta original, y cada vez que intento para usar las variables para generar clasificaciones produce resultados no válidos. Lo siguiente debe ayudar a comprender mejor el tema (He quitado los campos innecesarios por razones de brevedad):

Tabla Producto

productid | vendorid | approved | active | deleted 

vendedor Tabla

vendorid | approved | active | deleted 

orden de la tabla

orderid | `status` | deleted 

Ordene los artículos Tabla

orderitemid | orderid | productid | price 

Ahora, mi búsqueda original a obtener todos los resultados es el siguiente:

SELECT COUNT(oi.price) AS `NumSales`, 
     p.productid, 
     p.vendorid 
FROM products p 
INNER JOIN vendors v ON (p.vendorid = v.vendorid) 
INNER JOIN orders_items oi ON (p.productid = oi.productid) 
INNER JOIN orders o ON (oi.orderid = o.orderid) 
WHERE (p.Approved = 1 AND p.Active = 1 AND p.Deleted = 0) 
AND (v.Approved = 1 AND v.Active = 1 AND v.Deleted = 0) 
AND o.`Status` = 'SETTLED' 
AND o.Deleted = 0 
GROUP BY oi.productid 
ORDER BY COUNT(oi.price) DESC 
LIMIT 100; 

Por último, (y aquí es donde estoy confundido), estoy tratando de alterar la afirmación anterior de que tales Recibí solo los 3 mejores productos (por # vendidos) por proveedor. Agregaría lo que tengo hasta ahora, pero estoy avergonzado de hacerlo y esta pregunta ya es un muro de texto. Probé las variables pero sigo obteniendo resultados no válidos. Cualquier ayuda sería muy apreciada.

Respuesta

10

Aunque especifique LIMIT 100, este tipo de consulta requerirá un escaneo completo y una tabla para ser compilada, luego cada registro inspeccionado y numerado de filas antes de finalmente filtrar por los 100 que desea visualizar.

select 
    vendorid, productid, NumSales 
from 
(
    select 
     vendorid, productid, NumSales, 
     @r := IF(@g=vendorid,@r+1,1) RowNum, 
     @g := vendorid 
    from (select @g:=null) initvars 
    CROSS JOIN 
    (
     SELECT COUNT(oi.price) AS NumSales, 
       p.productid, 
       p.vendorid 
     FROM products p 
     INNER JOIN vendors v ON (p.vendorid = v.vendorid) 
     INNER JOIN orders_items oi ON (p.productid = oi.productid) 
     INNER JOIN orders o ON (oi.orderid = o.orderid) 
     WHERE (p.Approved = 1 AND p.Active = 1 AND p.Deleted = 0) 
     AND (v.Approved = 1 AND v.Active = 1 AND v.Deleted = 0) 
     AND o.`Status` = 'SETTLED' 
     AND o.Deleted = 0 
     GROUP BY p.vendorid, p.productid 
     ORDER BY p.vendorid, NumSales DESC 
    ) T 
) U 
WHERE RowNum <= 3 
ORDER BY NumSales DESC 
LIMIT 100; 

El enfoque aquí es

  1. Grupo por conseguir NumSales
  2. utilizar variables para remar número de las ventas por vendedor/producto
  3. Filtrar el conjunto de datos numerados para permitir un máximo de 3 por proveedor
  4. Solicite el restante por NumSales DESC y devuelva solo 100
+0

Eso es brillante Richard, gracias! La declaración CROSS JOIN fue el elemento clave que me faltaba en mi intento. Esa es una consulta extremadamente útil, en mi opinión, y una que planeo usar bastante. – Jeremy

+0

@jcargilo - FYI He arreglado el orden interno por. ¡Debe ser vendorid + NumSales solamente! – RichardTheKiwi

+0

¡Excelente, gracias! – Jeremy

0

Me gusta esta solución elegante, sin embargo, cuando ejecuto una consulta adaptada pero similar en mi máquina dev obtengo un conjunto de resultados no deterministas. Creo que esto se debe a la forma en que el optimizador de MySQL se ocupa de asignar y leer variables de usuario dentro de la misma declaración.

De the docs:

Como regla general, nunca se debe asignar un valor a una variable de usuario y leer el valor dentro de la misma declaración. Puede obtener los resultados que espera, pero esto no está garantizado. El orden de evaluación para expresiones que involucran variables de usuario no está definido y puede cambiar en función de los elementos contenidos en una declaración dada; Además, no se garantiza que este orden sea el mismo entre versiones del servidor MySQL.

Solo agregue esta nota aquí en caso de que alguien más se encuentre con este extraño comportamiento.

0

¡La respuesta dada por @RichardTheKiwi funcionó muy bien y me consiguió el 99% del camino hasta allí! Estoy usando MySQL y solo estaba obteniendo la primera fila de cada grupo marcada con un número de fila, mientras que el resto de las filas permanecían NULL. Esto dio lugar a que la consulta devolviera solo el hit más alto para cada grupo en lugar de las tres primeras filas. Para solucionar esto, tuve que inicializar @r en la subconsulta initvars. He cambiado,

from (select @g:=null) initvars

a

from (select @g:=null, @r:=null) initvars

También puede inicializar @r a 0 y que funcionaría de la misma. Y para aquellos menos familiarizados con este tipo de sintaxis, la sección adicional está leyendo cada grupo ordenado y si una fila tiene el mismo vendorid que la fila anterior, que se rastrea con la variable @g, incrementa el número de fila, que se almacena en la variable @r. Cuando este proceso alcanza el siguiente grupo con una nueva vendorid, la declaración IF ya no evalúe como verdadera y la variable @r (y con ello la RowNum) se restablecerá a 1.

Cuestiones relacionadas