One way to do this would be Object Types, in this case the type would be analagous to your
#t1. So it would need to be defined somewhere but it would not need to be global, it could be per-schema or per-procedure even. First, we can create a type:
SQL> create or replace type t1_type as object (x int, y int, z int) 2 / Type created. SQL> create or replace type t1 as table of t1_type 2 / Type created.
Now set up some sample data:
SQL> create table xy (x int, y int) 2 / Table created. SQL> insert into xy values (1, 2) 2 / 1 row created. SQL> insert into xy values (3, 4) 2 / 1 row created. SQL> commit 2 / Commit complete.
And create a function over this data returning our "temporary" type:
SQL> create or replace function fn_t1 return t1 as 2 v_t1 t1 := t1(); -- empty temporary table (really an array) 3 v_ix number default 0; -- index 4 begin 5 for r in (select * from xy) loop 6 v_ix := v_ix + 1; 7 v_t1.extend; 8 v_t1(v_ix) := t1_type(r.x, r.y, (r.x + r.y)); 9 end loop; 10 return v_t1; 11 end; 12 / Function created.
SQL> select * from the (select cast (fn_t1 as t1) from dual); X Y Z ---------- ---------- ---------- 1 2 3 3 4 7
As you can see this is pretty clunky (and uses collection pseudo-functions, which is an obscure feature at the best of times!), as I always say, porting from DB to DB is not merely about syntax and keywords in their SQL dialects, the real difficulty comes in different underlying assumptions (in the case of SQL Server, that cursors are expensive and their use avoided/worked around at all costs).