我有具有这种结构的用户和订单表(简化了问题):
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号),而不添加很多示例数据记录。
不需要一个请求-如果不能在一个请求中完成该请求,则几乎没有可能。
答案 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。这将为您提供从选择中选择的选择,但是应该更快。这里的困难是控制行的顺序,所以这是我设想的选择:
仅当您需要在一个请求中完成所有操作时才需要进行联合(您必须使用比其所使用的选项低的选择来初始化它们)。
编辑:好吧,如果您也需要日期,则可以将其与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中的分页方法派生的。如果您想将此方法应用于其他引擎,只需检查一下它们的最佳分页调用,您就可以思考。