user1316401
user1316401

Reputation: 465

Parsing/Processing XML and Inserting Into SQL Tables

Following up from: Is this XML format correct?

I've got some SQL code working that loops through the QABLOCKs and prints them (the PRINTs will change to INSERTs in production).

-- XML instance
DECLARE    @x1 XML
SELECT @x1 = 
'
<tests>
    <test id="1">
        <qablock number="1">
            <question>What is 1 + 1?</question>
            <explanation>It"s 2.</explanation>
            <options>
                <option number="1" value="1" correct="0" />
                <option number="2" value="2" correct="1" />
                <option number="3" value="3" correct="0" />
                <option number="4" value="4" correct="0" />
                <option number="5" value="5" correct="0" />
            </options>
        </qablock>
        <qablock number="2">
            <question>What is 2 + 2?</question>
            <explanation>It"s 4.</explanation>
            <options>
                <option number="1" value="1" correct="0" />
                <option number="2" value="2" correct="0" />
                <option number="3" value="3" correct="0" />
                <option number="4" value="4" correct="1" />
                <option number="5" value="5" correct="0" />
            </options>
        </qablock>
    </test>
</tests>
'

DECLARE 
    @cnt INT, 
    @totCnt INT,
    @child XML

-- counter variables
SELECT 
    @cnt = 1,
    @totCnt = @x1.value('count(/tests/test/qablock)','INT')

-- loop
WHILE @cnt <= @totCnt BEGIN
    SELECT
        1 AS tests_id, --this is hard-coded
        @cnt AS qablock_number, 
        @x1.value('(/tests/test/qablock[position()=sql:variable("@cnt")]/question/text())[1]','varchar(500)') AS question, 
        @x1.value('(/tests/test/qablock[position()=sql:variable("@cnt")]/explanation/text())[1]','varchar(1000)') AS explanation

    PRINT 'Processing Child Element: ' + CAST(@cnt AS VARCHAR)
    PRINT 'Child element:  ' + CAST(@child AS VARCHAR(max))
    PRINT ''

    -- increment the counter variable
    SELECT @cnt = @cnt + 1
END

