Я пытаюсь рассчитать, как долго элемент был активен в определенном состоянии с помощью KQL. В настоящее время я использую оконные функции вместе с секционированием. Это хорошо работает, когда мне нужно вычислить общее «время в состоянии» за определенный период, но проблема возникает при попытке разбить его на день (или любую произвольную степень детализации).
Вход:
let inputData=datatable(id:string, status: string, timestamp: datetime) [
"id1","P",datetime(2024-03-12T05:30:15),
"id1","F",datetime(2024-03-14T10:10:00),
"id2","P",datetime(2024-03-12T05:30:15)
];
let startDate=datetime(2024-03-12T00:00:00);
let endDate=datetime(2024-03-15T00:00:00);
Применение следующего запроса дает общее время, которое каждый идентификатор провел в каждом состоянии:
inputData
| partition hint.strategy=native by id
(
order by timestamp asc
| extend tsDiff = min_of(endDate, next(timestamp)) - timestamp
| extend pTime = iif (status == "P", tsDiff, timespan(0))
| extend fTime = iif (status == "F", tsDiff, timespan(0))
)
| summarize totalPTime=sum(pTime), totalFTime=sum(fTime) by id
Results:
id totalPTime totalFTime
id1 2.04:39:45 13:50:00
id2 2.18:29:45 00:00:00
Теперь я в тупике, как разбить это на повседневную жизнь. Попытка использовать, например, make-series дает:
inputData
| partition hint.strategy=native by id
(
order by timestamp asc
| extend tsDiff = min_of(endDate, next(timestamp)) - timestamp
| extend pTime = iif (status == "P", tsDiff, timespan(0))
| extend fTime = iif (status == "F", tsDiff, timespan(0))
)
| make-series
totalP=sum(pTime),
totalF=sum(fTime) on timestamp from startDate to endDate step 1d by id
Results:
id totalP totalF timestamp
id1 ["2.04:39:45","00:00:00","00:00:00"] ["00:00:00","00:00:00","13:50:00"] ["2024-03-12","2024-03-13","2024-03-14"]
id2 ["2.18:29:45","00:00:00","00:00:00"] ["00:00:00","00:00:00","00:00:00"] ["2024-03-12","2024-03-13","2024-03-14"]
Мне нужны результаты максимум за 1 день и переполнение на следующий день, если это имеет смысл. Например:
id totalP totalF timestamp
id1 ["1.00:00:00","1.00:00:00","04:39:45"] ["00:00:00","00:00:00","13:50:00"] ["2024-03-12","2024-03-13","2024-03-14"]
id2 ["1.00:00:00","1.00:00:00","18:29:45"] ["00:00:00","00:00:00","00:00:00"] ["2024-03-12","2024-03-13","2024-03-14"]
Нахожусь ли я на правильном пути или мне следует использовать для этой цели какую-то другую функциональность? Любая помощь очень ценится!
Вы можете использовать оператор mv-apply для добавления записей на каждый день:
inputData
| partition hint.strategy=native by id
(
order by timestamp asc
| extend dur = next(timestamp, 1, endDate) - timestamp
| mv-apply td = range(0d, dur, 1d) to typeof(timespan) on (
extend tsDiff = iff(dur - td >= 1d, 1d, dur - td)
| extend timestamp = timestamp + td
)
| extend pTime = iif (status == "P", tsDiff, timespan(0))
| extend fTime = iif (status == "F", tsDiff, timespan(0))
)
| make-series
totalP=sum(pTime),
totalF=sum(fTime) on timestamp from startDate to endDate step 1d by id
Я просто хотел сказать спасибо за такой интересный вопрос, он занимал мои мысли почти все выходные. На первый взгляд это казалось вполне логичным, но чем больше я об этом думал и тестировал код, тем сложнее это становилось! Я уверен, что вы были в точно таком же положении...
Я уверен, что придет кто-то другой и предложит вам гораздо более красноречивое и эффективное в вычислительном отношении решение, но я верю, что вы находитесь на правильном пути в том, что делаете. Чего, похоже, не хватает, так это того, что, как и большинство языков запросов, KQL не умеет показывать то, чего нет. Поэтому я лично предпочитаю всегда начинать с того, что мы знаем. Для вашего вопроса я взял уже имеющиеся у вас временные диапазоны.
Здесь имеется большое количество разделов (как вы использовали), диапазона , сканирования и динамических переменных.
У этого решения есть некоторые предостережения, а именно: в его текущем формате оно имеет точность только до минуты, нам может не понадобиться вычислять окончательный статус, и я делаю вывод, что статус всегда может быть только P или F.
Я знаю, что я не рассчитываю время так же, как ваш запрос, но, надеюсь, вы сможете настроить его по мере необходимости.
let inputData=datatable(id:string, status:string, timestamp: datetime) [
"id1","P",datetime(2024-03-12T05:30:15),
"id1","F",datetime(2024-03-14T10:10:00),
"id2","P",datetime(2024-03-12T05:30:15)
];
let units = 1m; //eg 1 minute calculating
let startDate = floor(datetime(2024-03-12T00:00:00), units);
let endDate = floor(datetime(2024-03-15T03:00:00), units);
range timestamp from startDate to endDate step units
| extend JoinHere = 1
| join kind=leftouter (inputData | summarize by id, JoinHere = 1) on JoinHere //Create an entry for every possible timestamp and id
| join kind=leftouter (inputData
| summarize arg_min(timestamp, status) by id //Get the first change
| extend status = iif (status == 'P', 'F', 'P') //Assume prior to this it was on the other status
| extend timestamp = startDate //Start it at the beginning of our window
) on timestamp, id
| join kind=leftouter (inputData
| summarize arg_max(timestamp, status) by id //Get the last change
| extend timestamp = endDate //End it at the end of the window
) on timestamp, id
| order by timestamp asc
| join kind=leftouter (inputData | extend timestamp = floor(timestamp, units)) on timestamp, id //Mix in our known changes
| extend status = coalesce(status, status1, status2) //From all the joins have a single value for status
| project timestamp, id, status
| partition hint.strategy=native by id
(
sort by timestamp asc
| scan declare (status:string) with
(
step Step1: true => status = iif (isempty(status), Step1.status, status);
)
)
| partition hint.strategy=native by id
(
summarize TimeUnits = array_length(make_set(timestamp)) by floor(timestamp, 1d), status, id
| extend pTime = iif (status == "P", TimeUnits, 0) //Unit in this case is 1 minute as above
| extend fTime = iif (status == "F", TimeUnits, 0)
)
| make-series
totalP=sum(pTime),
totalF=sum(fTime) on timestamp from startDate to endDate step 1d by id
Спасибо за ответ. Это решение выглядит очень элегантно, но оно сталкивается с проблемами в зависимости от входных данных. Возможно, данные моего примера не были оптимальными в этом вопросе, но учтите следующее:
"id1","P",datetime(2024-03-12T00:00:00), "id1","F",datetime(2024-03-12T02:00:00), "id1","P",datetime(2024-03-12T03:00:00),
Доходность:totalP: ["1.02:00:00","1.00:00:00","21:00:00"]