电源查询使用多个条件从上面的行中减去下面的行

power query subtract row below from row above using multiple conditions

我在 Excel 中使用 Power Query,我需要使用每个用户每天的时间列计算每个 "Door_side" 的持续时间。

数据来自基于卡的门禁系统,格式如下:

Date  Time   User_No   Door_side
03/12  08:59   User_05   Outside
03/12  09:00   User_33   Inside
03/12  09:01   User_10   Outside
03/12  09:01   User_04   Outside
03/12  09:02   User_26   Outside
03/12  09:03   User_19   Outside
03/12  09:03   User_15   Inside
03/12  09:04   User_31   Inside
03/12  09:05   User_31   Outside
03/12  09:06   User_15   Outside
03/12  09:06   User_06   Inside
03/12  09:06   User_06   Inside
03/12  09:06   User_06   Inside
03/12  09:08   User_32   Outside
03/12  09:09   User_10   Inside
03/12  09:09   User_13   Inside
03/12  09:10   User_10   Outside

我尝试了以下方法:

  1. 按日期、用户和时间对行进行排序;
  2. 添加了索引列;
  3. 创建了名为 PreviousTime;
  4. 的自定义列
  5. 计算的持续时间(时间 - 前一时间)。

上述步骤的完整代码是:

    let
    Source = Table,
     #"Sorted Rows" = Table.Sort(Source,{{"Date", Order.Ascending}, {"User_No", Order.Ascending}, {"Time", Order.Ascending}}),
    #"Added Index" = Table.AddIndexColumn(#"Sorted Rows", "Index", 0, 1),
    #"Added Custom" = Table.AddColumn(#"Added Index", "PreviousTime", each try 
