Sdílet prostřednictvím


Operátor join s časovým intervalem

Často je užitečné spojit mezi dvěma velkými datovými sadami pomocí některého klíče s vysokou kardinalitou, jako je ID operace nebo ID relace, a dále omezit záznamy na pravé straně ($right), které se musí shodovat s každým záznamem na levé straně ($left), přidáním omezení "časové vzdálenosti" mezi datetime sloupce vlevo a vpravo.

Výše uvedená operace se liší od obvyklé operace spojení Kusto, protože pro equi-join část párování klíče s vysokou kardinalitou mezi levou a pravou datovou sadou může systém také použít funkci vzdálenosti a použít ji k podstatnému zrychlení spojení.

Poznámka

Funkce vzdálenosti se nechová jako rovnost (to znamená, že pokud jsou obě hodnoty dist(x,y) i dist(y,z) pravdivé, nevypadá to, že dist(x,z) je také pravda.) Interně se tomu někdy říká "diagonální spojení".

Pokud například chcete identifikovat sekvence událostí v relativně malém časovém intervalu, předpokládejme, že máte tabulku T s následujícím schématem:

  • SessionId: Sloupec typu string s ID korelace.
  • EventType: Sloupec typu string , který identifikuje typ události záznamu.
  • Timestamp: Sloupec typu datetime označuje, kdy došlo k události popsané záznamem.
let T = datatable(SessionId:string, EventType:string, Timestamp:datetime)
[
    '0', 'A', datetime(2017-10-01 00:00:00),
    '0', 'B', datetime(2017-10-01 00:01:00),
    '1', 'B', datetime(2017-10-01 00:02:00),
    '1', 'A', datetime(2017-10-01 00:03:00),
    '3', 'A', datetime(2017-10-01 00:04:00),
    '3', 'B', datetime(2017-10-01 00:10:00),
];
T

Výstup

SessionId Typ události Timestamp
0 A 2017-10-01 00:00:00.0000000
0 B 2017-10-01 00:01:00.0000000
1 B 2017-10-01 00:02:00.0000000
1 A 2017-10-01 00:03:00.0000000
3 A 2017-10-01 00:04:00.0000000
3 B 2017-10-01 00:10:00.0000000

Příkaz k problému

Náš dotaz by měl zodpovědět následující otázku:

Vyhledejte všechna ID relací, ve kterých byl typ A události následovaný typem B události v časovém 1min okně.

Poznámka

Ve výše uvedených ukázkových datech je 0jediným takovým ID relace .

Sémanticky následující dotaz odpovídá na tuto otázku, i když neefektivně.

T 
| where EventType == 'A'
| project SessionId, Start=Timestamp
| join kind=inner
    (
    T 
    | where EventType == 'B'
    | project SessionId, End=Timestamp
    ) on SessionId
| where (End - Start) between (0min .. 1min)
| project SessionId, Start, End 

Výstup

SessionId Spustit End
0 2017-10-01 00:00:00.0000000 2017-10-01 00:01:00.0000000

Abychom tento dotaz optimalizovali, můžeme ho přepsat, jak je popsáno níže, aby se časové období vyjádřilo jako klíč spojení.

Přepište dotaz tak, aby se zohlednil časový interval.

Přepište dotaz tak, aby datetime se hodnoty "diskretizovaly" do kbelíků, jejichž velikost je poloviční než velikost časového intervalu. K porovnání těchto ID kbelíků equi-join použijte Kusto.

let lookupWindow = 1min;
let lookupBin = lookupWindow / 2.0; // lookup bin = equal to 1/2 of the lookup window
T 
| where EventType == 'A'
| project SessionId, Start=Timestamp,
          // TimeKey on the left side of the join is mapped to a discrete time axis for the join purpose
          TimeKey = bin(Timestamp, lookupBin)
| join kind=inner
    (
    T 
    | where EventType == 'B'
    | project SessionId, End=Timestamp,
              // TimeKey on the right side of the join - emulates event 'B' appearing several times
              // as if it was 'replicated'
              TimeKey = range(bin(Timestamp-lookupWindow, lookupBin),
                              bin(Timestamp, lookupBin),
                              lookupBin)
    // 'mv-expand' translates the TimeKey array range into a column
    | mv-expand TimeKey to typeof(datetime)
    ) on SessionId, TimeKey 
| where (End - Start) between (0min .. lookupWindow)
| project SessionId, Start, End 

Referenční informace k spustitelným dotazům (s vloženou tabulkou)

let T = datatable(SessionId:string, EventType:string, Timestamp:datetime)
[
    '0', 'A', datetime(2017-10-01 00:00:00),
    '0', 'B', datetime(2017-10-01 00:01:00),
    '1', 'B', datetime(2017-10-01 00:02:00),
    '1', 'A', datetime(2017-10-01 00:03:00),
    '3', 'A', datetime(2017-10-01 00:04:00),
    '3', 'B', datetime(2017-10-01 00:10:00),
];
let lookupWindow = 1min;
let lookupBin = lookupWindow / 2.0;
T 
| where EventType == 'A'
| project SessionId, Start=Timestamp, TimeKey = bin(Timestamp, lookupBin)
| join kind=inner
    (
    T 
    | where EventType == 'B'
    | project SessionId, End=Timestamp,
              TimeKey = range(bin(Timestamp-lookupWindow, lookupBin),
                              bin(Timestamp, lookupBin),
                              lookupBin)
    | mv-expand TimeKey to typeof(datetime)
    ) on SessionId, TimeKey 
| where (End - Start) between (0min .. lookupWindow)
| project SessionId, Start, End 

Výstup

SessionId Spustit End
0 2017-10-01 00:00:00.0000000 2017-10-01 00:01:00.0000000

Dotaz na 5M data

Další dotaz emuluje datovou sadu s 5M záznamy a ID ~1M a spustí dotaz pomocí výše popsané techniky.

let T = range x from 1 to 5000000 step 1
| extend SessionId = rand(1000000), EventType = rand(3), Time=datetime(2017-01-01)+(x * 10ms)
| extend EventType = case(EventType < 1, "A",
                          EventType < 2, "B",
                          "C");
let lookupWindow = 1min;
let lookupBin = lookupWindow / 2.0;
T 
| where EventType == 'A'
| project SessionId, Start=Time, TimeKey = bin(Time, lookupBin)
| join kind=inner
    (
    T 
    | where EventType == 'B'
    | project SessionId, End=Time, 
              TimeKey = range(bin(Time-lookupWindow, lookupBin), 
                              bin(Time, lookupBin),
                              lookupBin)
    | mv-expand TimeKey to typeof(datetime)
    ) on SessionId, TimeKey 
| where (End - Start) between (0min .. lookupWindow)
| project SessionId, Start, End 
| count 

Výstup

Počet
3344