【问题标题】:Read XML data into hierarchical tables将 XML 数据读入分层表
【发布时间】:2018-02-03 15:00:39
【问题描述】:

我有用于存储测验结果的 XML 数据。我需要将其转换为两张表,一张包含问题,另一张包含答案,但至关重要的是,它们之间存在关系。

目前这种关系只存在于 XML 结构中(没有 ID 值等)。

经过一天的研究和测试不同的方法,我已经提取了这两个部分,但不知道如何创建层次结构:

declare @xml xml = N'<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>What should your immediate action be on hearing a fire alarm?</questionText>
    <answer number="0" value="0" chosen="0" imageURL="">Find all of your colleagues before making a speedy exit together</answer>
    <answer number="1" value="0" chosen="0" imageURL="">Collect all your valuables before making a speedy exit</answer>
    <answer number="2" value="0" chosen="0" imageURL="">Check the weather to see if you need your coat before leaving</answer>
    <answer number="3" value="1" chosen="1" imageURL="">Leave the building by the nearest exit, closing doors behind you if the rooms are empty</answer>
    <result>Correct</result>
  </question>
  <question>
    <questionText>Which is the most suitable extinguisher for a Computer which is on fire?</questionText>
    <answer number="0" value="0" chosen="1" imageURL="">Water (H2O)</answer>
    <answer number="1" value="0" chosen="0" imageURL="">Powder</answer>
    <answer number="2" value="1" chosen="0" imageURL="">Carbon Dioxide (CO2)</answer>
    <result>Incorrect</result>
  </question>
</quizresult>';

-- Get questions only

DECLARE @questions TABLE (questionText nvarchar(max), result nvarchar(50));
INSERT INTO @questions (questionText, result)
SELECT
    n.q.value('(./questionText)[1]', 'nvarchar(max)') AS questionText,
    n.q.value('(./result)[1]', 'nvarchar(50)') AS result
FROM
    @xml.nodes('/quizresult/question') AS n (q);

-- Get answers only

DECLARE @answers TABLE (answer nvarchar(max), number int, val int, chosen bit);
INSERT INTO @answers (answer, number, val, chosen)
SELECT
    n.q.value('.[1]', 'nvarchar(max)') AS answer,
    n.q.value('@number', 'int') AS number,
    n.q.value('@value', 'int') AS val,
    n.q.value('@chosen', 'bit') AS chosen
FROM
    @xml.nodes('/quizresult/question/answer') AS n (q);

如果可以创建 ID/GUID(或其他东西)来创建尊重 XML 文件的父/子层次结构,谁能告诉我? 我应该补充一点,实际上这是一个 XML 列,数据将被整体转换。在弄清楚基本方法之前,我只是在使用一个变量。

【问题讨论】:

  • 我认为唯一的可能性是使用答案文本加入问题和答案。但在这种情况下,问题必须是唯一的。

标签: sql-server tsql sql-server-2012 xquery sqlxml


【解决方案1】:

我们可以(ab)使用ROW_NUMBER() 在XQuery 之外生成ID。序言:

WITH questions AS (
    SELECT
        ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS ID,
        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)
), questions_and_answers AS (
    SELECT ID, questionText, result, answer.query('.') AS answer
    FROM questions
    CROSS APPLY answers.nodes('answer') AS a(answer)
)

现在检索问题

SELECT ID, questionText, result 
FROM questions

还有答案

SELECT ID AS questionID,
    q.answer.value('answer[1]', 'nvarchar(max)') AS answer,
    q.answer.value('answer[1]/@number', 'int') AS number,
    q.answer.value('answer[1]/@value', 'int') AS val,
    q.answer.value('answer[1]/@chosen', 'bit') AS chosen
FROM questions_and_answers AS q

【讨论】:

  • 我真的很喜欢这种方法,谢谢。问题是我理想情况下需要问题和答案(都是你的例子),但因为 CTE 只使用一次,我必须运行两次查询,这很昂贵。有没有办法在不多次运行 XML 查询的情况下获取两个表?
  • @EvilDr:您可以先将questions_and_answers 存储在临时表中,然后对其进行查询(使用DISTINCT 获取问题)。一个查询不可能同时插入两个表,所以无论如何切片,都需要一些中间值。 (除非您使用游标,但逐行处理的性能缺陷可能会抵消它的任何好处。)
  • 好的,没问题。我的主要保留是那里重复问题的数量(每个答案一个),我希望可以避免。
  • 如果你的 XML 真的很大,那么粉碎/非​​规范化是瓶颈,并且你期望父行和子行之间的比例很大(不太可能有问题和答案,但它可能发生在一般),值得研究游标以循环问题(同时保持基于表的答案插入)或 XML 的客户端处理(不限于像 SQL 这样的基于集合的方法)。我不愿意在我的回答中找出任何一种方法,但这些都是选择。
  • 不,没关系。感谢您提供的帮助,并会接受答案。
