Sql Query - ограничение результатов запроса - PullRequest
7 голосов
/ 08 марта 2010

Я совершенно уверен, что мы не можем использовать предложение LIMIT для того, что я хочу сделать, поэтому хотел выяснить, есть ли другие способы, которыми мы можем это сделать.

У меня есть таблица, которая фиксирует, какой пользователь посетил какой магазин. Каждый раз, когда пользователь посещает магазин, в эту таблицу вставляется строка.

Некоторые поля

  • shopping_id (первичный ключ)
  • store_id
  • user_id

Теперь то, что я хочу, - для данного набора магазинов найдите 5 лучших пользователей, которые посетили магазин максимальное количество раз.

Я могу сделать это 1 магазин за раз, как: 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

Это даст мне 5 пользователей, которые посетили store_id = 60 макс. Раз

Что я хочу сделать, это предоставить список из 10 store_ids и для каждого магазина выбрать 5 пользователей, которые посетили этот магазин максимальное количество раз. 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 Это не будет работать, так как Limit в конце вернет только 5 строк вместо 5 строк для каждого магазина.

Любые идеи о том, как мне этого добиться. Я всегда могу написать цикл и передать 1 магазин за раз, но хотел знать, есть ли лучший способ

Ответы [ 5 ]

3 голосов
/ 09 марта 2010

Используя две пользовательские переменные и считая один и тот же последовательный store_id, вы можете заменить <= 5 любым желаемым лимитом

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:=@count+1
 END <= 5

Редактировать, как требуется, некоторые пояснения:

Первый подзапрос (a) - это тот, который группирует и упорядочивает данные так, чтобы у вас были такие данные, как:

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

Второй подзапрос (b) инициирует переменную пользователя @prev с -1 и@count с 1

, затем мы выбираем все данные из подзапроса (a), проверяя условие в case.

  • и проверяем, что предыдущий store_id (@prev) мы видели, отличается от текущего store_id.Так как первый @prev равен -1, ничто не соответствует текущему store_id, поэтому условие <> является истинным, которое мы вводим, тогда это второй случай, который просто служит для изменения значения @prev с текущим store_id.Это хитрость, поэтому я могу изменить две пользовательские переменные @count и @prev в одном и том же состоянии.

  • , если предыдущий store_id равен @prev, просто увеличить @count переменная.

  • мы проверяем, что число находится в пределах желаемого значения, поэтому <= 5

Итак, с нашими тестовыми данными:

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   
2 голосов
/ 08 марта 2010

Основное беспокойство вызывает количество запросов к базе данных. Если вы запрашиваете несколько раз из вашего скрипта. Это просто растрата ресурсов, и ее следует избегать. То есть вы НЕ должны запускать цикл для многократного запуска SQL, увеличивая определенное значение. В вашем случае от 60 до 61 и т. Д.

Решение 1: Создать вид Вот решение

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 

Теперь используйте

SELECT * from MyView

Это ограничено, потому что вы не можете сделать его динамичным. Что делать, если вам нужно от 60 до 100 вместо 60 до 66.

Решение 2: Используйте процедуру. Я не буду вдаваться в то, как написать процедуру, потому что это поздняя ночь, и я заснул. :) Ну, процедура должна принимать два значения 1-й начальный номер (60) и 2-й счет (6) Внутри процедуры создайте временную таблицу (курсор) для хранения данных, затем выполните цикл от начального числа до количества раз В вашем случае от 60 до 66 Внутри цикла напишите нужный скрипт. Замените 60 на переменную цикла.

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

И добавить результат во временную таблицу (курсор).

Надеюсь, это решит вашу проблему. Извините, я не могу дать вам код. Если вам все еще нужно, пожалуйста, отправьте мне сообщение. Я дам это тебе, когда проснусь на следующее утро.

1 голос
/ 08 марта 2010

Если вы не будете сохранять данные о том, когда пользователь посещал магазин, или что-то подобное, вы можете просто обновлять таблицу каждый раз, когда пользователь посещает магазин, вместо добавления новой строки.

Что-то вроде этого:

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

Но я думаю, что это не сработает, потому что ни user_id, ни store_id не являются уникальными.Вы должны добавить уникальный первичный ключ, такой как: user # store или что-то еще.

Другое мнение было бы сохранить эти данные (как часто пользователь был в магазине) в отдельной таблице, содержащей ID,user_id, store_id, посещения и приращения посещений каждый раз, когда вы также добавляете новую строку в существующую таблицу.

Чтобы получить Top5, вы можете использовать:

SELECT `visits`, `user_id` FROM `user_store_times` WHERE `store_id`=10 ORDER BY `visits` DESC LIMIT 5
1 голос
/ 08 марта 2010

UNION может быть тем, что вы ищете.

-- 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 голосов
/ 08 марта 2010

Простейшим способом было бы выдать 10 отдельных запросов, по одному для каждого магазина. Если вы используете параметризованные запросы (например, используя PDO в PHP ), это будет довольно быстро, так как запрос будет частично скомпилирован.

Если это все еще оказывается слишком ресурсоемким, то другим решением будет кэширование результатов в таблице хранилищ - т.е. добавьте поле, в котором список 5 лучших пользователей для каждого хранилища представлен в виде простого списка, разделенного запятыми. Это означает, что ваша база данных не будет на 100% нормализована, но это не должно быть проблемой.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...