MySQL-获取在此期间下达25个订单的用户

时间:2019-05-23 09:41:29

标签: mysql sql

我有具有这种结构的用户和订单表(简化了问题):

USERS

userid
registered(date)

ORDERS

id
date (order placed date)
user_id

我需要获取在指定时间段(例如,2019年5月)的第25个订单下的用户列表(用户ID 的列表)每个用户的第25个订单,下第25个订单的天数(用户注册日期与第25个订单的日期之间的差异)。

例如,如果用户在2018年4月注册,然后在2018年下了20个订单,然后在2019年1月至5月下了21-30个订单-如果该用户下第25个(帐户的全部),则该用户应该在此数组中在2019年5月订购。

如何使用MySQL请求做到这一点?

示例数据和结构:http://www.sqlfiddle.com/#!9/998358(为了进行测试,您可以获得3阶(例如25号),而不添加很多示例数据记录。

不需要一个请求-如果不能在一个请求中完成该请求,则几乎没有可能。

2 个答案:

答案 0 :(得分:1)

您可以使用相关子查询来获取用户在当前订单之前的订单数量。如果是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';

答案 1 :(得分:0)

执行此操作的基本低效方法是获取日期在您目标范围内的ORDERS中每一行的user_id,而具有相同user_id和较低日期的ORDERS中的行数恰好是24。 >

这可能非常丑陋,很快。

如果您要从自己控制的代码中调用此代码,就不能从代码中执行此操作吗?

如果没有,应该有一种方法可以为每行分配一个索引,该索引描述其特定user_id的顺序之间的排名,然后从索引为25且有正确日期的行中选择所有user_id。这将为您提供从选择中选择的选择,但是应该更快。这里的困难是控制行的顺序,所以这是我设想的选择:

  1. 从将要初始化为0的两个变量组成的表中,选择所有行,按user_id升序,日期升序排序,并没有合并为任何内容。
  2. 从中选择所有内容,同时更新var以知道某行的user_id是否与最后一行相同,并添加将报告此内容的字段(因此,对于每个user_id,第一行的顺序将具有特定值,例如0而同一user_id的其他行将带有1)
  3. 从中选择全部,再加上一个等于自身的字段,如果第一个添加的字段为1,则选择一个,否则为0
  4. 从中,从第二个添加字段为25且日期在范围内的行中选择user_id。

仅当您需要在一个请求中完成所有操作时才需要进行联合(您必须使用比其所使用的选项低的选择来初始化它们)。

编辑:好吧,如果您也需要日期,则可以将其与user_id一起选择,但是计算sql中的天数会很麻烦。只需将结果表加入到用户表中,即可获得25个订单的日期及其注册日期,您肯定可以在代码上有所不同。 我将尝试构建一个实际的请求,但是,如果您想真正地了解实现此请求所需的内容,则必须阅读mysql变量,联合和条件语句。

“看起来太复杂了。我确信这可以通过当前的数据库结构和1-2个请求来完成。”好吧,是的使用COUNT请求,这将很容易,而且速度很慢。

有关复杂的答案,请参见http://www.sqlfiddle.com/#!9/998358/21

由于可以使用多个请求,因此可以先初始化var。 其实并没有那么复杂,您只需要了解如何向SQL引擎具体表达“用户的第25条命令”的含义即可。

有关天的差异,请参见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 ;

使用该方法,需要注意的是基本表中列的数量和类型。在这里orders的第一个字段是一个int,所以我将INIT_PREV_USR放在第一个,然后再有两个字段,所以我只添加两个带名称的零,并将其命名为day。大多数类型都可以使用,因为联合实际上并没有做任何事情,但是当您的第一个字段是blob时,我不会尝试这样做(最糟糕的情况是您可以使用JOIN)。

您会注意到这是从mysql中的分页方法派生的。如果您想将此方法应用于其他引擎,只需检查一下它们的最佳分页调用,您就可以思考。