2010-03-08 27 views
7

Estoy bastante seguro de que no podemos usar la cláusula LIMIT para lo que quiero hacer, así que queríamos saber si hay otras maneras en que podamos lograr esto.Consulta SQL - Limitando los resultados de la consulta

Tengo una tabla que captura qué usuario visitó qué tienda. Cada vez que un usuario visita una tienda, se inserta una fila en esta tabla.

Algunos de los campos son

  • shopping_id (clave principal)
  • STORE_ID
  • user_id

Ahora lo que quiero es - para un conjunto dado de tiendas, encontrar la parte superior 5 usuarios que han visitado la tienda un máximo de veces.

que pueda hacer esto 1 tienda a la vez como:

 
select store_id,user_id,count(1) as visits 
from shopping 
where store_id = 60 
group by user_id,store_id 
order by visits desc Limit 5 

Esto me dará los 5 usuarios que han visitado STORE_ID = 60 los tiempos máximos

Lo que quiero hacer es proporcionar una lista de 10 store_ids y para cada tienda captar los 5 usuarios que visitaron esa tienda max times

 
select store_id,user_id,count(1) as visits 
from shopping 
where store_id in (60,61,62,63,64,65,66) 
group by user_id,store_id 
order by visits desc Limit 5 
Esto no funcionará ya que el límite al final devolverá solo 5 filas en lugar de 5 filas para cada tienda.

Cualquier idea sobre cómo puedo lograr esto. Siempre puedo escribir un bucle y pasar de 1 tienda a la vez, pero quería saber si hay una mejor manera.

+0

MySQL no tiene funciones analíticas - ROW_NUMBER, RANK , DENSE_RANK - que normalmente usarías para resolver situaciones como estas. –

Respuesta

3

El uso de dos variables de usuario y contando la misma store_id consecutiva, se puede reemplazar <= 5 con cualquier nivel que desee

SELECT a.* 
FROM (
SELECT store_id, user_id, count(1) as visits 
FROM shopping 
WHERE store_id IN (60,61,62,63,64,65,66) 
GROUP BY store_id, user_id 
ORDER BY store_id, visits desc, user_id 
) a, 
(SELECT @prev:=-1, @count:=1) b 
WHERE 
CASE WHEN @prev<>a.store_id THEN 
    CASE WHEN @prev:=a.store_id THEN 
    @count:=1 
    END 
ELSE 
    @count:[email protected]+1 
END <= 5 

Editar como solicitó alguna explicación:

La primera sub consulta (a) es la que agrupa y ordena el dato una por lo que tendrá datos como:

store_id | user_id | visits 
---------+---------+------- 
60   1  5 
60   2  3 
60   3  1 
61   2  4 
61   3  2 

la segunda subconsulta (b) init la variable de usuario @prev con -1 y @count con 1

continuación, elegimos todos los datos de la subconsulta (a) la verificación de la condición en el case.

  • verificar que el store_id anterior (@prev) que hemos visto es diferente de la store_id actual. Dado que el primer @prev es igual a -1, no hay nada que coincida con el store_id actual, por lo que la condición <> es verdadera, ingresamos entonces es el segundo caso que acaba de servir para cambiar el valor @prev con el store_id actual. Este es el truco, así que puedo cambiar las dos variables de usuario @count y @prev en las mismas condiciones.

  • si el store_id anterior es igual a @prev simplemente incremente la variable @count.

  • , comprobamos que el número está dentro del valor que queremos por lo que el <= 5

Así que con nuestros datos de prueba al:

step | @prev | @count | store_id | user_id | visits 
-----+-------+--------+----------+---------+------- 
    0  -1  1  
    1  60  1  60   1  5 
    2  60  2  60   2  3 
    3  60  3  60   3  1 
    4  61  1  61   2  4 
    5  61  2  61   3  2 
+0

Hola Patrick: nunca he visto una consulta como esta :) pero funciona perfectamente. Intenté con mis datos de prueba y arroja los resultados exactos que estaba buscando. Tendré que estudiar ahora qué está haciendo esta consulta. Muchas gracias – Gublooo

+0

Hola Patrick - Nunca he visto una consulta como esta :) Por favor, explica el código. Para que podamos aprender de eso. Estaré agradecido. –

+0

@Gubloo, @RAHUL PRASAD agregó alguna explicación – Patrick

1

UNION puede ser lo que estás buscando.

-- fist store 
(select store_id,user_id,count(1) as visits 
from shopping 
where store_id = 60 
group by user_id,store_id 
order by visits desc Limit 5) 
UNION ALL 
-- second store 
(select store_id,user_id,count(1) as visits 
from shopping 
where store_id = 61 
group by user_id,store_id 
order by visits desc Limit 5) 
... 

