If the columns are you select is fixed, then execute immediate will be fine, eg
l_sql := 'select c1,c2,c3 from mytable where 1=1 ';
l_sql :=
l_sql ||
case
when bind1 is not null then
' and mycol = :bind1 '
else
' and :bind1 is null'
end;
l_sql :=
l_sql ||
case
when bind2 is not null then
' and mycol2 = :bind2 '
else
' and :bind2 is null'
end;
...
...
That way, the number of binds is fixed , so you just do
execute immediate l_sql using bind1,bind2,bind3,.....bind10;
even if some of the binds are null.
If you want more flexibility, ie, the columns you select is variable etc, then its time for DBMS_SQL
https://asktom.oracle.com/pls/apex/asktom.search?tag=execute-immediate-dont-know-how-many-input-variables