用权力查询填补时间空白

fill time gaps with power query

我有以下数据

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

我想用"passive"

填补空白
   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 查询语言执行此操作?

您可以尝试如下操作(我的前两个步骤 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 步骤):

这就是我的结局:

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

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

编辑:

除了我的回答,我的建议是:

尝试更改上面代码中的这一行:

listOfRecords = Table.ToRecords(changedTypes),

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

我发现将列表存储在内存中显着减少了我的刷新时间(如果量化的话可能约为 90%)。我认为存在限制和缺点(例如,如果列表不适合),但对于您的用例可能没问题。

您是否遇到过类似行为?此外,不幸的是,我的基本图表表明代码总体上是非线性的复杂性。

最后说明:我发现生成和处理 100k 行导致堆栈溢出,同时刷新查询(这可能是由于生成输入行而不是插入新行,不知道).很明显,这种方法有局限性。

我会按如下方式处理:

  1. 复制第一个 table。
  2. 将"active"替换为"passive"。
  3. 删除 start 列。
  4. stop 重命名为 start
  5. 通过查找原始 table 中当前 stop 时间之后发生的最早 start 时间来创建新的 stop 列。
  6. 在此新列中过滤掉空值。
  7. 将此 table 附加到原始 table。

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]))

这会获取 column/list Source[start] 中的每个项目并将其与当前行中的时间进行比较。它只选择当前行中时间之后出现的那些,然后对该列表进行最小化以找到最早的一个。

我想我可能有更好的解决方案。

从您的来源 table(假设它已排序),添加一个从 0 开始的索引列和一个从 1 开始的索引列,然后合并 table本身在索引列上进行左外连接并展开 start 列。

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

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

最后,将此 table 附加到您原来的 table。

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 相似,但我认为应该比使用自定义更快功能,因为它将使用内置的合并,这可能是高度优化的。