在 MySQL 中模拟滞后函数
Posted
技术标签:
【中文标题】在 MySQL 中模拟滞后函数【英文标题】:Simulate lag function in MySQL 【发布时间】:2012-07-03 10:47:01 【问题描述】:| time | company | quote |
+---------------------+---------+-------+
| 0000-00-00 00:00:00 | GOOGLE | 40 |
| 2012-07-02 21:28:05 | GOOGLE | 60 |
| 2012-07-02 21:28:51 | SAP | 60 |
| 2012-07-02 21:29:05 | SAP | 20 |
如何在 mysql 中对这个表做一个滞后以打印引号中的差异,例如:
GOOGLE | 20
SAP | 40
【问题讨论】:
每个company
只有两个吗?还是可变的?
我看到你这里有两家公司,但每家公司只有两行吗?如果是这样,您可以轻松使用 MAX() - MIN()
聚合。如果每个company
有2行以上,那就更复杂了。
我只需要最新的两个时间戳。可能同一家公司有很多条目,但我只需要获取最新的两个时间戳并打印报价差异
如果一家公司仅由一行表示,您想在结果中返回该公司吗?如果是这样,应该返回什么差异?
在您的示例中,为什么其中一家公司的结果不是负面的?谷歌从 40 上升到 60,而 SAP 从 60 下降到 20。sqlfiddle.com/#!2/b62e1/1/0 或者你只想要绝对运动而不考虑方向(在这种情况下采用ABS(delta)
)?
【参考方案1】:
这是我最喜欢的 MySQL hack。
这是您模拟滞后功能的方式:
SET @quot=-1;
select time,company,@quot lag_quote, @quot:=quote curr_quote
from stocks order by company,time;
lag_quote
保存前一行引用的值。对于第一行,@quot 是 -1。
curr_quote
保存当前行引用的值。
注意事项:
order by
子句在这里很重要,就像它在常规中一样
窗口功能。
您可能还想对company
使用延迟,以确保您计算的是同一company
的引号中的差异。
你也可以用同样的方式实现行计数器@cnt:=@cnt+1
与使用聚合函数、存储过程或在应用服务器中处理数据等其他一些方法相比,该方案的优点在于计算上非常精简。
编辑:
现在以您提到的格式获取结果的问题:
SET @quot=0,@latest=0,company='';
select B.* from (
select A.time,A.change,IF(@comp<>A.company,1,0) as LATEST,@comp:=A.company as company from (
select time,company,quote-@quot as change, @quot:=quote curr_quote
from stocks order by company,time) A
order by company,time desc) B where B.LATEST=1;
嵌套并不相关,因此(计算上)不像看起来(语法上)那么糟糕:)
如果您需要任何帮助,请告诉我。
【讨论】:
我收到错误消息。 MySQL 的查询面板中不允许使用 DDL 和 DML 语句;只允许 SELECT 语句。将 DDL 和 DML 放在架构面板中。 虽然错误并未表明这一点,但请尝试启用“allowMultiQueries”。这是一个连接器参数。对于 JDBC 连接器,请参阅:dev.mysql.com/doc/refman/5.1/en/…。你能从 MySQL 客户端成功运行它吗? 您也可以尝试在同一个会话中单独执行这两个语句。 @javanx 你好,我是 SQL Fiddle 的作者。您提到的错误消息实际上是我处理某些类型的 MySQL 查询的方式中的一个错误。多亏了您在此处的消息,我才意识到这一点,并制定了修复它的解决方案(请参见此处,例如:sqlfiddle.com/#!2/4f8a1/2)。谢谢! 注意 您必须使变量的数据类型与您尝试滞后的数据类型保持一致。 EG,如果你想累积一个FLOAT字段,你必须将你的变量初始化为@quot=0.0
,否则将不起作用。我花了一分钟才弄清楚这一点!【参考方案2】:
从 MySQL 8.0 及以上版本无需模拟LAG
。它是本机支持的,
Window Function :
从在其分区内滞后(先于)当前行 N 行的行返回 expr 的值。如果没有这样的行,则返回值为默认值。例如,如果 N 为 3,则返回值为前两行的默认值。如果缺少 N 或默认值,则默认值分别为 1 和 NULL。
SELECT
company,
quote,
LAG(quote) OVER(PARTITION BY company ORDER BY time) AS prev_quote
FROM tab;
DBFiddle Demo
【讨论】:
【参考方案3】:要达到预期的结果,首先您需要找到每家公司的最后一个时间戳和倒数第二个时间戳。使用以下查询非常简单:
SELECT c.company, c.mts, max(l.ts) AS lts
FROM (SELECT company, max(ts) AS mts FROM cq GROUP BY company) AS c
LEFT JOIN cq l
ON c.company = l.company AND c.mts > l.ts
GROUP BY c.company, c.mts;
现在您必须将此子查询与原始表连接起来才能获得所需的结果:
SELECT c.company, l.quote, coalesce(l1.quote, 0),
(l.quote - coalesce(l1.quote, 0)) AS result
FROM (SELECT c.company, c.mts, max(l.ts) AS lts
FROM (SELECT company, max(ts) AS mts FROM cq GROUP BY company) AS c
LEFT JOIN cq l
ON c.company = l.company AND c.mts > l.ts
GROUP BY c.company, c.mts) AS c
LEFT JOIN cq AS l ON l.company = c.company AND l.ts = c.mts
LEFT JOIN cq AS l1 ON l1.company = c.company AND l1.ts = c.lts;
您可以在SQL Fiddle 上观察结果。
此查询仅使用标准 SQL 功能,应该适用于任何 RDBMS。
【讨论】:
很好的答案,特别是对于准备技术面试的人,因为他们经常不鼓励使用窗口函数。以上是关于在 MySQL 中模拟滞后函数的主要内容,如果未能解决你的问题,请参考以下文章
Android 模拟器(来自 Android Studio)滞后