2016-05-31 7 views
3

У меня проблема, похожая на LIMITing a SQL JOIN, но с немного более сложным требованием.ОГРАНИЧИТЬ SQL JOIN, с условиями JOIN

Я хочу искать для пользователей и связанных с ними сделок, которые лежат в диапазоне времени:

SELECT u.*, t.* 
FROM User u 
JOIN Transaction t ON t.user_id = u.id 
WHERE t.timestamp >= ? and t.timestamp <= ?; 

До сих пор, так хорошо. Теперь я хочу повторить запрос, но с LIMIT количеством возвращаемых пользователей. Однако не должно быть ограничений на количество транзакций, возвращенных данному пользователю.

Если я следовать подходу, предложенному в другом вопросе, это означало бы в:

SELECT u.*, t.* 
FROM (SELECT * FROM User LIMIT 10) u 
JOIN Transaction t ON t.user_id = u.id 
WHERE t.timestamp >= ? and t.timestamp <= ?; 

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

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

Как я могу достичь этого с помощью MySQL?

ответ

1

Вы можете использовать переменные для этого:

SELECT * 
FROM (
    SELECT *, 
     @rn := IF(@uid = user_id, @rn, 
        IF(@uid := user_id, @rn +1, @rn + 1)) AS rn 
    FROM (
    SELECT u.*, t.* 
    FROM User u 
    JOIN Transaction t ON t.user_id = u.id 
    WHERE t.timestamp >= x and t.timestamp <= y) AS t 
    CROSS JOIN (SELECT @rn := 0, @uid := 0) AS vars 
    ORDER BY user_id) AS x 
WHERE x.rn <= 10 

Переменная @rn увеличивается на 1 каждый раз, когда новый пользователь возвращается в запросе. Таким образом, мы можем контролировать количество пользователей, возвращаемых с помощью @rn <= 10.

+0

Спасибо, это действительно работает, за исключением отсутствующего закрытия parenthese в конце второго 'IF()'. Есть ли влияние производительности на два подзаголовка? – Benjamin

+0

Также, подумав об этом немного, мне кажется, что MySQL должен будет прочитать полный результат (все строки) исходного JOIN (возможно, во временной таблице?), Прежде чем возвращать только первые несколько, выполняя внешний SELECT. Разве это не может быть огромным узким местом производительности, когда у вас много пользователей, и только некоторые из них имеют соответствующие транзакции? – Benjamin

+0

@Benjamin Вы можете протестировать все предлагаемые запросы с вашими фактическими данными и рассказать нам, как они сравниваются друг с другом. –

1

Вы можете сделать это без переменных, но это требует повторения join логики:

SELECT u.*, t.* 
FROM (SELECT * 
     FROM User 
     WHERE EXISTS (SELECT 1 
        FROM Transaction t 
        WHERE t.user_id = u.id AND 
          t.timestamp >= ? and t.timestamp <= ? 
        ) 
     LIMIT 10 
    ) u JOIN 
    Transaction t 
    ON t.user_id = u.id 
WHERE t.timestamp >= ? and t.timestamp <= ?; 

EDIT:

Вероятно, самый быстрый ответ что-то вроде этого:

select u.*, t.* 
from (select user_id 
     from (select user_id 
      from transaction t 
      where t.timestamp >= ? and t.timestamp <= ? 
      limit 1000 
      ) t 
     limit 30 
    ) tt join 
    user u 
    on tt.userid = u.id join 
    transaction t 
    on tt.userid = t.userid and t.timestamp >= ? and t.timestamp <= ?; 

Первый Подзапрос выбирает 1000 записей соответствия в таблице транзакций. Я предполагаю, что этого более чем достаточно, чтобы получить 30 пользователей. Этот список затем соединяется с таблицей пользователя и транзакции, чтобы получить окончательные результаты. Ограничивая список без необходимости полного сканирования таблицы, первый запрос должен быть довольно быстрым. , , особенно с дополнительным индексом на (timestamp, user).

+0

Спасибо, я не очень люблю запросы с переменной основанием, но повторение JOIN еще более громоздко, я бы сказал, особенно если запрос становится более сложным (и он будет). В любом случае, каково влияние производительности повторения JOIN? Должен ли MySQL выполнять эту работу дважды, или есть ли какая-то оптимизация производительности? – Benjamin

Смежные вопросы