Follow two solutions in SQL depending if you know the maximum number of product types or not.
SQL> select * from p_pt order by 1, 2;
PERSON PRODUCT_TY
---------- ----------
janedoe p1
janedoe p2
janedoe p2
janedoe p3
janedoe p3
johndoe p1
johndoe p1
johndoe p1
johndoe p2
johndoe p3
SQL> -- Maximum number of columns known, say 5
SQL> set head off
SQL> set feed off
SQL> col nop noprint
SQL> col person format a10
SQL> col val format a3
SQL> with
2 product_types as (
3 select distinct
4 product_type,
5 dense_rank () over (order by product_type) rk
6 from p_pt
7 )
8 select 1 nop, 'person' person,
9 lpad(max(decode(rk,1,product_type)),3) val,
10 lpad(max(decode(rk,2,product_type)),3) val,
11 lpad(max(decode(rk,3,product_type)),3) val,
12 lpad(max(decode(rk,4,product_type)),3) val,
13 lpad(max(decode(rk,5,product_type)),3) val
14 from product_types
15 union all
16 select 2 nop, person,
17 to_char(sum(decode(a.product_type||'/1',b.product_type||'/'||b.rk,1)),'99') val,
18 to_char(sum(decode(a.product_type||'/2',b.product_type||'/'||b.rk,1)),'99') val,
19 to_char(sum(decode(a.product_type||'/3',b.product_type||'/'||b.rk,1)),'99') val,
20 to_char(sum(decode(a.product_type||'/4',b.product_type||'/'||b.rk,1)),'99') val,
21 to_char(sum(decode(a.product_type||'/5',b.product_type||'/'||b.rk,1)),'99') val
22 from p_pt a, product_types b
23 group by person
24 order by nop, person
25 /
person p1 p2 p3
janedoe 1 2 2
johndoe 3 1 1
SQL> insert into p_pt values ('janedoe', 'p4');
SQL> with
2 product_types as (
3 select distinct
4 product_type,
5 dense_rank () over (order by product_type) rk
6 from p_pt
7 )
8 select 1 nop, 'person' person,
9 lpad(max(decode(rk,1,product_type)),3) val,
10 lpad(max(decode(rk,2,product_type)),3) val,
11 lpad(max(decode(rk,3,product_type)),3) val,
12 lpad(max(decode(rk,4,product_type)),3) val,
13 lpad(max(decode(rk,5,product_type)),3) val
14 from product_types
15 union all
16 select 2 nop, person,
17 to_char(sum(decode(a.product_type||'/1',b.product_type||'/'||b.rk,1)),'99') val,
18 to_char(sum(decode(a.product_type||'/2',b.product_type||'/'||b.rk,1)),'99') val,
19 to_char(sum(decode(a.product_type||'/3',b.product_type||'/'||b.rk,1)),'99') val,
20 to_char(sum(decode(a.product_type||'/4',b.product_type||'/'||b.rk,1)),'99') val,
21 to_char(sum(decode(a.product_type||'/5',b.product_type||'/'||b.rk,1)),'99') val
22 from p_pt a, product_types b
23 group by person
24 order by nop, person
25 /
person p1 p2 p3 p4
janedoe 1 2 2 1
johndoe 3 1 1
SQL> rollback;
SQL> -- Maximum number of columns unknown
SQL> col val format a80
SQL> with
2 product_types as (
3 select distinct
4 product_type,
5 dense_rank () over (order by product_type) rk,
6 count(distinct product_type) over () cnt
7 from p_pt
8 ),
9 persons as ( select distinct person from p_pt )
10 select 1 nop, 'person' person,
11 translate(substr(sys_connect_by_path(lpad(product_type,3),'/'),2),'/',' ') val
12 from product_types
13 where rk = cnt
14 connect by prior rk = rk - 1
15 start with rk = 1
16 union all
17 select 2 nop, person,
18 translate(substr(sys_connect_by_path(to_char(val,'99'),'/'),2),'/',' ') val
19 from ( select a.person, b.rk, b.cnt,
20 nvl((select sum(1) from p_pt c
21 where c.person = a.person and c.product_type = b.product_type)
22 ,0) val
23 from persons a, product_types b
24 group by a.person, b.product_type, b.rk, b.cnt
25 )
26 where rk = cnt
27 connect by prior rk = rk - 1 and prior person = person
28 start with rk = 1
29 order by nop, person
30 /
person p1 p2 p3
janedoe 1 2 2
johndoe 3 1 1
SQL> insert into p_pt values ('janedoe', 'p4');
SQL> with
2 product_types as (
3 select distinct
4 product_type,
5 dense_rank () over (order by product_type) rk,
6 count(distinct product_type) over () cnt
7 from p_pt
8 ),
9 persons as ( select distinct person from p_pt )
10 select 1 nop, 'person' person,
11 translate(substr(sys_connect_by_path(lpad(product_type,3),'/'),2),'/',' ') val
12 from product_types
13 where rk = cnt
14 connect by prior rk = rk - 1
15 start with rk = 1
16 union all
17 select 2 nop, person,
18 translate(substr(sys_connect_by_path(to_char(val,'99'),'/'),2),'/',' ') val
19 from ( select a.person, b.rk, b.cnt,
20 nvl((select sum(1) from p_pt c
21 where c.person = a.person and c.product_type = b.product_type)
22 ,0) val
23 from persons a, product_types b
24 group by a.person, b.product_type, b.rk, b.cnt
25 )
26 where rk = cnt
27 connect by prior rk = rk - 1 and prior person = person
28 start with rk = 1
29 order by nop, person
30 /
person p1 p2 p3 p4
janedoe 1 2 2 1
johndoe 3 1 1 0
In this second query, I gave another way to get the right row using count (rk=cnt) instead of max.
Regards
Michel