http://dev.mysql.com/doc/refman/5.0/en/union.html

0

La forma más sencilla sería emitir 10 consultas separadas, una para cada tienda. Si utiliza consultas parametrizadas (por ejemplo, usando PDO in PHP), esto será bastante rápido ya que la consulta se compilará en parte.

Si esto demuestra ser demasiado intensivo en recursos, entonces otra solución sería almacenar en caché los resultados en la tabla de la tienda; es decir, agregar un campo que enumere los 5 mejores usuarios de cada tienda como una simple lista separada por comas. Significa que su base de datos no estaría 100% normalizada, pero eso no debería ser un problema.

+0

Hmmm, es una idea interesante, por lo que está sugiriendo, cada vez que un usuario visita una tienda, en ese momento realiza una comprobación y actualiza la columna de los 5 primeros usuarios. Pensé en ese enfoque, pero luego decidí no hacerlo, pensando en por qué almacenar datos de forma permanente que se pueden recuperar con un simple SQL – Gublooo

+0

@Gubloo: quise ejecutar un script (como un trabajo cron, o para el primer usuario que visita después medianoche, por ejemplo) que harían las actualizaciones. Lo que dijiste puede ser demasiado intensivo en recursos. Sin embargo, tienes razón: si puedes obtener los datos con una simple consulta, eso es ciertamente mejor, pero parece que tu respuesta aceptada está bastante lejos de ser simple;) – DisgruntledGoat

+0

:) - bueno, aunque esa consulta parece compleja - No estoy seguro de que es un recurso intensivo, pero al menos es una forma sencilla de obtener los resultados sobre la marcha. – Gublooo

2

La principal preocupación aquí es la cantidad de veces que consulta una base de datos. Si consultas varias veces desde tu script. Es simplemente un desperdicio de recursos y debe evitarse. Es decir, NO debe ejecutar un ciclo para ejecutar el SQL varias veces al incrementar cierto valor. En su caso 60 a 61 y así sucesivamente.

Solución 1: crear una vista Aquí está la solución

CREATE VIEW myView AS 
select store_id,user_id,count(1) as visits 
from shopping 
where store_id = 60 
group by user_id,store_id 
order by visits desc Limit 5 
UNION 
select store_id,user_id,count(1) as visits 
from shopping 
where store_id = 61 
group by user_id,store_id 
order by visits desc Limit 5 
UNION 
select store_id,user_id,count(1) as visits 
from shopping 
where store_id = 62 
group by user_id,store_id 
order by visits desc Limit 5 

Ahora usa

SELECT * from MyView 

Esta es limitado porque usted no puede hacerlo dinámico. ¿Qué sucede si necesita 60 a 100 en lugar de 60 a 66?

Solución 2: Utilice el procedimiento. No entraré en cómo escribir un procedimiento porque es tarde y pude dormir. :) Bien, el procedimiento debe aceptar dos valores 1er número inicial (60) y 2do recuento (6) Dentro del procedimiento, cree una tabla temporal (cursor) para almacenar datos y luego ejecute un ciclo desde el número inicial hasta el recuento En su caso de 60 a 66 Dentro del bucle escribe el script deseado Reemplazando 60 con una variable de bucle.

select store_id,user_id,count(1) as visits 
from shopping 
where store_id = 60 
group by user_id,store_id 
order by visits desc Limit 5 

Y añada el resultado en la tabla temporal (cursor).

Espero que esto resuelva su problema. Lo siento, no pude darle el código. Si aún lo necesita, por favor envíeme un mensaje. Te lo daré cuando me despierte a la mañana siguiente.

1

Si no va a guardar datos sobre cuándo un usuario visitó una tienda o algo así, simplemente puede actualizar la tabla cada vez que un usuario visita una tienda en lugar de agregar una nueva fila.

Algo como esto:

INSERT INTO `user_store` (`user_id`, `store_id`, `visits`) VALUES ('USER', 'SHOP', 1) 
ON DUPLICATE KEY UPDATE `visits` = `visits` + 1 

Pero creo que esto no funcionaría, porque ni user_id ni store_id son únicos. Debe agregar una clave principal única como esta: usuario # tienda u otra cosa.

Otra opinión sería guardar estos datos (con qué frecuencia un usuario estaba en una tienda) en una tabla separada que contenga ID, user_id, store_id, visitas e incremente las visitas cada vez que también agregue una nueva fila a la tabla existente.

Para obtener el Top5 a continuación, puede utilizar:

SELECT `visits`, `user_id` FROM `user_store_times` WHERE `store_id`=10 ORDER BY `visits` DESC LIMIT 5 
+0

Gracias por ON DUPLICATE KEY UPDATE 'visitas' =' visitas' + 1 cosa. –

Cuestiones relacionadas