用功率查询填补时间空白

时间:2019-01-22 17:20:28

标签: powerquery m

我有以下数据

   start        stop       status
+-----------+-----------+-----------+
| 09:01:10  | 09:01:40  |  active   |
| 09:02:30  | 09:04:50  |  active   |
| 09:10:01  | 09:11:50  |  active   |
+-----------+-----------+-----------+

我想用“被动”来填补空白

   start        stop       status
+-----------+-----------+-----------+
| 09:01:10  | 09:01:40  |  active   |
| 09:01:40  | 09:02:30  |  passive  |
| 09:02:30  | 09:04:50  |  active   |
| 09:04:50  | 09:10:01  |  passive  |
| 09:10:01  | 09:11:50  |  active   |
+-----------+-----------+-----------+

如何使用M Query语言执行此操作?

3 个答案:

答案 0 :(得分:2)

您可以尝试以下操作(我的前两个步骤someTablechangedTypes只是为了重新创建示例数据)

let
    someTable = Table.FromColumns({{"09:01:10", "09:02:30", "09:10:01"}, {"09:01:40", "09:04:50", "09:11:50"}, {"active", "active", "active"}}, {"start","stop","status"}),
    changedTypes = Table.TransformColumnTypes(someTable, {{"start", type duration}, {"stop", type duration}, {"status", type text}}),
    listOfRecords = Table.ToRecords(changedTypes),
    transformList = List.Accumulate(List.Skip(List.Positions(listOfRecords)), {listOfRecords{0}}, (listState, currentIndex) =>
        let
            previousRecord = listOfRecords{currentIndex-1},
            currentRecord = listOfRecords{currentIndex},
            thereIsAGap = currentRecord[start] <> previousRecord[stop],
            recordsToAdd = if thereIsAGap then {[start=previousRecord[stop], stop=currentRecord[start], status="passive"], currentRecord} else {currentRecord},
            append = listState & recordsToAdd
        in
            append
    ),
    backToTable = Table.FromRecords(transformList, type table [start=duration, stop=duration, status=text])
in
    backToTable

这就是我的出发点(在changedTypes步骤):

Input table

这就是我的最终结果:

Output table

要与您现有的M代码集成,您可能需要:

  • 从我的代码中删除someTablechangedTypes(并替换为您现有的查询)
  • changedTypes步骤中的listOfRecords更改为最后一步所调用的内容(否则,如果代码中没有changedTypes表达式,则会出现错误)。

编辑:

进一步回答,我的建议是:

尝试在上面的代码中更改此行:

listOfRecords = Table.ToRecords(changedTypes),

listOfRecords = List.Buffer(Table.ToRecords(changedTypes)),

我发现将列表存储在内存中会大大减少刷新时间(如果量化的话,刷新时间可能约为90%)。我认为存在限制和弊端(例如,如果列表不适合),但对于您的用例来说可能还可以。

Peformance graphed

您是否经历过类似的行为?另外,我的基本图表很不幸地表明了代码整体的非线性复杂性。

最后的注意:我发现在刷新查询时生成和处理10万行会导致堆栈溢出(这可能是由于生成了输入行,而且可能没有插入新行,所以不知道)。很明显,这种方法有局限性。

答案 1 :(得分:0)

我将按以下方式进行处理:

  1. 复制第一个表。
  2. 将“主动”替换为“被动”。
  3. 删除start列。
  4. stop重命名为start
  5. 通过查找原始表中最早的stop时间(当前时间start之后)来创建新的stop列。
  6. 在此新列中过滤出空值。
  7. 将此表追加到原始表中。

M代码如下所示:

let
    Source = <...your starting table...>
    PassiveStatus = Table.ReplaceValue(Source,"active","passive",Replacer.ReplaceText,{"status"}),
    RemoveStart = Table.RemoveColumns(PassiveStatus,{"start"}),
    RenameStart = Table.RenameColumns(RemoveStart,{{"stop", "start"}}),
    AddStop = Table.AddColumn(RenameStart, "stop", (C) => List.Min(List.Select(Source[start], each _ > C[start])), type time),
    RemoveNulls = Table.SelectRows(AddStop, each ([stop] <> null)),
    CombineTables = Table.Combine({Source, RemoveNulls}),
    #"Sorted Rows" = Table.Sort(CombineTables,{{"start", Order.Ascending}})
in
    #"Sorted Rows"

上面唯一棘手的地方是自定义列部分,我在其中定义了新列,如下所示:

(C) => List.Min(List.Select(Source[start], each _ > C[start]))

这将获取列/列表Source[start]中的每个项目,并将其与当前行中的时间进行比较。它只选择在当前行时间之后发生的事件,然后将最小值移到该列表中以找到最早的事件。

Result

答案 2 :(得分:0)

我认为我可能有一个更好的解决方案。

从源表(假设已排序)中,添加一个从0开始的索引列和一个从1开始的索引列,然后将表与其自身合并,对索引进行左外部联接列,然后展开start列。

Self Merge

删除stopstatusstart.1以外的列,并过滤出空值。

将列重命名为startstatusstop,并将"active"替换为"passive"

最后,将此表追加到原始表中。

let
    Source = Table.RenameColumns(#"Removed Columns",{{"Column1.2", "start"}, {"Column1.3", "stop"}, {"Column1.4", "status"}}),
    Add1Index = Table.AddIndexColumn(Source, "Index", 1, 1),
    Add0Index = Table.AddIndexColumn(Add1Index, "Index.1", 0, 1),
    SelfMerge = Table.NestedJoin(Add0Index,{"Index"},Add0Index,{"Index.1"},"Added Index1",JoinKind.LeftOuter),
    ExpandStart1 = Table.ExpandTableColumn(SelfMerge, "Added Index1", {"start"}, {"start.1"}),
    RemoveCols = Table.RemoveColumns(ExpandStart1,{"start", "Index", "Index.1"}),
    FilterNulls = Table.SelectRows(RemoveCols, each ([start.1] <> null)),
    RenameCols = Table.RenameColumns(FilterNulls,{{"stop", "start"}, {"start.1", "stop"}}),
    ActiveToPassive = Table.ReplaceValue(RenameCols,"active","passive",Replacer.ReplaceText,{"status"}),
    AppendQuery = Table.Combine({Source, ActiveToPassive}),
    #"Sorted Rows" = Table.Sort(AppendQuery,{{"start", Order.Ascending}})
in
    #"Sorted Rows"

这应该是 O n )复杂性,其逻辑与@chillin类似,但是我认为应该比使用自定义函数更快,因为它将使用内置函数-in合并可能会高度优化。