【解决方案2】:

通过使用MERGE 插入您的问题,您可以从源数据和目标数据中捕获字段。这样您就可以访问新插入的问题 ID 和相应的答案,而无需依赖问题文本来链接问题和答案。

你只需要一个映射表来存储插入问题后的中间结果。

DECLARE @Mapping TABLE (QuestionID INT NOT NULL, Answers XML);

MERGE @questions AS q
USING
(   SELECT  questionText = q.x.value('questionText[1]', 'NVARCHAR(MAX)'),
            result = q.x.value('result[1]', 'NVARCHAR(MAX)'),
            Answers = q.x.query('answer')
    FROM    @xml.nodes('quizresult/question') q (x)
) AS t
    ON 1 = 0 
WHEN NOT MATCHED THEN 
    INSERT (QuestionText, Result)
    VALUES (t.QuestionText, t.Result)
OUTPUT inserted.QuestionID, t.Answers INTO @Mapping (QuestionID, Answers);

然后存储中间结果,包括问题 ID,您可以查询映射表以插入答案。

-- INSERT ANSWERS USING MAPPING TABLE
INSERT @Answers (QuestionID, Answer, Number, Val, Chosen)
SELECT  m.QuestionID,
        answer = a.x.value('text()[1]', 'NVARCHAR(MAX)'),
        number = a.x.value('@number[1]', 'INT'),
        val = a.x.value('@value[1]', 'INT'),
        chosen = a.x.value('@chosen[1]', 'BIT')
FROM    @Mapping m
        CROSS APPLY Answers.nodes('answer') a (x);

完整的工作演示

DECLARE @xml XML = N'<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>What should your immediate action be on hearing a fire alarm?</questionText>
    <answer number="0" value="0" chosen="0" imageURL="">Find all of your colleagues before making a speedy exit together</answer>
    <answer number="1" value="0" chosen="0" imageURL="">Collect all your valuables before making a speedy exit</answer>
    <answer number="2" value="0" chosen="0" imageURL="">Check the weather to see if you need your coat before leaving</answer>
    <answer number="3" value="1" chosen="1" imageURL="">Leave the building by the nearest exit, closing doors behind you if the rooms are empty</answer>
    <result>Correct</result>
  </question>
  <question>
    <questionText>Which is the most suitable extinguisher for a Computer which is on fire?</questionText>
    <answer number="0" value="0" chosen="1" imageURL="">Water (H2O)</answer>
    <answer number="1" value="0" chosen="0" imageURL="">Powder</answer>
    <answer number="2" value="1" chosen="0" imageURL="">Carbon Dioxide (CO2)</answer>
    <result>Incorrect</result>
  </question>
  <question>
    <questionText>Which is the most suitable extinguisher for a Computer which is on fire?</questionText>
    <answer number="0" value="0" chosen="1" imageURL="">Water (H2O) DUPLICATE</answer>
    <answer number="1" value="0" chosen="0" imageURL="">Powder DUPLICATE</answer>
    <answer number="2" value="1" chosen="0" imageURL="">Carbon Dioxide (CO2) DUPLICATE</answer>
    <result>Incorrect</result>
  </question>
</quizresult>';

-- DEMO TARGE TABLES
DECLARE @questions TABLE 
(
    QuestionID INT IDENTITY(1, 1) NOT NULL,
    questionText NVARCHAR(MAX), 
    result NVARCHAR(50)
);
DECLARE @answers TABLE 
(
    AnswerID INT IDENTITY(1, 1) NOT NULL,
    QuestionID INT NOT NULL,
    answer NVARCHAR(MAX), 
    number INT, 
    val INT, 
    chosen BIT
);



-- MAPPING TABLE
DECLARE @Mapping TABLE (QuestionID INT NOT NULL, Answers XML);

-- INSERT ANSWERS
MERGE @questions AS q
USING
(   SELECT  questionText = q.x.value('questionText[1]', 'NVARCHAR(MAX)'),
            result = q.x.value('result[1]', 'NVARCHAR(MAX)'),
            Answers = q.x.query('answer')
    FROM    @xml.nodes('quizresult/question') q (x)
) AS t
    ON 1 = 0 
