One way of doing this is using the much useful PostgreSQL SPLIT_PART function, which allows you to split on a character (in your specific case, the space). As long as you don't need brackets for the last field, you may split on the open bracket and remove the last bracket with the RTRIM function.
SELECT id,
SPLIT_PART(question, ' ', 1) AS questionid,
SPLIT_PART(question, ' ', 2) AS question_name,
RTRIM(SPLIT_PART(question, '[', 2), ']') AS sub_question_name
FROM tab
Check the demo here.
You can deepen your understanding of these functions on PostgreSQL official documentation related to the string functions.
EDIT: For a more advanced matching, you should consider using regex and PostgreSQL pattern matching:
SELECT id,
(REGEXP_MATCHES(question, '^[\d\.]+'))[1] AS questionid,
(REGEXP_MATCHES(question, '(?<= )[^[]+'))[1] AS question_name,
(REGEXP_MATCHES(question, '(?<=\[).*(?=\]$)'))[1] AS sub_question_name
FROM tab
Regex for questionid Explanation:
^: start of string
[\d\.]+: any existing combination of digit and dots
Regex for question_name Explanation:
(?<= ): positive lookbehind that matches a space before the match
[^[]+: any existing combination of any character other than [
Regex for sub_question_name Explanation:
(?<=\[): positive lookbehind that matches an open bracket before the match
.*: any character
(?=\]$): positive lookahead that matches a closed bracket after the match
Check the demo here.