For a function to be able to return multiple rows it has to be declared as returns table() (or returns setof)
And to actually return a result from within a PL/pgSQL function you need to use return query (as documented in the manual)
To build dynamic SQL in Postgres it is highly recommended to use the format() function to properly deal with identifiers (and to make the source easier to read).
So you need something like:
create or replace function get_data(p_sort_column text) returns table (id integer) as $$ begin return query execute format( 'with q1 as ( select id from table_two join table_three on ... ) select q1.id from q1 order by %I desc', p_sort_column); end; $$ language plpgsql;
Note that the order by inside the CTE is pretty much useless if you are sorting the final query unless you use a LIMIT or distinct on () inside the query.
You can make your life even easier if you use another level of dollar quoting for the dynamic SQL:
create or replace function get_data(p_sort_column text) returns table (id integer) as $$ begin return query execute format( $query$ with q1 as ( select id from table_two join table_three on ... ) select q1.id from q1 order by %I desc $query$, p_sort_column); end; $$ language plpgsql;
EXECUTE, or what results you expect from this query. There's no reason for the above code to even be in plpgsql - you could just use a SQL function