2017-08-29 52 views
3

我知道如何將XML變量加入到其他表中,但在這種情況下,我試圖從表中選擇每行加上來自每個對應表的XML 的結構排,旁邊那行。我無法在線找到任何示例來幫助解決這個問題,因爲大多數示例處理單個XML值(如果存在道歉,我無法在其他XML示例中找到它們)。除了結構化XML數據之外,還選擇行數據

表結構是這樣的:

CREATE TABLE tbl_QuizHistory (
    HistoryId int PRIMARY KEY, 
    QuizData xml NOT NULL 
); 

每個QuizData行值是與此類似:

<quizresult> 
    <question> 
    <questionText>Which fire extinguisher is most suitable for a waste paper basket fire?</questionText> 
    <answer number="0" value="0" chosen="0" imageURL="">Powder</answer> 
    <answer number="1" value="0" chosen="0" imageURL="">Carbon Dioxide (CO2)</answer> 
    <answer number="2" value="1" chosen="1" imageURL="">Water (H2O)</answer> 
    <answer number="3" value="0" chosen="0" imageURL="">Foam</answer> 
    <result>Correct</result> 
    </question> 
    <question> 
    <questionText>Should you use lifts during a fire?</questionText> 
    <answer number="0" value="0" chosen="0" imageURL="">Yes</answer> 
    <answer number="1" value="1" chosen="1" imageURL="">No</answer> 
    <result>Correct</result> 
    </question> 
</quizresult> 

earlier question I was shown how to display the XML data hierarchically@xml ==> questions ==> answer(s)),但只爲單個XML值,這是我適用於將問題/答案層級遷移到表格中:

-- Works for a single XML value/variable... 
;WITH q AS (
    SELECT 
     ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS qID, 
     n.q.value('(./questionText)[1]', 'nvarchar(max)') AS questionText, 
     n.q.value('(./result)[1]', 'nvarchar(50)') AS result, 
     n.q.query('answer') AS answers 
    FROM 
     @xml.nodes('/quizresult/question') AS n (q) 
), 
qa AS (
    SELECT 
     qID, 
     questionText, 
     result, 
     answer.query('.') AS answer 
    FROM 
     q CROSS APPLY 
     answers.nodes('answer') AS a(answer) 
) 
SELECT 
    qa.qID, 
    q.questionText, 
    q.result, 
    qa.answer.value('answer[1]', 'nvarchar(max)') AS answer, 
    qa.answer.value('answer[1]/@number', 'int') AS number, 
    qa.answer.value('answer[1]/@value', 'int') AS val, 
    qa.answer.value('answer[1]/@chosen', 'bit') AS chosen 
FROM 
    qa INNER JOIN 
    q ON qa.qID = q.qID; 

如何將此邏輯應用於每個表格行中的每個XML值?我需要顯示

  1. 測驗HistoryId
  2. 每個問題從測驗(爲了清楚起見,可選的ID,雖然這是由SQL語句生成,並在XML不存在)
  3. 所有答案對每個問題

最終的結果我想實現會產生這樣的:

HistoryId qID questionText                   result  answer                     number val chosen 
--------- ---- --------------------------------------------------------------------------------------- ---------- ---------------------------------------------------------------------------------------- ------- ---- ------ 
100  1 Which fire extinguisher is most suitable for a waste paper basket fire?     Correct Powder                     0  0 0 
100  1 Which fire extinguisher is most suitable for a waste paper basket fire?     Correct Carbon Dioxide (CO2)                  1  0 0 
100  1 Which fire extinguisher is most suitable for a waste paper basket fire?     Correct Water (H2O)                    2  1 1 
100  1 Which fire extinguisher is most suitable for a waste paper basket fire?     Correct Foam                      3  0 0 
100  2 What should your immediate action be on hearing a fire alarm?       Correct Find all of your colleagues before making a speedy exit together       0  0 0 
100  2 What should your immediate action be on hearing a fire alarm?       Correct Collect all your valuables before making a speedy exit         1  0 0 
100  2 What should your immediate action be on hearing a fire alarm?       Correct Check the weather to see if you need your coat before leaving       2  0 0 
100  2 What should your immediate action be on hearing a fire alarm?       Correct Leave the building by the nearest exit, closing doors behind you if the rooms are empty 3  1 1 
101  1 Which fire extinguisher is most suitable for a waste paper basket fire?     Correct Powder                     0  0 0 
101  1 Which fire extinguisher is most suitable for a waste paper basket fire?     Correct Carbon Dioxide (CO2)                  1  0 0 
101  1 Which fire extinguisher is most suitable for a waste paper basket fire?     Correct Water (H2O)                    2  1 1 
101  1 Which fire extinguisher is most suitable for a waste paper basket fire?     Correct Foam                      3  0 0 
101  2 Should you use lifts during a fire?              Correct Yes                      0  0 0 
101  2 Should you use lifts during a fire?              Correct No                      1  1 1 
101  3 Which part of a Carbon Dioxide (CO2) extinguisher should you not touch when operating? Incorrect The body of the extinguisher                0  0 1 
101  3 Which part of a Carbon Dioxide (CO2) extinguisher should you not touch when operating? Incorrect The release trigger and the bottom of the extinguisher         1  0 0 
101  3 Which part of a Carbon Dioxide (CO2) extinguisher should you not touch when operating? Incorrect The horn of the extinguisher                2  1 0 

