This question was much more difficult to solve than you might expect. Your attempt with crosstab() was directed in the right direction. But to assign dynamic column names, you need dynamic SQL: EXECUTE in the plpgsql function.
Change the data type of the infos.type column from text to regtype to prevent SQL injection and other errors. For example, you have a number data type that is not a valid PostgreSQL data type. I replaced it with numeric so that it can work.
You can simplify the task by avoiding column names that require double quoting. Like nume_anterior instead of "nume anterior" .
You might want to add the row_id column to the row_id table to mark all the elements of the same row. You need it for the crosstab() function, and it allows you to ignore columns with NULL values. The crosstab() function with two parameters can deal with missing columns. I am synthesizing a missing column with the expression (d.id-1)/13 below - which works for the data in your example.
You need to install the additional tablefunc module (once for each database):
CREATE EXTENSION tablefunc;
Find further clarifications and links in this related answer .
This function will do what it is looking for:
CREATE OR REPLACE FUNCTION f_mytbl() RETURNS TABLE (id int , nume text , prenume text , cnp numeric , "nume anterior" text, "stare civila" text, cetatenie text , rezidenta text , adresa text , "tip act" text , "serie ci" text , "numar ci" text , "data eliberarii" text , "eliberat de" text) LANGUAGE plpgsql AS $BODY$ BEGIN RETURN QUERY EXECUTE $f$ SELECT * FROM crosstab( 'SELECT (d.id-1)/13 -- AS row_id , i.id, d.value FROM infos i JOIN info_data d ON d.id_info = i.id ORDER BY 1, i.id', 'SELECT id FROM infos ORDER BY id' ) AS tbl ($f$ || 'id int, , nume text , prenume text , cnp numeric , "nume anterior" text, "stare civila" text, cetatenie text , rezidenta text , adresa text , "tip act" text , "serie ci" text , "numar ci" text , "data eliberarii" text , "eliberat de" text)'; END; $BODY$;
Call:
SELECT * FROM x.mytbl();
Do not confuse nested dollar-quoting .
BTW: I created a list of columns with this statement:
SELECT 'id int,' || string_agg(quote_ident(name) || ' ' || type ,', ' ORDER BY i.id) FROM infos i;