1

I have a table of answers from a submission form that I need to convert to an XML file and upload to a website. Each row is one answer from one submissions and the answers need to be stored as the element name.

Table example: Note: Answer is a string value. The form has multiple questions, I'm only concerned with the Answers.

Submission ID Answer
1 foo
1 bar
2 baz

Then the XML should like this:

<allSubmissions>
    <submission>
        <foo />
        <bar />
    </submission>
    <submission>
        <baz />
    </submission>
</allSubmissions>

How can I accomplish this? I've been successful in using the FOR XML EXPLICIT to format tables where the column name is equal to the XML tag name, but not where the value from the column is supposed to be the name of the tag.

I would prefer NOT use some sort of concatenation work around if possible since I'd like to save the results to an XML file.

Answering the questions in the comment.

  1. XML is the required file type I need, so no CSV, text file, etc.
  2. Correction; I mispoke when I said "any string value". The answer will always be a string and it comes from a pre-defined list of about ~10 values. So I shouldn't have to worry about an illegal element name like something that starts with a number.
  3. The example format provided is the required schema that the validator tests against. So unfortunately, I can't do something like <answer>foo</answer>
  4. A text file that looks like XML would work provided that I can convert to an XML file.
6
  • 6
    answers need to be stored as the element name and Answer could be any string value. Might I suggest to you then that XML is the wrong choice? Among other things XML element names have naming rules, so you can't have one named foo bar (with a space) or 1baz (starting with a digit), nor can they start with the letters XML Xml xml. Commented Sep 27 at 4:15
  • 3
    Just no, noone wants that kind of xml. Commented Sep 27 at 8:37
  • 3
    Why not <submission><id>1</id><answer>foo</answer><answer>bar</answer></submission>? Commented Sep 27 at 8:53
  • 1
    Do you want an XML file that will actually pass an XML validator, or do you want a text file that contains probably-not-valid-but-looks-like-XML? Commented Sep 27 at 13:20
  • Updated question to answer the comments. Commented Sep 27 at 21:58

3 Answers 3

2

Since you know all 10 values upfront, you can just output each column using CASE

But you want a grouped output by SubmissionID, so you are going to have to use FOR XML EXPLICIT, which can be very fiddly.

Each grouped row becomes a single <submission> node, so Tag can be 1 and Parent can be NULL.

Then each of those values becomes a separate column, where you do a pivot, outputting either '' to get an empty node, or NULL to omit the node. The node name goes into the column name as shown.

SELECT
  1 AS Tag,
  NULL AS Parent,
  --t.SubmissionID AS [submission!1!SubmissionID!hide],
  MIN(CASE WHEN t.Answer = 'foo' THEN '' END) AS [submission!1!foo!element],
  MIN(CASE WHEN t.Answer = 'bar' THEN '' END) AS [submission!1!bar!element],
  MIN(CASE WHEN t.Answer = 'baz' THEN '' END) AS [submission!1!baz!element]
FROM YourTable t
GROUP BY
  t.SubmissionID
ORDER BY
  t.SubmissionID
FOR XML EXPLICIT, ROOT('allSubmissions'), TYPE;

db<>fiddle

Sign up to request clarification or add additional context in comments.

Comments

0

I will suggest a mix of other answers - it will make use of FOR XML directive and also use dynamic SQL to not work with statically listed options from the column.

Here's the query:

DECLARE @cols NVARCHAR(MAX) = '';

-- Build dynamic columns based on distinct answers
SELECT @cols = STRING_AGG(
  'MAX(CASE WHEN Answer = ' + QUOTENAME(Answer, '''') + ' THEN '''' END) AS ' + QUOTENAME(Answer),
  ', ')
FROM (SELECT DISTINCT Answer FROM Answers) d;

-- Build the dynamic query
DECLARE @sql NVARCHAR(MAX) = '
SELECT ' + @cols + '
FROM Answers
GROUP BY SubmissionID
FOR XML PATH(''submission''), ROOT(''allSubmissions'')';

-- Execute it
EXEC(@sql);

DB fiddle

1 Comment

Also aggregation via a variable is not recommended and may give incorrect results, see learn.microsoft.com/en-us/sql/t-sql/language-elements/… Use STRING_AGG instead
0

Initially, my first idea was to try to use XQuery with FLWOR expression. Unfortunately, SQL Server's XQuery FLWOR expression does not support dynamic construction of XML element names. This is a key limitation: SQL Server’s XQuery is based on a restricted subset of XQuery 1.0, and dynamic element construction (e.g., <{Answer}/> where Answer is a variable or column) is not allowed.

But we can use FOR XML PATH creatively with dynamic SQL and string concatenation to simulate composition of the XML fragments.

This method is NOT requiring to know upfront a predefined list of answers.

dbfiddle

SQL

-- DDL and sample data population, start
DECLARE @tbl TABLE (SubmissionID INT, Answer VARCHAR(100));
INSERT @tbl (SubmissionID, Answer) VALUES
('1', 'foo'),
('1', 'bar'),
('2', 'baz');
-- DDL and sample data population, end

DECLARE @result NVARCHAR(MAX) = '<allSubmissions>';

SELECT @result = @result + '<submission>' +
  (
    SELECT '<' + Answer + ' />'
    FROM @tbl AS t2
    WHERE t2.SubmissionID = t1.SubmissionID
    FOR XML PATH(''), TYPE
  ).value('.', 'NVARCHAR(MAX)')
  + '</submission>'
FROM (SELECT SubmissionID FROM @tbl GROUP BY SubmissionID) AS t1;

SET @result = @result + '</allSubmissions>';

SELECT TRY_CAST(@result AS XML) AS FinalXml;

Output

<allSubmissions>
  <submission>
    <foo />
    <bar />
  </submission>
  <submission>
    <baz />
  </submission>
</allSubmissions>

5 Comments

Any particular reason to avoid STRING_AGG?
Oops. I missed that the OP is on the SQL Server 2022. But that's okay. The answer will work on a wide range of SQL Server versions.
Swapping out the table names, this yields FinalXml as the column name and NULL as the value. Not sure if it makes a difference, but I'm using a common table in place of @tbl (your declared temp table)
My answer follows a so called minimal reproducible example pattern. You copy it to SSMS as-is, run it , and it works as expected. Probably there is a difference in data sample that you provided. Table could be any real persisted db table, it makes no difference in comparison with table variable @tbl.
I added a dbfiddle to the answer.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.