As an alternative to catching an exception, in recent versions of Oracle (since 12c) you can use the validate_conversion() function, which returns 1 for success and zero for failure:
create or replace FUNCTION is_column_numberic (table_name IN VARCHAR2, column_name in VARCHAR2)
RETURN NUMBER
AS
l_result NUMBER;
BEGIN
EXECUTE IMMEDIATE 'SELECT MIN(VALIDATE_CONVERSION(' ||column_name|| ' AS NUMBER)) from ' ||table_name
INTO l_result;
RETURN l_result;
END;
/
fiddle
I've added MIN() around it so that if any value in the column cannot be converted then the overall result will be 0; if all values can then it will be 1. I have omitted the NVL() because if there are null values you can't really say if those are numeric or not, and it wasn't doing anything in your version anyway; but if you want to treat a column with no values at all as numeric or not numeric then you can do NVL(MAX(...), 0) - but with no data the answer is really 'unknown', so just returning null might be appropriate, as long as the caller knows what that means.
And as OldProgrammer said, you need an INTO clause - not having one is why your version isn't ever throwing an exception. The documentation says:
Note: If dynamic_sql_statement is a SELECT statement, and you omit both into_clause and bulk_collect_into_clause, then execute_immediate_statement never executes.
The statement is still parsed, but the query is not executed - so it would report a syntax error (and return zero for all rows because of when others in your version), but it won't access the table data or try to convert any values, hence it not complaining about non-numeric values when you run your code.