I don't know if that's the correct procedure whatsoever, but at least it seems to work. What I'd like to do next is run another similar script that loops through all the OPTIONs and PRINTs them as well. For each OPTION, I need to print: QABLOCK number, OPTION number, OPTION value, and OPTION is_correct. I can't even get close. :( Here's what I've got so far:

-- XML instance
DECLARE    @x1 XML
SELECT @x1 = 
'
<tests>
    <test id="1">
        <qablock number="1">
            <question>What is 1 + 1?</question>
            <explanation>It"s 2.</explanation>
            <options>
                <option number="1" value="1" correct="0" />
                <option number="2" value="2" correct="1" />
                <option number="3" value="3" correct="0" />
                <option number="4" value="4" correct="0" />
                <option number="5" value="5" correct="0" />
            </options>
        </qablock>
        <qablock number="2">
            <question>What is 2 + 2?</question>
            <explanation>It"s 4.</explanation>
            <options>
                <option number="1" value="1" correct="0" />
                <option number="2" value="2" correct="0" />
                <option number="3" value="3" correct="0" />
                <option number="4" value="4" correct="1" />
                <option number="5" value="5" correct="0" />
            </options>
        </qablock>
    </test>
</tests>
'

DECLARE 
    @cnt INT, 
    @totCnt INT,
    @child XML

-- counter variables
SELECT 
    @cnt = 1,
    @totCnt = @x1.value('count(/tests/test/qablock/options/option)','INT')

-- loop
WHILE @cnt <= @totCnt BEGIN
    SELECT
        1 AS tests_id, --hard-coded value
        @x1.value('(/tests/test/qablock/@number)[1]','varchar(500)') AS qablock_number, 
        @x1.value('(/tests/test/qablock/options/option[position()=sql:variable("@cnt")]/@number)[1]','varchar(500)') AS option_number

    PRINT 'Processing Child Element: ' + CAST(@cnt AS VARCHAR)
    PRINT 'Child element:  ' + CAST(@child AS VARCHAR(max))
    PRINT ''

    -- increment the counter variable
    SELECT @cnt = @cnt + 1
END

So, obviously it's messed up, but I can't seem to figure out what to change.

First of all, I don't know how to get the parent QABLOCK number of the current OPTION.

Second, only my first 5 option_number's are displaying their numbers properly, the second 5 are showing NULL.

P.S. Some of this code was from: http://beyondrelational.com/modules/2/blogs/28/posts/10316/xquery-lab-35-how-to-write-a-loop-to-process-all-the-child-elements-of-an-xml-value.aspx

Upvotes: 0

Views: 784

Answers (1)

JohnLBevan
JohnLBevan

Reputation: 24430

This should do the trick:

-- XML instance
DECLARE    @x1 XML
SELECT @x1 = 
'
<tests>
    <test id="1">
        <qablock number="1">
            <question>What is 1 + 1?</question>
            <explanation>It"s 2.</explanation>
            <options>
                <option number="1" value="1" correct="0" />
                <option number="2" value="2" correct="1" />
                <option number="3" value="3" correct="0" />
                <option number="4" value="4" correct="0" />
                <option number="5" value="5" correct="0" />
            </options>
        </qablock>
        <qablock number="2">
            <question>What is 2 + 2?</question>
            <explanation>It"s 4.</explanation>
            <options>
                <option number="1" value="1" correct="0" />
                <option number="2" value="2" correct="0" />
                <option number="3" value="3" correct="0" />
                <option number="4" value="4" correct="1" />
                <option number="5" value="5" correct="0" />
            </options>
        </qablock>
    </test>
</tests>
'

declare @testCount int
, @testIndex int 
, @qaCount int
, @qaIndex int 
, @optCount int
, @optIndex int 

, @testId int
, @qaNo int
, @question nvarchar(500)
, @explanation nvarchar(500)
, @optNo int
, @optVal int
, @optCorrect bit


--for each test
set @testCount = @x1.value('count(/tests/test)','int')
set @testIndex = 1 
while @testIndex <= @testCount
begin

    --insert a record for this test
    set @testId = @x1.value('(/tests/test[sql:variable(''@testIndex'')]/@id)[1]','int')
    print 'insert into tests (id, ...) values (' + + cast(@testId as nvarchar(9)) + ')'
    print 'set @testIdDB = scope_identity()' --if we're using db generated ids rather than relying on the ids in the imported xml

    --for each qablock
    set @qaCount = @x1.value('count(/tests/test[sql:variable(''@testIndex'')]/qablock)','int')
    set @qaIndex = 1    
    while @qaIndex <= @qaCount
    begin   
        --insert a qa record
        select @qaNo = @x1.value('(/tests/test[sql:variable(''@testIndex'')]/qablock[sql:variable(''@qaIndex'')]/@number)[1]','int')
        , @question = @x1.value('(/tests/test[sql:variable(''@testIndex'')]/qablock[sql:variable(''@qaIndex'')]/question/text())[1]','nvarchar(500)')
        , @explanation = @x1.value('(/tests/test[sql:variable(''@testIndex'')]/qablock[sql:variable(''@qaIndex'')]/explanation/text())[1]','nvarchar(500)')
        print 'insert into qa (testId, qaNo, Question, Explanation) values (@testIdDB, ' + cast(@qaNo as nvarchar(9)) + ', ' + quotename(@question,'''') + ', ' + quotename(@explanation,'''') + ')'
        print 'set @qaIdDB = scope_identity()' 

        --for each option
        set @optCount = @x1.value('count(/tests/test[sql:variable(''@testIndex'')]/qablock[sql:variable(''@qaIndex'')]/options/option)','int')
        set @optIndex = 1   
        while @optIndex <= @optCount
        begin   
            --insert an option record
            select @optNo = @x1.value('(/tests/test[sql:variable(''@testIndex'')]/qablock[sql:variable(''@qaIndex'')]/options/option[sql:variable(''@optIndex'')]/@number)[1]','int') 
            , @optVal  = @x1.value('(/tests/test[sql:variable(''@testIndex'')]/qablock[sql:variable(''@qaIndex'')]/options/option[sql:variable(''@optIndex'')]/@value)[1]','int')
            , @optCorrect  = @x1.value('(/tests/test[sql:variable(''@testIndex'')]/qablock[sql:variable(''@qaIndex'')]/options/option[sql:variable(''@optIndex'')]/@correct)[1]','bit')
            print 'insert into tests (qaId, number, value, isCorrect) values (@qaIdDB, ' + cast(@optNo as nvarchar(9)) + ', ' + cast(@optVal as nvarchar(9)) + ', ' + cast(@optCorrect as nchar(1)) +  ')'

            set @optIndex = @optIndex + 1
        end
        set @qaIndex = @qaIndex + 1
    end
    set @testIndex = @testIndex + 1
end

Or better still (thanks to the link you provided for prompting me):

select x.value('@id','int') testId
FROM @x1.nodes('/tests/test') d(x)

select x.value('../@id','int') parentTestId
, x.value('@number','int') qaNumber
,  x.value('(question/text())[1]','nvarchar(500)') question
,  x.value('(explanation/text())[1]','nvarchar(500)') explanation
FROM @x1.nodes('/tests/test/qablock') d(x)

select x.value('../../@number','int') parentQaNumber
, x.value('@number','int') number
, x.value('@value','int') value
, x.value('@correct','bit') correct
FROM @x1.nodes('/tests/test/qablock/options/option') d(x)

Upvotes: 1

Related Questions