在 MySQL 5.6 中使用 GROUP BY 模拟 LAG
Simulate LAG with GROUP BY in MySQL 5.6
如何在 MySQL 5.6 中模拟 MySQL 8.0 中的 LAG 函数,在那里我获得具有相同 ItemID 的先前信息。我创建此插图是为了模拟 table 和我需要的查询输出。灰色数据是原始数据 table,橙色数据是前一行中具有相同 ItemID 和最近日期的数据。
我尝试在 A.ItemID = B.ItemID AND B.Date < A.Date 上自行加入 table。我也试过按 ItemID 分组,并尝试让最大日期低于当前日期。
我也试过了。
SET @lag = -1;
SELECT *, @lag PreviousInfo, @lag:=Info CurrentInfo FROM Table1
然而,这始终只是 returns 上一行的信息,并不按 ItemID 分组。
您可以使用两个相关的子查询:
select
t.*,
(
select date
from mytable t1
where t1.itemid = t.itemid and t1.date < t.date
order by t.date desc
limit 1
) previous_date,
(
select info
from mytable t1
where t1.itemid = t.itemid and t1.date < t.date
order by t.date desc
limit 1
) previous_info
from mytable t
然而,当您需要从以前的记录中恢复更多列时,这并不能很好地扩展。在这种情况下,我们可以使用 not exists
条件进行自连接以过滤上一条记录:
select
t.*,
tlag.date previous_date,
tlag.info previous_info
from mytable t
left join mytable tlag
on tlag.itemid = t.itemid
and tlag.date < t.date
and not exists (
select 1
from mytable t1
where t1.itemid = t.itemid and t1.date < t.date and t1.date > tlag.date
)
对于两个查询的性能,请考虑 (item_id, date)
上的以下索引。您可能希望将 info
添加到索引中,例如:(item_id, date, info)
,尤其是对于第一个查询,因此两个子查询都被索引 覆盖。
在大型数据集上最有效的方法可能是使用变量,但您必须谨慎使用:
SELECT t1.*,
(case when (@i <> itemid)
then (case when (@prevd := date) = null -- never happens
else null
end)
when (@stash := @prevd) = null -- never happens
then null
when (@prevd := date) = null -- never happens
then null
else @x
end) as prev_d
FROM (SELECT t1.*
FROM Table1 t1
ORDER BY itemid, date
) t1 CROSS JOIN
(SELECT @i := -1, @prevd := null) params;
变量的使用非常棘手,因为 MySQL 不能保证 SELECT
中表达式的计算顺序。因此,这使用 CASE
表达式来确保正确的评估顺序。
您可能不需要 MySQL 5.6 中的子查询 -- 外部查询中的 ORDER BY
可能会处理变量(在 5.6/5.7 左右的某个时候它停止工作)。
如何在 MySQL 5.6 中模拟 MySQL 8.0 中的 LAG 函数,在那里我获得具有相同 ItemID 的先前信息。我创建此插图是为了模拟 table 和我需要的查询输出。灰色数据是原始数据 table,橙色数据是前一行中具有相同 ItemID 和最近日期的数据。
我尝试在 A.ItemID = B.ItemID AND B.Date < A.Date 上自行加入 table。我也试过按 ItemID 分组,并尝试让最大日期低于当前日期。
我也试过了。
SET @lag = -1;
SELECT *, @lag PreviousInfo, @lag:=Info CurrentInfo FROM Table1
然而,这始终只是 returns 上一行的信息,并不按 ItemID 分组。
您可以使用两个相关的子查询:
select
t.*,
(
select date
from mytable t1
where t1.itemid = t.itemid and t1.date < t.date
order by t.date desc
limit 1
) previous_date,
(
select info
from mytable t1
where t1.itemid = t.itemid and t1.date < t.date
order by t.date desc
limit 1
) previous_info
from mytable t
然而,当您需要从以前的记录中恢复更多列时,这并不能很好地扩展。在这种情况下,我们可以使用 not exists
条件进行自连接以过滤上一条记录:
select
t.*,
tlag.date previous_date,
tlag.info previous_info
from mytable t
left join mytable tlag
on tlag.itemid = t.itemid
and tlag.date < t.date
and not exists (
select 1
from mytable t1
where t1.itemid = t.itemid and t1.date < t.date and t1.date > tlag.date
)
对于两个查询的性能,请考虑 (item_id, date)
上的以下索引。您可能希望将 info
添加到索引中,例如:(item_id, date, info)
,尤其是对于第一个查询,因此两个子查询都被索引 覆盖。
在大型数据集上最有效的方法可能是使用变量,但您必须谨慎使用:
SELECT t1.*,
(case when (@i <> itemid)
then (case when (@prevd := date) = null -- never happens
else null
end)
when (@stash := @prevd) = null -- never happens
then null
when (@prevd := date) = null -- never happens
then null
else @x
end) as prev_d
FROM (SELECT t1.*
FROM Table1 t1
ORDER BY itemid, date
) t1 CROSS JOIN
(SELECT @i := -1, @prevd := null) params;
变量的使用非常棘手,因为 MySQL 不能保证 SELECT
中表达式的计算顺序。因此,这使用 CASE
表达式来确保正确的评估顺序。
您可能不需要 MySQL 5.6 中的子查询 -- 外部查询中的 ORDER BY
可能会处理变量(在 5.6/5.7 左右的某个时候它停止工作)。