MySQL - получить пользователей, которые разместили 25-й заказ за период - PullRequest
0 голосов
/ 23 мая 2019

У меня есть таблицы пользователей и заказов с этой структурой (упрощенно для вопроса):

USERS

userid
registered(date)

ORDERS

id
date (order placed date)
user_id

Мне нужно получить массив пользователей (массив идентификатор пользователя ), которые разместили свои 25-й заказ в течение указанного периода (например, в мае 2019 года), дата 25-го заказа для каждого пользователя, количество дней для размещения 25-го заказа (разница между датой регистрации дляпользователь и дата размещения 25-го заказа).

Например, если пользователь зарегистрировался в апреле 2018 года, затем разместил 20 заказов в 2018 году, а затем разместил 21-30-е заказы в январе-мае 2019 года - этот пользователь должен находиться в этом массиве, если он разместил 25-е место (в целом дляего учетная запись) заказ в мае 2019 года.

Как я могу сделать это с запросом MySQL?

Образец данных и структура: http://www.sqlfiddle.com/#!9/998358 (для тестирования вы можете получить 3-й заказ как экс., не 25-го, чтобы не добавлять много записей образцов данных).

Один запрос не требуется - если это невозможно сделать в одном запросе, возможно несколько запросов.

Ответы [ 2 ]

1 голос
/ 23 мая 2019

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

SELECT o1.user_id,
       o1.date,
       datediff(o1.date, u1.registered)
       FROM orders o1
            INNER JOIN users u1
                       ON u1.userid = o1.user_id
       WHERE (SELECT count(*)
                     FROM orders o2
                     WHERE o2.user_id = o1.user_id
                           AND o2.date < o1.date
                               OR o2.date = o1.date
                                  AND o2.id < o1.id) = 24
             AND o1.date >= '2019-01-01'
             AND o1.date < '2019-06-01';
0 голосов
/ 23 мая 2019

Основной неэффективный способ сделать это состоит в том, чтобы получить user_id для каждой строки в ORDERS, где дата находится в целевом диапазоне И количество строк в ORDERS с тем же user_id и более низкой датой составляет ровно 24.

Хотя это может быть очень уродливо, очень быстро.

Если вы вызываете это из кода, которым вы управляете, вы не можете сделать это из кода?

Если нет, должен быть способ назначить каждой строке индекс, описывающий ее ранг среди заказов для его определенного user_id, и выбрать из этого все user_id из строк с индексом 25 и правильной датой.Это даст вам возможность выбора из выбора, но это должно быть намного быстрее.Сложность заключается в том, чтобы контролировать порядок строк, поэтому я представляю следующие селекты:

  1. Выбрать все строки, упорядочить по user_id asc, по дате asc, объединить до нуля из созданной таблицыиз двух переменных, которые вы инициализируете в 0.
  2. , выберите все при обновлении переменной, чтобы узнать, совпадает ли user_id строки с последней, и добавьте поле, которое будет сообщать об этом (так для каждогоuser_id первая строка в порядке будет иметь определенное значение, например 0, в то время как другие строки для того же user_id будут иметь 1)
  3. , выберите все, плюс равное себе поле плюс одно в случае добавления первогополе равно 1, иначе 0
  4. , из этого выберите user_id из строк, где второе добавленное поле равно 25, а дата находится в диапазоне.

Объединение необходимо толькоесли вам нужно сделать все это в одном запросе (вам нужно инициализировать их с более низким выбором, чем тот, в котором они используются).

Редактировать: Ну, если вам нужна дата, вы можете просто выбрать еевместес user_id, но вычисление количества дней в sql будет проблемой.Просто присоедините таблицу результатов к таблице пользователей и получите и дату 25-го заказа, и дату их регистрации, и вы наверняка сможете изменить код.Я попытаюсь составить реальный запрос, однако, если вы хотите действительно понять, что вам нужно для этого, вы должны прочитать о переменных, объединениях и условных выражениях mysql.

"Выглядит слишком сложно. Я уверен, чточто это можно сделать с помощью текущей структуры БД и 1-2 запросов.Ну, да.Используйте запрос COUNT, он будет легким и медленным до чертиков.