我明白這會造成大量的重複(因爲每個答案都會重複提問),但沒關係。

我有一個SQL Fiddle,我一直在從事樣品數據設置。

回答

1

它可以用一系列3 CROSS短一點適用,通過水平

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; 

級如果沒有一級的具體計算(例如row_number())是需要:

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) 

Demo

+0

這很有趣。您能否擴展以顯示其他答案的答案詳細程度,以便我可以看到如何在該級別進行審訊? – EvilDr

+1

是的,我已經編輯了答案 – Serg

1

如果我正確理解你想要的:

;WITH q AS (
    SELECT 
     ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS qID, 
     n.q.value('(./questionText)[1]', 'nvarchar(max)') AS questionText, 
     n.q.value('(./result)[1]', 'nvarchar(50)') AS result, 
     n.q.query('answer') AS answers 
    FROM tbl_QuizHistory t 
    CROSS APPLY t.QuizData.nodes('/quizresult/question') AS n (q) 
), 
qa AS (
    SELECT 
     qID, 
     questionText, 
     result, 
     answer.query('.') AS answer 
    FROM q 
    CROSS APPLY answers.nodes('answer') AS a(answer) 
) 
SELECT 
    qa.qID, 
    q.questionText, 
    q.result, 
    qa.answer.value('answer[1]', 'nvarchar(max)') AS answer, 
    qa.answer.value('answer[1]/@number', 'int') AS number, 
    qa.answer.value('answer[1]/@value', 'int') AS val, 
    qa.answer.value('answer[1]/@chosen', 'bit') AS chosen 
FROM qa 
JOIN q ON qa.qID = q.qID; 

Rextester Demo


或者更短:

;WITH q AS (
    SELECT 
     ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS qID, 
     n.q.value('(./questionText)[1]', 'nvarchar(max)') AS questionText, 
     n.q.value('(./result)[1]', 'nvarchar(50)') AS result, 
     n.q.query('answer') AS answers, 
     answer.query('.') AS answer 
    FROM tbl_QuizHistory t 
    CROSS APPLY t.QuizData.nodes('/quizresult/question') AS n (q) 
    CROSS APPLY n.q.nodes('answer') AS a(answer) 
) 
SELECT 
    q.qID, 
    q.questionText, 
    q.result, 
    answer.value('answer[1]', 'nvarchar(max)') AS answer, 
    answer.value('answer[1]/@number', 'int') AS number, 
    answer.value('answer[1]/@value', 'int') AS val, 
    answer.value('answer[1]/@chosen', 'bit') AS chosen 
FROM q; 

Rextester Demo 2

編輯:

;WITH q AS (
    SELECT 
     t.HistoryId, 
     ROW_NUMBER() OVER(PARTITION BY t.HistoryId ORDER BY(SELECT NULL)) AS qID, 
     n.q.value('(./questionText)[1]', 'nvarchar(max)') AS questionText, 
     n.q.value('(./result)[1]', 'nvarchar(50)') AS result, 
     n.q.query('answer') AS answers, 
     answer.query('.') AS answer 
    FROM tbl_QuizHistory t 
    CROSS APPLY t.QuizData.nodes('/quizresult/question') AS n (q) 
    CROSS APPLY n.q.nodes('answer') AS a(answer) 
) 
SELECT 
    q.HistoryId, 
    q.qID, 
    q.questionText, 
    q.result, 
    answer.value('answer[1]', 'nvarchar(max)') AS answer, 
    answer.value('answer[1]/@number', 'int') AS number, 
    answer.value('answer[1]/@value', 'int') AS val, 
    answer.value('answer[1]/@chosen', 'bit') AS chosen 
FROM q; 
+0

差不多......在這兩種情況下,qID都會生成不正確。這應該重置爲1爲下一個HistoryId。我也需要結果中的HistoryId。 – EvilDr

+0

@EvilDr'ROW_NUMBER()OVER(PARTITION BY t.History_ID ORDER BY(SELECT NULL))AS qID,'請檢查更新後的答案 – lad2025

+0

嗨。我修改了Rextester,但問題依然存在。在結果1-4行,這是同樣的問題,但有四個答案,所以qID應該是相同的('1')(請參閱我在OP中的示例)。在結果第5行中,問題文本發生變化,所以這是一個新問題,對於接下來的三行,qId應該是'2'。在第9行中,這是一個不同的測驗,所以qID應該重置爲1.這有意義嗎? – EvilDr

