Pode ser um pouco mais curto com uma série de 3 CROSS APPLY, nível por nível
SELECT HistoryId,
t.qID,
t.questionText,
t.result,
a.aId,
a.answerNbr,
a.answerChosen,
a.answerTxt
FROM
tbl_QuizHistory
CROSS APPLY QuizData.nodes('quizresult') AS n(q)
CROSS APPLY (
SELECT
ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS qID,
t.q.value('(./questionText)[1]', 'nvarchar(max)') AS questionText,
t.q.value('(./result)[1]', 'nvarchar(50)') AS result,
t.q.query('.') queryXml
FROM
n.q.nodes('./question') t(q)
) t
CROSS APPLY (
SELECT
ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS aID,
q.a.value('(./@number)[1]', 'int') as answerNbr,
q.a.value('(./@chosen)[1]', 'bit') as answerChosen,
q.a.value('.','nvarchar(max)') as answerTxt
FROM
t.queryXml.nodes('question/answer') q(a)
) a;
Se não houver cálculos específicos de nível (por exemplo,
row_number()
) são necessários: SELECT HistoryId,
t.qID,
t.questionText,
t.result,
q.a.value('(./@number)[1]', 'int') as answerNbr,
q.a.value('(./@chosen)[1]', 'bit') as answerChosen,
q.a.value('.','nvarchar(max)') as answerTxt
FROM
tbl_QuizHistory
CROSS APPLY QuizData.nodes('quizresult') AS n(q)
CROSS APPLY (
SELECT
ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS qID,
t.q.value('(./questionText)[1]', 'nvarchar(max)') AS questionText,
t.q.value('(./result)[1]', 'nvarchar(50)') AS result,
t.q.query('.') queryXml
FROM n.q.nodes('./question') t(q)
) t
CROSS APPLY t.queryXml.nodes('question/answer') q(a)
Demonstração