【问题标题】:Simulate LAG with GROUP BY in MySQL 5.6在 MySQL 5.6 中使用 GROUP BY 模拟 LAG
【发布时间】:2020-06-14 03:12:15
【问题描述】:

如何在 MySQL 5.6 中从 MySQL 8.0 模拟 LAG 函数,在其中我获得具有相同 ItemID 的先前信息。我创建了这个插图来模拟我需要的表和查询的输出。灰色的数据是原始表格,橙色是上一行的数据,具有相同的 ItemID 和最接近的上一个日期。

我尝试在 A.ItemID = B.ItemID AND B.Date

我也试过这个。

SET @lag = -1;
SELECT *, @lag PreviousInfo, @lag:=Info CurrentInfo FROM Table1

但是,这总是只返回上一行的信息,而不是按 ItemID 分组。

【问题讨论】:

    标签: mysql sql window-functions mysql-5.6


    【解决方案1】:

    您可以使用两个相关的子查询:

    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),尤其是在第一个查询中,因此两个子查询都被索引覆盖

    【讨论】:

    • 我已经设法使用两个变量 PreviousItemID 和 PreviousInfo 达到了预期的效果,然后我可以 SELECT IF(PreviousItemID = ItemID, PreviousInfo , -1) PreviousInfo, PreviousInfo:=Info CurrentInfo, PreviousItemID := ItemID CurrentItemID ORDER BY ItemID ASC, Date ASC。与您的联接和子查询解决方案相比,您知道使用 2 个变量的性能差异吗?
    • @TobiasKnudsen:是的,(有时)可以用用户变量模拟窗口函数。我不是这些解决方案的忠实拥护者,我发现它们更难读写,因此我提供了上述解决方案。您可能想针对您的真实数据集评估每个解决方案的性能(我也很想知道)。我在答案中添加了索引建议。
    • 好的,我在我的解决方案中发现了一个缺陷,我刚刚尝试实施您的解决方案。到目前为止,它运行良好。我有一个包含大约 23.000.000 行的数据库,并且通过子查询和连接,我能够在大约 1 秒内检索到数据。我正在考虑将列 previous_info 或者可能只是以前的 ID 添加到表结构中。这可能会加快加入速度。感谢您的帮助!
    【解决方案2】:

    在大型数据集上最有效的方法可能是使用变量,但您必须谨慎使用:

    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 express 来确保正确的评估顺序。

    您可能不需要 MySQL 5.6 中的子查询——外部查询中的 ORDER BY 可能会处理变量(在 5.6/5.7 左右的某个时间点停止工作)。

    【讨论】:

      猜你喜欢
      • 2019-03-31
      • 2011-10-28
      • 1970-01-01
      • 2011-12-15
      • 1970-01-01
      • 2017-07-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多