我有以下数据
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语言执行此操作?
答案 0 :(得分:2)
您可以尝试以下操作(我的前两个步骤someTable
和changedTypes
只是为了重新创建示例数据)
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
代码集成,您可能需要:
someTable
和changedTypes
(并替换为您现有的查询)changedTypes
步骤中的listOfRecords
更改为最后一步所调用的内容(否则,如果代码中没有changedTypes
表达式,则会出现错误)。 编辑:
进一步回答,我的建议是:
尝试在上面的代码中更改此行:
listOfRecords = Table.ToRecords(changedTypes),
到
listOfRecords = List.Buffer(Table.ToRecords(changedTypes)),
我发现将列表存储在内存中会大大减少刷新时间(如果量化的话,刷新时间可能约为90%)。我认为存在限制和弊端(例如,如果列表不适合),但对于您的用例来说可能还可以。
您是否经历过类似的行为?另外,我的基本图表很不幸地表明了代码整体的非线性复杂性。
最后的注意:我发现在刷新查询时生成和处理10万行会导致堆栈溢出(这可能是由于生成了输入行,而且可能没有插入新行,所以不知道)。很明显,这种方法有局限性。
答案 1 :(得分:0)
我将按以下方式进行处理:
start
列。stop
重命名为start
。stop
时间(当前时间start
之后)来创建新的stop
列。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]
中的每个项目,并将其与当前行中的时间进行比较。它只选择在当前行时间之后发生的事件,然后将最小值移到该列表中以找到最早的事件。
答案 2 :(得分:0)
我认为我可能有一个更好的解决方案。
从源表(假设已排序)中,添加一个从0
开始的索引列和一个从1
开始的索引列,然后将表与其自身合并,对索引进行左外部联接列,然后展开start
列。
删除stop
,status
和start.1
以外的列,并过滤出空值。
将列重命名为start
,status
和stop
,并将"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合并可能会高度优化。