Use a view that acts as a quick-and-useful abstraction to mimic a denormalized table?
E.g.
```
CREATE VIEW vw_events_and_projects AS
SELECT *
FROM events
JOIN projects
```
Then
```
SELECT * FROM vw_events_and_projects
```
Edit:
And if you need OLAP, replicate the normalized table to a database that handles analytics workflows better (e.g. ClickHouse).
Then you get the normalized forms for your OLTP workflows (your "bread and butter"); and you also get the efficiency and ergonomics of real-deal OLAP.
Of course, your biggest issue is going to be keeping the two in-sync. Obvious case is to have your OLTP database stream synchronization data to the replica whenever data is modified.
E.g.
``` CREATE VIEW vw_events_and_projects AS SELECT * FROM events JOIN projects ```
Then
``` SELECT * FROM vw_events_and_projects ```
Edit:
And if you need OLAP, replicate the normalized table to a database that handles analytics workflows better (e.g. ClickHouse).
Then you get the normalized forms for your OLTP workflows (your "bread and butter"); and you also get the efficiency and ergonomics of real-deal OLAP.
Of course, your biggest issue is going to be keeping the two in-sync. Obvious case is to have your OLTP database stream synchronization data to the replica whenever data is modified.