You may need to consider a bit more closely how you want to join the values. Each duplicate value creates a match.
So if you have 3 of one value of x in one set and 2 values in the other and do a left join you end up with 6 (3*2) records with that value.
Example:
data one;
input x y;
datalines;
1 1
1 2
1 3
2 1
2 2
;
data two;
input x z;
datalines;
1 11
1 22
2 11
2 22
;
proc sql;
select a.* , b.z
from one as a
left join
two as b
on a.x=b.x
;
quit;
You may want to consider whether some other variables might be suitable to reduce the duplicates, such as a date, geographic location, product code or similar that appears in pairs of the data sets.
Something else to consider is to split the query into two pieces. Do the join for A and B and let that complete.
You may want to consider if you need a Distinct to reduce duplicate records on that query. The result would then be used to join with C as a separate query, again considering what you need.
Subsetting data as early as practical may help by reducing the number of variables. If you only need the value if ID2 from B then perhaps
from data0 as A left join (select distinct id, id2 from data2) as B
which will reduce the number of duplicate id2 values that might come in from B as well as reducing the number of things pushed around in the back ground as well.
Similar for C just get the values you need and Select distinct to reduce duplication.
Note: Distinct by itself is a time consuming instruction but if you don't expect 31,000 records it may get you down to something closer to 3000.
Sort of a generic answer as I don't have any data to work with.
Basically if you need all the results you're going to eat the time and disk space. If you don't, then you need to figure out one or more ways to reduce the records at each step.