2017-08-29 69 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