Kusto Detective Agency, Season 2, Onboarding

It’s time for season 2 of Kusto Detective Agency, a fun way to test and hone your Kusto Query Language (KQL) skills!

The first mission of season 2 (Onboarding) asks you to find the id of the detective that earned the most money in 2022, based on the data present in the DetectiveCases table.

After injecting the data, we can take a quick look at the table (I’ve edited the timestamps to reduce the width of the table).

DetectiveCases
| take 10
TimestampEventTypeDetectiveIdCaseIdProperties
2022-06-05 04:52:00CaseAssignedkvc39927c2b59a3c5b70d1CASE_0515946
2022-06-05 04:52:00CaseUnsolvedkvc78dac427567a91ce316CASE_0462853
2022-06-05 04:52:00CaseUnsolvedkvc6e5f6a0b5b659238109CASE_0477589
2022-06-05 04:52:00CaseUnsolvedkvc424fe2f4d2b5b501400CASE_0465830
2022-06-05 04:52:00CaseUnsolvedkvc41b3343db2fd4bcb71fCASE_0458904
2022-06-05 04:53:00CaseOpenedCASE_0521475{“Bounty”:6167}
2022-06-05 04:53:00CaseUnsolvedkvc7324095bda78cf91517CASE_0477512
2022-06-05 04:53:00CaseUnsolvedkvc658c44632d1f2d525dbCASE_0455097
2022-06-05 04:53:00CaseAssignedkvc33a2c659ab5f725d12bCASE_0518729
2022-06-05 04:53:00CaseUnsolvedkvc6bf01a86d80faf6a3cfCASE_0470580
Table resulting from the query above.

So there are events associated to detectives and cases. What kind of events are there?

DetectiveCases
| distinct EventType
CaseAssigned
CaseUnsolved
CaseSolved
CaseOpened
Table resulting from the query above.

The CaseId seems to be common to all records, so let’s take a look at one example.

