【问题标题】:MySQL - get users who placed 25th order during periodMySQL - 获取期间下达第 25 个订单的用户
【发布时间】:2019-05-23 09:41:29
【问题描述】:

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

USERS

userid
registered(date)

ORDERS

id
date (order placed date)
user_id

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

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

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

示例数据和结构:http://www.sqlfiddle.com/#!9/998358(用于测试,您可以获得 3 阶,而不是 25 阶,以不添加大量示例数据记录)。

不需要一个请求 - 如果不能在一个请求中完成,则很少有可能并被允许。

【问题讨论】:

  • 添加适当的数据样本、您的预期结果......以及您正在尝试的代码......
  • 如果我知道如何发出这个 SQL 请求,我就不会问这个问题。我不知道如何提出这样的要求:(
  • Edit 问题并将表格的 DDL 添加到已请求的示例数据和预期结果中。
  • @stickybit 什么是 DDL?
  • 数据定义语言——尤其是CREATE TABLE语句。

标签: mysql sql


【解决方案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';

【讨论】:

  • 非常感谢! - 知道如何在此或下一个请求中获取“每个用户的第 25 次订单的日期”、“下第 25 次订单的天数”吗?
  • @Dmitry:来吧,包含日期真的很简单,只需将其包含在列列表中即可。要获取所选日期和该用户的最小订单日期之间的日期,请使用 datediff()。你可以在子查询中得到它。查看编辑。
  • 我认为您的要求对于日期差异不太正确。要获取用户用于下 25 次订单的天数,我们需要查看用户注册日期(来自用户表)和第 25 次订单日期(而不是第一次订单日期和第 25 次订单日期之间)之间的差异。您能帮我更改您的查询以在此处正确加入 users 表以检查 users.registered 字段(这是用户注册日期字段)而不是 min(o3.date) 吗?
  • 谢谢。我在真实站点上运行此请求,并且使用真实数据非常慢(来自 250 个客户的 1500 个订单 - 这不是那么大的值,在 1 个月内进行了测试) - 加载需要几分钟(我不是在谈论获取信息一年 - 这将永远持续)。有什么办法可以优化这个请求?或者您可以说这个请求的哪一部分最耗时(这样我们就可以摆脱这些信息)?
【解决方案2】:

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

不过,这可能会变得非常丑陋,但很快。

如果你从你控制的代码中调用它,你不能从代码中调用它吗?

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

  1. 从将初始化为 0 的两个变量组成的表中选择所有行,按 user_id asc、date asc、union-ed 排序。
  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

由于您可以使用多个请求,因此您可以先初始化变量。 它实际上并没有那么复杂,您只需要了解如何将“用户的第 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 中的分页方法。如果您想将此应用到其他引擎,只需查看他们最好的分页调用,您应该能够进行思考。

【讨论】:

  • 看起来太复杂了。我确信这可以通过当前的数据库结构和 1-2 个请求来完成。
猜你喜欢
  • 2021-03-20
  • 1970-01-01
  • 2019-09-07
  • 2015-06-19
  • 2013-05-05
  • 2011-05-06
  • 1970-01-01
  • 2018-11-06
  • 2021-01-15
相关资源
最近更新 更多