Aber natürlich hat das damit zu tun, da ich mir inzwischen angewöhnt habe, skalare subselects die mir genau 1 Wert zurückliefern in lateral joins umzuschreiben, weil eben dies besser optimiert werden kann und somit auch mehrere skalare subselects zu einem lateral join vereinfacht werden können.

Und sicherlich bringt dein left join dieselbe Lösung ist aber schlechter optimierbar da sie den Join auf dem Group-Ergebnis ausführen muss.

Code:
from tablea tbla 
     left join lateral (select aggr(x) as ax, Agg(y) as ay, aggr(z) as az
                  from tableb b
                 where b.key1 = tbla.Key1 
                    and b.key2 = tbla.Key2 
                    and b.key3 = tbla.Key3                  
               ) tblb on 1=1
Ergänzt:
Den Group By kann man sich sogar sparen, da direkt per Where gefiltert wird.
Statt left geht alternativ nun auch cross join, so dass man auch 1=1 spart.
Man kann die Aggregate im Outerselect verrechnen (ax * 100.0 / ay) oder bereits im subselect als aggr(x) * 100.0 / aggr(y).

Desweiteren erlaubt ein Lateral auch einen Order By mit Limit, da "fetch first n rows" nur am Ende erlaubt ist und Order by in Subselects (außer over()) ignoriert werden.

Somit kann man z.B. eine Preisfindung mittels

cross / left join lateral (select preis from preistable
where datum between gueltigvon and gueltigbis
order by gueltigbis -- je nach Modell auch order by gueltigvon desc
limit 1
) p [on 1=1]

durchführen, was ansonsten ungleich komplexer ist.
Und warum soll ich lateral nur bei UDTF's verwenden wenn sie doch um so viel einfacher und performanter verwendet werden können als CTE's?

Und was die CTE's angeht, kann man das im Job über die Anzahl Open einer Tabelle und die Zugriffe prüfen, da CTE's keine temporäre Tabelle aufbauen.
Temporäre Tabellen werden im generellen Optimierungsweg unabhängig von CTE's.

Und im Debugmode (bzw. SQL-Analysator) kann man auch schon mal entdecken, warum Abfragen langsamer sind als gedacht.
Z.B. wurde bei einer einfachen Abfrage
where numfeld = 0
das numfeld in cast(numfeld as ...) umgeschrieben, was eine Indexverwendung ausschloss.
Durch numfeld = cast(0 as numfeld-typ) wurde dann wieder der Index angenommen.