TimestampEventTypeDetectiveIdProperties
2022-06-03 08:17:00CaseOpened {“Bounty”:5450}
2022-06-04 02:06:00CaseAssignedkvc1487d972b63c7fbf7b5
2022-06-04 04:42:00CaseAssignedkvc27ae220a01d416e70f9
2022-06-04 21:14:00CaseAssignedkvc169b30a1b5e4104e02a
2022-06-05 04:52:00CaseAssignedkvc39927c2b59a3c5b70d1
2022-06-05 06:30:00CaseAssignedkvc57d800cd46e28013346
2022-06-05 09:48:00CaseAssignedkvc6b174d5b26dcaad80c9
2022-06-05 19:45:00CaseAssignedkvc56118f70631428091df
2022-06-06 06:51:00CaseAssignedkvc1b0d48165cdaf5c22b2
2022-06-06 14:46:00CaseAssignedkvc733f22c0f597958f303
2022-06-06 14:52:00CaseAssignedkvc7bbe4bf4f5807f986b0
2022-06-06 20:43:00CaseAssignedkvc56a9431b61632d5e0c7
2022-06-07 09:18:00CaseAssignedkvc77df12ed3ce726cc202
2022-06-07 20:39:00CaseAssignedkvc29d392ca965f09646f8
2022-06-08 06:03:00CaseAssignedkvc5c8c994dac519e175a2
2022-06-08 23:08:00CaseAssignedkvc0242571a3473d65b591
2022-06-09 00:48:00CaseAssignedkvc17b66524120a286000b
2022-06-09 08:27:00CaseAssignedkvc066be95ff4bc08fa532
2022-06-09 16:48:00CaseAssignedkvc03ccfc7926d25469596
2022-06-09 19:06:00CaseAssignedkvc123e17dd44ef7d5c581
2022-06-09 19:44:00CaseAssignedkvc75edecf777cd1e63402
2022-06-09 21:25:00CaseAssignedkvc5800cf32381e9ba41b9
2022-06-10 04:14:00CaseAssignedkvc7556ead7abedba2970e
2022-06-10 09:19:00CaseAssignedkvc6b522887583e178d3a5
2022-06-10 18:13:00CaseAssignedkvc2a5aa0209a24ab3c04a
2022-06-10 19:44:00CaseSolvedkvc17b66524120a286000b
2022-06-11 13:05:00CaseSolvedkvc1b0d48165cdaf5c22b2
2022-06-13 12:38:00CaseSolvedkvc066be95ff4bc08fa532
2022-06-14 11:43:00CaseSolvedkvc6b522887583e178d3a5
2022-06-16 17:14:00CaseSolvedkvc733f22c0f597958f303
2022-06-17 06:29:00CaseSolvedkvc5c8c994dac519e175a2
2022-06-18 02:07:00CaseUnsolvedkvc1487d972b63c7fbf7b5
2022-06-18 04:43:00CaseUnsolvedkvc27ae220a01d416e70f9
2022-06-18 21:15:00CaseUnsolvedkvc169b30a1b5e4104e02a
2022-06-19 04:53:00CaseUnsolvedkvc39927c2b59a3c5b70d1
2022-06-19 06:31:00CaseUnsolvedkvc57d800cd46e28013346
2022-06-19 09:49:00CaseUnsolvedkvc6b174d5b26dcaad80c9
2022-06-19 19:46:00CaseUnsolvedkvc56118f70631428091df
2022-06-20 14:53:00CaseUnsolvedkvc7bbe4bf4f5807f986b0
2022-06-20 20:44:00CaseUnsolvedkvc56a9431b61632d5e0c7
2022-06-21 09:19:00CaseUnsolvedkvc77df12ed3ce726cc202
2022-06-21 20:40:00CaseUnsolvedkvc29d392ca965f09646f8
2022-06-22 23:09:00CaseUnsolvedkvc0242571a3473d65b591
2022-06-23 16:49:00CaseUnsolvedkvc03ccfc7926d25469596
2022-06-23 19:07:00CaseUnsolvedkvc123e17dd44ef7d5c581
2022-06-23 19:45:00CaseUnsolvedkvc75edecf777cd1e63402
2022-06-23 21:26:00CaseUnsolvedkvc5800cf32381e9ba41b9
2022-06-24 04:15:00CaseUnsolvedkvc7556ead7abedba2970e
2022-06-24 18:14:00CaseUnsolvedkvc2a5aa0209a24ab3c04a

It appears that cases are opened (with a bounty), assigned to a number of detectives, then each detective either solves the case or not.

Only the first detective that solves a case earns the bounty, so let’s find them for each case, with the help of arg_min:

DetectiveCases
| where EventType == "CaseSolved"
| project Timestamp, DetectiveId, CaseId
| summarize arg_min(Timestamp, DetectiveId) by CaseId

And then we want to know the bounty for each case:

DetectiveCases
| where EventType == "CaseOpened"
| project CaseId, Properties

The Properties column contains JSON data, so let’s parse it to have a more useful integer:

DetectiveCases
| where EventType == "CaseOpened"
| project CaseId, Bounty=toint(parse_json(Properties)["Bounty"])

Now let’s do a join to find the bounties collected by every detective:

DetectiveCases
| where EventType == "CaseSolved"
| project Timestamp, DetectiveId, CaseId
| summarize arg_min(Timestamp, DetectiveId) by CaseId
| join kind=inner
(
    DetectiveCases
    | where EventType == "CaseOpened"
    | project CaseId, Bounty=toint(parse_json(Properties)["Bounty"])
) on CaseId

Finally, let’s remove columns we don’t need and find the detective with the highest total sum of bounties:

DetectiveCases
| where EventType == "CaseSolved"
| project Timestamp, DetectiveId, CaseId
| summarize arg_min(Timestamp, DetectiveId) by CaseId
| join kind=inner
(
    DetectiveCases
    | where EventType == "CaseOpened"
    | project CaseId, Bounty=toint(parse_json(Properties)["Bounty"])
) on CaseId
| project-away CaseId, CaseId1, Timestamp
| summarize sum(Bounty) by DetectiveId
| order by sum_Bounty desc

The first DetectiveId is the answer, case closed.

Leave a comment