A join in Invantive UniversalSQL joins whatever the connection can reach: two tables of one platform, a table of a platform with a table of a database, a table with the answer of another statement. The engine reads each side where it lives and joins them itself.
## An Example
Over the [[Invantive Query Tool/Sample Database|sample database]], each project beside the number of projects in its municipality:
```sql
select pjt.pjt_code
, pjt.pjt_gemeente
, tot.aantal
from Projects@dummy pjt
join ( select pjt_gemeente gem
, count(*) aantal
from Projects@dummy
group
by pjt_gemeente
) tot
on tot.gem = pjt.pjt_gemeente
order
by tot.aantal desc
, pjt.pjt_code
```
![[20260904-qt-join.png]]
## Across Platforms
The same statement joins two data containers by naming them:
```sql
select ...
from Projects@dummy pjt
join MyTable@sqlserver tbl
on tbl.code = pjt.pjt_code
```
Both containers have to belong to the database connected to; see [[Invantive Query Tool/Databases and Data Containers|Databases and Data Containers]]. This is what makes an exchange between two platforms one statement instead of a program with an intermediate file.
## What It Costs
A join is not free where one side is a cloud platform. The engine narrows the second side with the keys of the first where the platform can filter on them, and reads the table whole where it cannot. Which of the two happened shows in the [[Invantive Query Tool/Progress|Progress]] panel as the rows a step read, and in the [[Invantive Query Tool/Connection Monitor|Connection Monitor]] as the number of calls.
Two habits keep that in hand: select the columns actually needed rather than `select *`, and put the smaller or better-filtered side first. A join whose driving side is one row and whose other side is a whole cloud table is a different statement from the same join the other way round.