Сложный ответ см. http://www.sqlfiddle.com/#!9/998358/21

Поскольку вы можете использовать несколько запросов, вы можете сначала инициализировать переменные,Это на самом деле не сложно, вам просто нужно понять, как конкретно выразить то, что вы подразумеваете под «25-й командой пользователя», для механизма SQL.

См. http://www.sqlfiddle.com/#!9/998358/24 разницу в днях, оказывается, есть метод для этого.

Редактировать 5: кажется, вы идете с методом COUNT.Я буду молиться, чтобы ваша БД была маленькой.

Редактировать 6: Для потомков: метод подсчета займет годы на очень больших базах данных.Поскольку OP не вернулся, я предполагаю, что его достаточно мало, чтобы пропустить скорость запроса.Если это не ваш случай и, скажем, прошло 10 лет, а ссылки sqlfiddle не работают;Вот решение для двух запросов:

SET @PREV_USR:=0;
SELECT user_id, date_ FROM (
  SELECT user_id, date_, SAME_USR AS IGNORE_SMUSR,
  @RANK_USR:=(CASE SAME_USR WHEN 0 THEN 1 ELSE @RANK_USR+1 END) AS RANK FROM (
    SELECT orders.*, CASE WHEN @PREV_USR = user_id THEN 1 ELSE 0 END AS SAME_USR,
    @PREV_USR:=user_id AS IGNORE_USR FROM
      orders
      ORDER BY user_id ASC, date_ ASC, id ASC
    ) AS DERIVED_1
  ) AS DERIVED_2
WHERE RANK = 25 AND YEAR(date_) = 2019 AND MONTH(date_) = 4 ;

Просто измените RANK =?и условия, соответствующие вашим потребностям.Если вы хотите полностью понять это, начните с самого внутреннего SELECT, затем продвигайтесь высоко;эта версия объединяет пункты 1 и 2. моего объяснения.

Теперь иногда вам придется использовать API или что-то еще, и это не позволит вам хранить значения переменных в памяти, если вы не передадите это или какое-то другое ограничение, и вынужно будет сделать это одним запросом.Для этого вы ставите инициализацию на один шаг ниже и делаете так, чтобы она не влияла на высшие операторы.IMO лучший способ сделать это в UNION с поддельной таблицей, где исключена только строка.Вы избежите хлопот JOIN, и это просто лучше в целом.

SELECT user_id, date_ FROM (
  SELECT user_id, date_, SAME_USR AS IGNORE_SMUSR,
  @RANK_USR:=(CASE SAME_USR WHEN 0 THEN 1 ELSE @RANK_USR+1 END) AS RANK FROM (
    SELECT DERIVED_4.*, CASE WHEN @PREV_USR = user_id THEN 1 ELSE 0 END AS SAME_USR,
    @PREV_USR:=user_id AS IGNORE_USR FROM
      (SELECT * FROM orders
        UNION
        SELECT * FROM (
          SELECT (@PREV_USR:=0) AS INIT_PREV_USR, 0 AS COL_2, 0 AS COL_3
        ) AS DERIVED_3
        WHERE INIT_PREV_USR <> 0
      ) AS DERIVED_4
      ORDER BY user_id ASC, date_ ASC, id ASC
    ) AS DERIVED_1
  ) AS DERIVED_2
WHERE RANK = 25 AND YEAR(date_) = 2019 AND MONTH(date_) = 4 ;

При использовании этого метода нужно следить за количеством и типом столбцов в вашей базовой таблице.Здесь первое поле ордеров - int, поэтому сначала я помещаю INIT_PREV_USR, затем есть еще два поля, поэтому я просто добавляю два нуля с именами и называю это день.Большинство типов работают, так как объединение на самом деле ничего не делает, но я бы не стал это делать, когда вашим первым полем является BLOB-объект (в худшем случае вы можете использовать JOIN).

Вы заметитеэто получено из метода нумерации страниц в MySQL.Если вы хотите применить это к другим движкам, просто проверьте их лучшие вызовы нумерации страниц, и вы сможете уметь продумывать.

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