1

我認爲,最直接的方法來實現它來包裝你的代碼,適用於給定變量爲表值函數。您可以通過內嵌您的查詢來獲得相同的結果,但如果您使用函數,則使用像您這樣的複雜代碼更具可讀性。性能將保持不變,因爲它是「內聯」表值函數,而不是多語句函數。

參見例如When would you use a table-valued function?

功能

CREATE FUNCTION [dbo].[GetQuizData] 
(
    @ParamQuizData xml 
) 
RETURNS TABLE 
AS 
RETURN 
(
    WITH q AS 
    (
    SELECT 
     ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS qID, 
     n.q.value('(./questionText)[1]', 'nvarchar(max)') AS questionText, 
     n.q.value('(./result)[1]', 'nvarchar(50)') AS result, 
     n.q.query('answer') AS answers 
    FROM 
     @ParamQuizData.nodes('/quizresult/question') AS n (q) 
    ), 
    qa AS 
    (
     SELECT 
      qID, 
      questionText, 
      result, 
      answer.query('.') AS answer 
     FROM 
      q CROSS APPLY 
      answers.nodes('answer') AS a(answer) 
    ) 
    SELECT 
     qa.qID, 
     q.questionText, 
     q.result, 
     qa.answer.value('answer[1]', 'nvarchar(max)') AS answer, 
     qa.answer.value('answer[1]/@number', 'int') AS number, 
     qa.answer.value('answer[1]/@value', 'int') AS val, 
     qa.answer.value('answer[1]/@chosen', 'bit') AS chosen 
    FROM 
     qa INNER JOIN 
     q ON qa.qID = q.qID 
) 

主查詢

SELECT 
    tbl_QuizHistory.HistoryID 
    ,Q.* 
FROM 
    tbl_QuizHistory 
    CROSS APPLY [dbo].[GetQuizData](tbl_QuizHistory.QuizData) AS Q 
; 

參見SQL Fiddle


聲明:我沒有從正確性的問題分析你的代碼。我只是簡單地將它包裝到函數中,假設它在你需要它的時候工作。


您可以手動將TVF的長查詢內聯到CROSS APPLY。你也必須內聯CTE,它看起來很醜。您可以將此變體和變體的執行計劃與TVF進行比較。他們應該是一樣的。

這是SQL Fiddle

內聯查詢

SELECT 
    tbl_QuizHistory.HistoryID 
    ,CA.* 
FROM 
    tbl_QuizHistory 
    CROSS APPLY 
    (
     SELECT 
      qa.qID, 
      q.questionText, 
      q.result, 
      qa.answer.value('answer[1]', 'nvarchar(max)') AS answer, 
      qa.answer.value('answer[1]/@number', 'int') AS number, 
      qa.answer.value('answer[1]/@value', 'int') AS val, 
      qa.answer.value('answer[1]/@chosen', 'bit') AS chosen 
     FROM 
      (
       SELECT 
        qID, 
        questionText, 
        result, 
        answer.query('.') AS answer 
       FROM 
        (
         SELECT 
          ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS qID, 
          n.q.value('(./questionText)[1]', 'nvarchar(max)') AS questionText, 
          n.q.value('(./result)[1]', 'nvarchar(50)') AS result, 
          n.q.query('answer') AS answers 
         FROM 
          tbl_QuizHistory.QuizData.nodes('/quizresult/question') AS n (q) 
        ) AS q0 
        CROSS APPLY 
        answers.nodes('answer') AS a(answer) 
      ) AS qa 
      INNER JOIN 
      (
       SELECT 
        ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS qID, 
        n.q.value('(./questionText)[1]', 'nvarchar(max)') AS questionText, 
        n.q.value('(./result)[1]', 'nvarchar(50)') AS result, 
        n.q.query('answer') AS answers 
       FROM 
        tbl_QuizHistory.QuizData.nodes('/quizresult/question') AS n (q) 
      ) AS q 
      ON qa.qID = q.qID 
    ) AS CA 
; 

此長的查詢可以簡化,但我沒有分析它能做什麼以及它是如何做到的。我只是內聯給定的工作查詢。

+0

謝謝你。雖然這樣做很好,但是您是否也可以在沒有*函數的情況下展示一個示例(例如,比您希望看到的更長的SQL語句)?我試圖理解聲明的各個部分是如何圍繞「APPLY」固定在一起的,並且無法弄清楚。謝謝。 – EvilDr

+1

@EvilDr,你在內聯函數時沒有什麼特別的地方,它只需要注意一些細節。起初,我在函數中插入了CTE。只是從字面上把'SELECT'子查詢,而不是它們的引用。然後把函數中的查詢放在'CROSS APPLY'括號內。我更新了答案。 –

+1

絕對太棒了。當我看到這些部分如何組合在一起時,學習起來就會更加容易。在時限到期時我會獎勵賞金。謝謝。 – EvilDr