WHEN NOT MATCHED THEN 
    INSERT (QuestionText, Result)
    VALUES (t.QuestionText, t.Result)
OUTPUT inserted.QuestionID, t.Answers INTO @Mapping (QuestionID, Answers);

-- INSERT ANSWERS USING MAPPING TABLE
INSERT @Answers (QuestionID, Answer, Number, Val, Chosen)
SELECT  m.QuestionID,
        answer = a.x.value('text()[1]', 'NVARCHAR(MAX)'),
        number = a.x.value('@number[1]', 'INT'),
        val = a.x.value('@value[1]', 'INT'),
        chosen = a.x.value('@chosen[1]', 'BIT')
FROM    @Mapping m
        CROSS APPLY Answers.nodes('answer') a (x);

-- CHECK RESULTS
SELECT  *
FROM    @Questions AS q
        INNER JOIN @Answers AS a
            ON a.QuestionID = q.QuestionID;

【讨论】:

  • 请参阅我的 cmets 以获取 @Rex 关于在 questionText 上匹配的答案。此解决方案是否容易受到相同(极不可能,但可能)的风险?
  • 不。我刚刚编辑了工作示例,因此其中有一个重复的问题表明它仍然有效。
【解决方案3】:

这里唯一的关系是与 questionText。因此可以像这样获取此列

DECLARE @answers TABLE (questionText nvarchar(max),answer nvarchar(max), 
number int, val int, chosen bit);

INSERT INTO @answers (questionText, answer, number, val, chosen)
SELECT
n.q.value('(../questionText)[1]', 'nvarchar(max)') as questionText,
n.q.value('.[1]', 'nvarchar(max)') AS answer,
n.q.value('@number', 'int') AS number,
n.q.value('@value', 'int') AS val,
n.q.value('@chosen', 'bit') AS chosen
FROM
@xml.nodes('/quizresult/question/answer') AS n (q);

或者,您可以根据 questionText 生成 ID

DECLARE @questions TABLE (Id int, questionText nvarchar(max), result nvarchar(50));

INSERT INTO @questions (id, questionText, result)
SELECT
    Rank() over(order by n.q.value('(./questionText)[1]', 'nvarchar(max)')) as Id,
    n.q.value('(./questionText)[1]', 'nvarchar(max)') AS questionText,
    n.q.value('(./result)[1]', 'nvarchar(50)') AS result
FROM
    @xml.nodes('/quizresult/question') AS n (q);

DECLARE @answers TABLE (Id int, questionText nvarchar(max),answer 
nvarchar(max), number int, val int, chosen bit);

INSERT INTO @answers (Id, questionText, answer, number, val, chosen)
SELECT
   Dense_rank() over(order by n.q.value('(../questionText)[1]', 'nvarchar(max)')) as Id,
   n.q.value('(../questionText)[1]', 'nvarchar(max)') as questionText,
   n.q.value('.[1]', 'nvarchar(max)') AS answer,
   n.q.value('@number', 'int') AS number,
   n.q.value('@value', 'int') AS val,
   n.q.value('@chosen', 'bit') AS chosen
FROM
   @xml.nodes('/quizresult/question/answer') AS n (q);

如果相同的问题/答案可以重复,其他解决方案是

DECLARE @questions TABLE (Id int identity(1,1), questionText nvarchar(max), result nvarchar(50), answer xml);
INSERT INTO @questions ( questionText, result, answer)
SELECT
n.q.value('(./questionText)[1]', 'nvarchar(max)') AS questionText,
n.q.value('(./result)[1]', 'nvarchar(50)') AS result,
 n.q.query('answer') AS answer
FROM
@xml.nodes('/quizresult/question') AS n (q);

DECLARE @answers TABLE (Id int, questionText nvarchar(max),answer nvarchar(max), number int, val int, chosen bit);
INSERT INTO @answers (Id, questionText, answer, number, val, chosen)
SELECT
q.Id as Id,
q.questionText as questionText,
n.q.value('.[1]', 'nvarchar(max)') AS answer,
n.q.value('@number', 'int') AS number,
n.q.value('@value', 'int') AS val,
n.q.value('@chosen', 'bit') AS chosen
FROM
@questions q
 outer apply q.answer.nodes('answer') as n(q)

select * from @questions
select * from @answers

【讨论】:

  • 加入 questionText 的危险在于有可能(极不可能,但仍有可能)相同的问题文本可以在测验中出现两次。因此,我希望在不依赖文本匹配的问题级别生成一个 ID...
  • 答案呢?重复的每个问题都一样吗?
  • 是的。同样,它极不可能发生,但 可能在理论上发生。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多