if List.AllTrue(
{[User_No]=#"Added Index"[User_No]{[Index]-1},[Date]=#"Added Index"[Date]{[Index]-1}
}
)
then try #"Added Index"[Time]{[Index]-1} otherwise [Time]
else [Time]
otherwise [Time]),
    Duration = Table.AddColumn(#"Added Custom", "Duration", each [Time] - [PreviousTime], type duration)
in
    Duration

这适用于小数据集,但会导致功能问题,并且在处理大量数据时完全失败。 我对 Power Query 和 M 还很陌生,所以我无法弄清楚自定义列公式中到底是什么导致了问题或如何以其他方式解决这个问题。

我试图将上述代码保留为查询的一部分,并将其用作函数,但这两种方法在功能方面并没有太大区别。 处理后的 table 将被发送到数据模型,但我希望在 Power Query 而不是 Power Pivot 中获得持续时间。 非常感谢您!


为了更详细地说明任务,我上传了 12 月份 3 个用户的缩减版数据。您可以在这里找到它:https://1drv.ms/x/s!AocQlL_KAzymgwhqiKxSL5JMZheL.

我想实现的是根据用户和日期计算时间戳之间的持续时间。 作为一个加号,我没有用户在午夜之后工作,因此特定班次的所有时间戳都在同一日期内。

也可以在工作簿中找到所需结果的示例,如下所示(在 Excel 中计算):

Date    Time    User    Door_side    Duration
03/12   06:54   User_1  Outside 
03/12   07:26   User_1  Inside    00:32:00
03/12   07:27   User_1  Outside   00:01:00
03/12   07:44   User_1  Inside    00:17:00
03/12   07:52   User_1  Outside   00:08:00
03/12   08:35   User_1  Inside    00:43:00
03/12   08:36   User_1  Outside   00:01:00
03/12   11:50   User_1  Inside    03:14:00
03/12   12:01   User_1  Outside   00:11:00
03/12   13:27   User_1  Inside    01:26:00
03/12   13:43   User_1  Outside   00:16:00
03/12   14:57   User_1  Inside    01:14:00
03/12   15:20   User_1  Inside    00:23:00
03/12   15:26   User_1  Outside   00:06:00
03/12   15:34   User_1  Inside    00:08:00

因为数据包含所有用户和多天,我试图在按日期和用户分组的 table 内进行计算。


我花了一些时间测试下面介绍的所有 3 种方法(List.Min、Table.FirstN 和嵌套 tables),并且在有限的数据集上它们都做得很好。

然而,当应用于更大的数据集时(我有大约 20000 行 1 个月)嵌套 tables 方法似乎是最快的。

感谢 Eugene 和 Marc 的帮助,更重要的是,感谢他们教了我一些新知识。

如果我没看错你的任务,你需要下一个事件发生的时间,假设这是门关闭的时间。 在这种情况下,我强烈建议您避免使用索引。相反,我建议您考虑如何应用行选择过程来满足您对每一行的需求。

如果我对您的任务的理解是正确的,那么我认为应该这样做:

let
    Source = Excel.CurrentWorkbook(){[Name="Data"]}[Content],
    SplitDateTime = Table.SplitColumn(Table.TransformColumnTypes(Source, {{"Booking time", type text}}, "en-GB"), "Booking time", Splitter.SplitTextByDelimiter(" ", QuoteStyle.Csv), {"Date", "Time"}),
    FilteredDoorside = Table.SelectRows(SplitDateTime, each ([Doorside] <> "-")),
    ChangedType = Table.Buffer(Table.TransformColumnTypes(FilteredDoorside,{{"Date", type date}, {"Time", type time}, {"User", type text}, {"Doorside", type text}})),
    GetCloseTime = Table.AddColumn(ChangedType, "Duration", (row)=>List.Min(Table.SelectRows(ChangedType, each [Date]=row[Date] and [Time]>row[Time])[Time]) - row[Time]),
    SetType = Table.TransformColumnTypes(GetCloseTime,{{"Duration", type duration}})
in
    SetType

GetCloseTime 步骤中,我添加了函数列,它从 table self 中选择具有相同日期但时间较晚的行,然后选择最短时间。这将是下一个活动时间。如果需要,您可以添加其他条件。

另一种方法是使用 List.Min 进行排序派生 table 并获取其第一行和时间列中的值:{0}[Time]

let
    Source = Excel.CurrentWorkbook(){[Name="Data"]}[Content],
    SplitDateTime = Table.SplitColumn(Table.TransformColumnTypes(Source, {{"Booking time", type text}}, "en-GB"), "Booking time", Splitter.SplitTextByDelimiter(" ", QuoteStyle.Csv), {"Date", "Time"}),
    FilteredDoorside = Table.SelectRows(SplitDateTime, each ([Doorside] <> "-")),
    ChangedType = Table.Buffer(Table.TransformColumnTypes(FilteredDoorside,{{"Date", type date}, {"Time", type time}, {"User", type text}, {"Doorside", type text}})),
    GetCloseTime = Table.AddColumn(ChangedType, "Duration", (row)=>Table.FirstN(Table.Sort(Table.SelectRows(ChangedType, each [Date]=row[Date] and [Time]>row[Time]),{{"Time", Order.Ascending}}),1){0}[Time] - row[Time]),
    SetType = Table.TransformColumnTypes(GetCloseTime,{{"Duration", type duration}})
in
    SetType

这是一种不同的方法。它依赖于在嵌套 tables.

中工作

我从你的电子表格中的数据开始,在一个名为 Table1 的 table 中:

在 Power Query 中,使用 Table1 作为源,我拆分了“预订时间”列,重命名了生成的日期和时间列,过滤掉了 - Doorside 条目,并根据您的指导进行了排序:

然后我按预订日期和用户分组:

然后我在每个嵌套的 table 中添加了一个索引列,在一个新的自定义列中:

然后我在每个嵌套的 table 中添加了一个新列,其中包含以前的时间,在一个新的自定义列中: (你在这里看到的错误是因为没有以前的时间。)

然后我在每个嵌套的 table 中的新自定义列中添加了一个新列,其中包含我添加上一个日期时的错误更正。 我想我会 "correct" 没有以前的时间造成的,通过用 "current" 预订时间替换错误,这将导致持续时间为零

然后我在一个新的自定义列中添加了一个新列,其中包含在每个嵌套 table 中计算的持续时间:

然后我删除了所有列,除了我添加的最后一列,我称之为 AddDuration:

然后我扩展了 AddDuration 列:

这是我的 M 代码:

let
Source = Excel.CurrentWorkbook(){[Name="Table1"]}[Content],
#"Split Column by Delimiter" = Table.SplitColumn(Table.TransformColumnTypes(Source, {{"Booking time", type text}}, "en-US"), "Booking time", Splitter.SplitTextByEachDelimiter({" "}, QuoteStyle.Csv, false), {"Booking time.1", "Booking time.2"}),
#"Renamed Columns" = Table.RenameColumns(#"Split Column by Delimiter",{{"Booking time.1", "Booking Date"}, {"Booking time.2", "Booking Time"}}),
#"Changed Type" = Table.TransformColumnTypes(#"Renamed Columns",{{"Booking Date", type date}, {"Booking Time", type time}}),
#"Filtered Rows" = Table.SelectRows(#"Changed Type", each ([Doorside] <> "-")),
#"Sorted Rows" = Table.Sort(#"Filtered Rows",{{"Booking Date", Order.Ascending}, {"User", Order.Ascending}, {"Booking Time", Order.Ascending}}),
#"Grouped Rows" = Table.Group(#"Sorted Rows", {"Booking Date", "User"}, {{"AllData", each _, type table}}),
#"Added Custom" = Table.AddColumn(#"Grouped Rows", "AddIndex", each Table.AddIndexColumn([AllData],"Index",0,1)),
#"Added Custom1" = Table.AddColumn(#"Added Custom", "AddPreviousTime", each let tblName = [AddIndex] in Table.AddColumn([AddIndex],"Previous Time",each tblName{[Index]-1}[Booking Time], type time)),
#"Added Custom2" = Table.AddColumn(#"Added Custom1", "CorrectErrors", each Table.ReplaceErrorValues([AddPreviousTime], {{"Previous Time", [AddPreviousTime][Booking Time]{0}}})),
#"Added Custom3" = Table.AddColumn(#"Added Custom2", "AddDuration", each Table.AddColumn([CorrectErrors],"Duration", each [Booking Time] - [Previous Time], type duration)),
#"Removed Other Columns" = Table.SelectColumns(#"Added Custom3",{"AddDuration"}),
#"Expanded AddDuration" = Table.ExpandTableColumn(#"Removed Other Columns", "AddDuration", {"Booking Date", "Booking Time", "User", "Doorside", "Index", "Previous Time", "Duration"}, {"Booking Date", "Booking Time", "User", "Doorside", "Index", "Previous Time", "Duration"})
in
#"Expanded AddDuration"