{"id":102257,"date":"2024-06-03T17:56:51","date_gmt":"2024-06-03T17:56:51","guid":{"rendered":"https:\/\/www.red-gate.com\/simple-talk\/?p=102257"},"modified":"2026-02-25T13:45:35","modified_gmt":"2026-02-25T13:45:35","slug":"effective-strategies-for-storing-and-parsing-xml-in-sql-server","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/t-sql-programming-sql-server\/effective-strategies-for-storing-and-parsing-xml-in-sql-server\/","title":{"rendered":"Store, Parse &#038; Convert XML in SQL Server"},"content":{"rendered":"<p>SQL Server provides a native XML data type with built-in support for XQuery, OPENXML, XML indexes, and schema validation. Store XML in XML-typed columns (not VARCHAR) to get type checking, XML-specific indexing, and native XQuery support. Parse XML values using the .value(), .query(), .nodes(), and .modify() methods, or use OPENXML for rowset-based shredding.<\/p>\n<p>Convert between XML and string types with CAST(xml_column AS NVARCHAR(MAX)) or CAST(string_column AS XML). For performance, create primary and secondary XML indexes on columns that are frequently queried. This guide covers the full lifecycle of XML in SQL Server &#8211; from storage and parsing to indexing, validation, and conversion.<\/p>\n<h2>Introduction<\/h2>\n<p>XML is a common storage format for data, metadata, parameters, or other semi-structured data. Because of this, it often finds its way into SQL Server databases and needs to be managed alongside other data types.<\/p>\n<p>Even though a relational database is not the optimal place to store and manage XML data, it is often needed due to application requirements, convenience, or a need to maintain this information in close proximity to other app data.<\/p>\n<p>This article dives into a variety of common XML challenges and the functionality included in SQL Server to help make managing them as simple as possible.<\/p>\n<h2>Store XML as XML. Store non-XML as non-XML.<\/h2>\n<p>XML will enter our data lives in one of two ways:<\/p>\n<ol>\n<li>Data stored in an XML-typed column.<\/li>\n<li>Data stored in some other format.<\/li>\n<\/ol>\n<p>Before diving into managing XML, it is important to note that not all XML requires a data engineer to know advanced (or any) knowledge about how to index, filter, update, or use XML.<\/p>\n<p>Sometimes, XML is stored in a database for convenience and is simply written or read by the application directly. In these scenarios, there is little architectural work involved aside from the need to have an XML-typed column available to store the data.<\/p>\n<p>In many ways, this is an ideal situation. The purpose of a database is to store and retrieve data, and not needing to index, filter, or shred XML allows a database to do what it is best at while not introducing complexity into database or application code to manage it. If XML is stored in SQL Server, it will often need to be directly queried, even if filtering, aggregating, or other more advanced functionality is not needed.<\/p>\n<p>If possible, store XML data in an XML typed column. Developers sometimes choose to store XML in string columns instead, such as <code>VARCHAR(MAX)<\/code>. While strings are more familiar to us and easier to work with, they do not provide the many XML-related benefits that an XML-typed column does. Oftentimes, when XML functionality is later needed against a string column, the result is the awkward need to convert from <code>VARCHAR<\/code> to XML and then back again, as needed. This is inefficient and creates additional opportunities for mistakes to occur as data is being frequently converted, in addition to written, read, filtered, etc&#8230;<\/p>\n<p>Similarly: do not store data as XML unless needed. Data or metadata that will be routinely consumed by SQL Server ideally should be stored in a format that is quick and easy to read\/write. Whether in a configuration table, metadata table, temporal table, or some other convenient data structure, consider the ideal candidate for storing data before finalizing the decision.<\/p>\n<p>Data types are database design decisions that have long lives ahead of them. Choosing the wrong data type for an important data element can result in poor performance, wasted time and effort, and technical debt that remains for years to come. Therefore, be sure to store XML in XML-typed columns and non-XML in non-XML data types.<\/p>\n<h2>Parsing XML<\/h2>\n<p>The most common need with XML is to read it, either in its entirety or by pulling out specific details from a document. For the demos in this article, the <code>dbo.DatabaseLog<\/code> table will be used, which can be found in the <code>AdventureWorksDW<\/code> database. (You can download a backup to restore <a href=\"https:\/\/learn.microsoft.com\/en-us\/sql\/samples\/adventureworks-install-configure\">here<\/a> from the Microsoft site. I am using the 2022 version of the database.)<\/p>\n<p>This table contains logs of SQL commands used to create or modify database objects. For example, the following query retrieves a single row from the table:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">SELECT\n     *\nFROM dbo.DatabaseLog\nWHERE DatabaseLog.PostTime = '2017-10-27 14:36:32.110';<\/pre>\n<p>The results show a column called <code>XmlEvent<\/code>:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"1190\" height=\"68\" class=\"wp-image-102258\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2024\/04\/word-image-102257-1.jpeg\" \/><\/p>\n<p>The simplest way to read data from an XML column is by using an XQuery against it. In the following example, several elements are returned, each as a separate column:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">SELECT\n  DatabaseLog.XMLEvent.value('(EVENT_INSTANCE\/EventType)[1]',\n                              'VARCHAR(MAX)') AS EventType,\n  DatabaseLog.XMLEvent.value('(EVENT_INSTANCE\/ServerName)[1]', \n                              'VARCHAR(MAX)') AS ServerName,\n  DatabaseLog.XMLEvent.value('(EVENT_INSTANCE\/LoginName)[1]', \n                               'VARCHAR(MAX)') AS LoginName,  \n  DatabaseLog.XMLEvent.value\n          ('(EVENT_INSTANCE\/TSQLCommand\/CommandText)[1]'\n                             , 'VARCHAR(MAX)') AS SetOptions\nFROM dbo.DatabaseLog\nWHERE DatabaseLog.PostTime = '2017-10-27 14:36:32.110';<\/pre>\n<p>Each column specifically retrieves the first element for the various tags provided. The results look like this:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"851\" height=\"66\" class=\"wp-image-102259\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2024\/04\/word-image-102257-2.jpeg\" \/><\/p>\n<p>The data type provided alongside the tag detail is the data type that the result will be returned as. Using <code>VARCHAR(MAX)<\/code> is overkill for <code>EventType<\/code>, <code>ServerName<\/code>, and <code>LoginName<\/code>, as these columns will not exceed 128 characters. It is a fairly typical process to start with a large datatype as you are interrogating the data in XML. Moving forward in this article, data types will be chosen that are more fitting for the data elements provided.<\/p>\n<p>This syntax is perfect when you know exactly what data elements are needed, what they are called, and where to find them. This is an easy way to parse XML when it is served to you nicely on a platter. If only life were always so easy!<\/p>\n<p><code>OPENXML<\/code> may also be used to parse an XML document. This is ideally used on a scalar variable or XML that is read into a single variable\/parameter. An extra step is needed to prepare the XML document, which converts it into a format that SQL Server can easily consume:<\/p>\n<pre class=\"lang:none theme:none\">DECLARE @XMLDoc INT;\nDECLARE @XMLEvent XML = '\n&lt;EVENT_INSTANCE&gt;\n  &lt;EventType&gt;ALTER_TABLE&lt;\/EventType&gt;\n  &lt;PostTime&gt;2017-10-27T14:36:32.113&lt;\/PostTime&gt;\n  &lt;SPID&gt;62&lt;\/SPID&gt;\n  &lt;ServerName&gt;BARBKESS24\\MSSQL2017RTM&lt;\/ServerName&gt;\n  &lt;LoginName&gt;REDMOND\\barbkess&lt;\/LoginName&gt;\n  &lt;UserName&gt;dbo&lt;\/UserName&gt;\n  &lt;DatabaseName&gt;AdventureWorksDW2017&lt;\/DatabaseName&gt;\n  &lt;SchemaName&gt;dbo&lt;\/SchemaName&gt;\n  &lt;ObjectName&gt;DimPromotion&lt;\/ObjectName&gt;\n  &lt;ObjectType&gt;TABLE&lt;\/ObjectType&gt;\n  &lt;AlterTableActionList&gt;\n    &lt;Create&gt;\n      &lt;Constraints&gt;\n        &lt;Name&gt;PK_DimPromotion_PromotionKey&lt;\/Name&gt;\n      &lt;\/Constraints&gt;\n    &lt;\/Create&gt;\n  &lt;\/AlterTableActionList&gt;\n  &lt;TSQLCommand&gt;\n    &lt;SetOptions ANSI_NULLS=\"ON\" ANSI_NULL_DEFAULT=\"ON\" ANSI_PADDING=\"ON\" QUOTED_IDENTIFIER=\"ON\" ENCRYPTED=\"FALSE\" \/&gt;\n    &lt;CommandText&gt;ALTER TABLE [dbo].[DimPromotion] WITH CHECK ADD \n    CONSTRAINT [PK_DimPromotion_PromotionKey] PRIMARY KEY CLUSTERED \n    (\n       [PromotionKey]\n    )  ON [PRIMARY]&lt;\/CommandText&gt;\n  &lt;\/TSQLCommand&gt;\n&lt;\/EVENT_INSTANCE&gt;\n';\nEXEC sys.sp_xml_preparedocument @XMLDoc OUTPUT, @XMLEvent;<\/pre>\n<p>The script above takes the same XML from the previous example, stores it in a scalar XML variable, and then converts it using <code>sp_xml_preparedocument<\/code>, placing the result into <code>@XMLDoc<\/code>. (Note that as this is a variable, you will need to execute the following <code>SELECT<\/code> statement in the same batch of code for it to execute.)<\/p>\n<p>From here, there are a handful of options available. The first allows for specific elements to be retrieved from the XML document, like this:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">SELECT\n     *\nFROM OPENXML (@XMLDoc, '\/EVENT_INSTANCE', 2)\nWITH (EventType VARCHAR(50),\n      ServerName VARCHAR(128),\n      LoginName VARCHAR(128),\n      TSQLCommand VARCHAR(MAX));<\/pre>\n<p>This syntax will explicitly return the same four elements that were previously parsed out of the XML document. The second parameter (2) indicates that the XML is element centric. If an XML document is attribute-centric, then set this to 1.<\/p>\n<p>Ensure that data types are correct, or an error will be returned. For example, if <code>EventType<\/code> were declared as an INT, then the following error would be thrown.<\/p>\n<p><code>Msg 245, Level 16, State 1, Line 61<br \/>\n<\/code><code>Conversion failed when converting the nvarchar value 'ALTER_TABLE' to data type int.<\/code><\/p>\n<p>If there is a need to return everything from an XML document, then consider the following syntax, instead:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">SELECT\n     *\nFROM OPENXML (@XMLDoc, '\/EVENT_INSTANCE'); \n-- No value for XML flag: All elements returned \n--in an edge table.<\/pre>\n<p>The result will be a hierarchical table that looks like this:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"660\" height=\"249\" class=\"wp-image-102260\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2024\/04\/word-image-102257-3.jpeg\" \/><\/p>\n<p>Note that while the column headers will the same no matter what sort of XML document is parsed, the content of the rows will vary greatly. There are many ways to use this data, such as:<\/p>\n<ul>\n<li>Return all details for a given attribute.<\/li>\n<li>Search for specific values.<\/li>\n<li>Validate the existence (or lack thereof) for an attribute or value.<\/li>\n<\/ul>\n<p>Because XML documents can be large, be sure that you are prepared for the size of the output. If testing, a <code>TOP<\/code> or <code>COUNT(*)<\/code> may be used with the <code>SELECT<\/code> statement that returns the table. This can help with understanding the size of the output before diving into it.<\/p>\n<p>The results use a pairing of <code>id<\/code> and <code>parentid<\/code> to create a hierarchy to understand each level of XML within the document. <code>Nodetype<\/code> indicates the type of XML node that the row references, which can be one of three values:<\/p>\n<ol>\n<li>Element node<\/li>\n<li>Attribute node<\/li>\n<li>Text node<\/li>\n<\/ol>\n<p><code>localname<\/code> is the tag name. If it references an XML text value, then it will contain the string, \u201c<code>#text<\/code>\u201d.<\/p>\n<p>The column <code>prev<\/code> indicates the id of the previous element tag. If that element contains many levels of detail, then there can be a long gap of <code>NULL<\/code>s between each value in this column.<\/p>\n<p>Finally, the column <em>text<\/em> contains the element text detail, if applicable. It will be <code>NULL<\/code> for XML metadata and contain values for XML data.<\/p>\n<p>If XML document manipulation is a part of a larger script\\batch, then it is a best practice to remove the document when it is no longer needed. This can be accomplished with <code>sys.sp_xml_removedocument<\/code>, like this:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">EXEC sys.sp_xml_removedocument @XMLDoc;<\/pre>\n<h2>Creating XML<\/h2>\n<p>Sometimes there is a need to create XML documents for use elsewhere in SQL Server, or by an application. There are a variety of ways to do this, which will be outlined here.<\/p>\n<p>The simplest way to generate XML quickly is by using <code>FOR XML AUTO<\/code>. This mindless syntax can convert the contents of a SQL query into XML. One nice aspect of this syntax is that the query can include any of the standard T-SQL syntax, such as <code>WHERE<\/code>, <code>GROUP BY<\/code>, and <code>ORDER BY<\/code>, for example. The following query generates XML that converts the contents of four columns in <code>dbo.DatabaseLog<\/code> into elements within an XML document.<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">SELECT\n\tDatabaseLogID,\n\tPostTime,\n\t[Schema],\n\t[Object]\nFROM dbo.DatabaseLog\nWHERE PostTime &gt;= '10\/27\/2017 14:36:34.000'\nORDER BY PostTime ASC\nFOR XML AUTO;<\/pre>\n<p>Here is the XML output:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"1811\" height=\"304\" class=\"wp-image-102261\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2024\/04\/word-image-102257-4.png\" \/><\/p>\n<p>Note that table name is included as the tag for each set of attributes and the attribute names are simply the column name. Each row is its own addition to the XML file, as it is attribute centric.<\/p>\n<p>Element-centric XML may be generated by adding <code>ELEMENTS<\/code> to the <code>FOR XML AUTO <\/code>clause:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">SELECT\n\tDatabaseLogID,\n\tPostTime,\n\t[Schema],\n\t[Object]\nFROM dbo.DatabaseLog\nWHERE PostTime &gt;= '10\/27\/2017 14:36:34.000'\nORDER BY PostTime ASC\nFOR XML AUTO, ELEMENTS;<\/pre>\n<p>This addition to the query greatly changes the shape of the XML output. The following is a sample of how it looks:<\/p>\n<pre class=\"lang:none theme:none\">&lt;dbo.DatabaseLog&gt;\n  &lt;DatabaseLogID&gt;89&lt;\/DatabaseLogID&gt;\n  &lt;PostTime&gt;2017-10-27T14:36:34.113&lt;\/PostTime&gt;\n  &lt;Schema&gt;dbo&lt;\/Schema&gt;\n  &lt;Object&gt;FactResellerSales&lt;\/Object&gt;\n&lt;\/dbo.DatabaseLog&gt;\n&lt;dbo.DatabaseLog&gt;\n  &lt;DatabaseLogID&gt;90&lt;\/DatabaseLogID&gt;\n  &lt;PostTime&gt;2017-10-27T14:36:34.120&lt;\/PostTime&gt;\n  &lt;Schema&gt;dbo&lt;\/Schema&gt;\n  &lt;Object&gt;FactSalesQuota&lt;\/Object&gt;\n&lt;\/dbo.DatabaseLog&gt;<\/pre>\n<p>In this scenario, each row has been converted into its own element. Within each element are the detailed gleaned from the columns that were selected in the query. The choice to use attribute-centric or element-centric XML will depend on the application or process that is consuming the document.<\/p>\n<p>There is no need to accept the table name as the root node name. This can be easily adjusted like this:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">SELECT\n\tDatabaseLogID,\n\tPostTime,\n\t[Schema],\n\t[Object]\nFROM dbo.DatabaseLog\nWHERE PostTime &gt;= '10\/27\/2017 14:36:34.000'\nORDER BY PostTime ASC\nFOR XML PATH ('Log');<\/pre>\n<p>In this example, the root node will be named \u201c<code>Log<\/code>\u201d instead of \u201c<code>dbo.DatabaseLog<\/code>\u201d. Here is a sample of the output:<\/p>\n<pre class=\"lang:none theme:none\">&lt;Log&gt;\n  &lt;DatabaseLogID&gt;89&lt;\/DatabaseLogID&gt;\n  &lt;PostTime&gt;2017-10-27T14:36:34.113&lt;\/PostTime&gt;\n  &lt;Schema&gt;dbo&lt;\/Schema&gt;\n  &lt;Object&gt;FactResellerSales&lt;\/Object&gt;\n&lt;\/Log&gt;<\/pre>\n<p>Since the XML is being generated directly from the query output, columns may be renamed, converted to other data types, or formatted as part of the query. Basically, any of the usual tactics that may be used to adjust how column output looks can be used here as well.<\/p>\n<p>If XML column values are included in the creation of an XML document, they will be added and nested accordingly. For example, the following query includes the column XmlEvent, which itself contains XML data:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">SELECT\n\tDatabaseLogID,\n\tXmlEvent\nFROM dbo.DatabaseLog\nWHERE PostTime &gt;= '10\/27\/2017 14:36:34.000'\nORDER BY PostTime ASC\nFOR XML AUTO;<\/pre>\n<p>The output will include nested attributes based on the contents of each <code>XmlEvent<\/code> value, like this partial output:<\/p>\n<pre class=\"lang:none theme:none\">&lt;dbo.DatabaseLog DatabaseLogID=\"89\"&gt;\n  &lt;XmlEvent&gt;\n    &lt;EVENT_INSTANCE&gt;\n      &lt;EventType&gt;ALTER_TABLE&lt;\/EventType&gt;\n      &lt;PostTime&gt;2017-10-27T14:36:34.113&lt;\/PostTime&gt;\n      &lt;SPID&gt;62&lt;\/SPID&gt;\n      &lt;ServerName&gt;BARBKESS24\\MSSQL2017RTM&lt;\/ServerName&gt;\n      &lt;LoginName&gt;REDMOND\\barbkess&lt;\/LoginName&gt;\n      &lt;UserName&gt;dbo&lt;\/UserName&gt;\n      &lt;DatabaseName&gt;AdventureWorksDW2017&lt;\/DatabaseName&gt;\n      &lt;SchemaName&gt;dbo&lt;\/SchemaName&gt;\n      &lt;ObjectName&gt;FactResellerSales&lt;\/ObjectName&gt;\n      &lt;ObjectType&gt;TABLE&lt;\/ObjectType&gt;\n      &lt;AlterTableActionList&gt;\n        &lt;Create&gt;\n          &lt;Constraints&gt;\n            &lt;Name&gt;FK_FactResellerSales_DimCurrency&lt;\/Name&gt;<\/pre>\n<p>This can be a handy way to combine XML and non-XML data quickly and easily, if needed.<\/p>\n<p>One final way to create XML data in SQL Server is somewhat obvious, but often overlooked, and is to do so directly:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">UPDATE DatabaseLog\n\tSET XmlEvent = '\n&lt;EVENT_INSTANCE&gt;\n  &lt;EventType&gt;ALTER_TABLE&lt;\/EventType&gt;\n  &lt;PostTime&gt;2017-10-27T14:36:34.120&lt;\/PostTime&gt;\n  &lt;SPID&gt;62&lt;\/SPID&gt;\n  &lt;ServerName&gt;BARBKESS24\\MSSQL2017RTM&lt;\/ServerName&gt;\n  &lt;LoginName&gt;REDMOND\\barbkess&lt;\/LoginName&gt;\n  &lt;UserName&gt;dbo&lt;\/UserName&gt;\n  &lt;DatabaseName&gt;AdventureWorksDW2017&lt;\/DatabaseName&gt;\n  &lt;SchemaName&gt;dbo&lt;\/SchemaName&gt;\n  &lt;ObjectName&gt;FactSalesQuota&lt;\/ObjectName&gt;\n  &lt;ObjectType&gt;TABLE&lt;\/ObjectType&gt;\n  &lt;AlterTableActionList&gt;\n    &lt;Create&gt;\n      &lt;Constraints&gt;\n        &lt;Name&gt;FK_FactSalesQuota_DimEmployee&lt;\/Name&gt;\n        &lt;Name&gt;FK_FactSalesQuota_DimDate&lt;\/Name&gt;\n      &lt;\/Constraints&gt;\n    &lt;\/Create&gt;\n  &lt;\/AlterTableActionList&gt;\n  &lt;TSQLCommand&gt;\n    &lt;SetOptions ANSI_NULLS=\"ON\" ANSI_NULL_DEFAULT=\"ON\" ANSI_PADDING=\"ON\" QUOTED_IDENTIFIER=\"ON\" ENCRYPTED=\"FALSE\" \/&gt;\n    &lt;CommandText&gt;ALTER TABLE [dbo].[FactSalesQuota] ADD \n    CONSTRAINT [FK_FactSalesQuota_DimEmployee] FOREIGN KEY([EmployeeKey])\n\t\t\tREFERENCES [dbo].[DimEmployee] ([EmployeeKey]),\n    CONSTRAINT [FK_FactSalesQuota_DimDate] FOREIGN KEY([DateKey])\n\t\t\tREFERENCES [dbo].[DimDate] ([DateKey])&lt;\/CommandText&gt;\n  &lt;\/TSQLCommand&gt;\n&lt;\/EVENT_INSTANCE&gt;'\nFROM dbo.DatabaseLog\nWHERE DatabaseLogID = 90;<\/pre>\n<p>If XML data already exists outside of a table, variable, or parameter, its value can be updated in the same way as if it were any other SQL Server data type.<\/p>\n<h2>Updating XML<\/h2>\n<p>There are a variety of ways to update XML using standard XML methods. The following are a handful of ways to make inline changes to XML using the XQuery method<em> modify()<\/em>, without the need to rewrite the entire document.<\/p>\n<p><code>modify()<\/code> may be used to insert, delete, and update values within an XML document. This is very much a scalpel that can meticulously adjust XML values without creating change throughout an entire document.<\/p>\n<p>Note that SQL Server expects values to be modified inline as part of the syntax. An attempt to update XML like this will result in an error message:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">UPDATE DatabaseLog\n\tSET XmlEvent = XmlEvent.modify('insert &lt;color&gt;Orange&lt;\/color&gt; after (\/EVENT_INSTANCE\/EventType)[1]')\nFROM dbo.DatabaseLog\nWHERE DatabaseLogID = 90;<\/pre>\n<p>This will throw a confusing error:<\/p>\n<p><code>Msg 8137, Level 16, State 1, Line 168<\/code><br \/><code>Incorrect use of the XML data type method 'modify'. A non-mutator method is expected in this context.<\/code><\/p>\n<p>Could that be any more confusing?! If attempts to use <code>modify()<\/code> are failing with an error like this, be sure to check the syntax being used and verify it is exactly as demonstrated. Once working, it is easy enough to adjust, but getting it right the first time can be a bit awkward.<\/p>\n<p>It is a bit counter-intuitive to what we are used to, but when updating XML with an <code>UPDATE\u2026SET<\/code> statement, there is no equals operator. Simply use SET with the column and modify to get the desired results.<\/p>\n<p>Consider the following XML sample:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">DECLARE @XMLSample XML = '\n&lt;Log&gt;\n  &lt;DatabaseLogID&gt;89&lt;\/DatabaseLogID&gt;\n  &lt;PostTime&gt;2017-10-27T14:36:34.113&lt;\/PostTime&gt;\n  &lt;Schema&gt;dbo&lt;\/Schema&gt;\n  &lt;Object&gt;FactResellerSales&lt;\/Object&gt;\n&lt;\/Log&gt;\n&lt;Log&gt;\n  &lt;DatabaseLogID&gt;90&lt;\/DatabaseLogID&gt;\n  &lt;PostTime&gt;2017-10-27T14:36:34.120&lt;\/PostTime&gt;\n  &lt;Schema&gt;dbo&lt;\/Schema&gt;\n  &lt;Object&gt;FactSalesQuota&lt;\/Object&gt;\n&lt;\/Log&gt;';<\/pre>\n<p>What if there was a need to add a username to only the second log entry? This can be done using <code>modify()<\/code> and the <code>insert\u2026into<\/code>\u2026 syntax:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">SET @XMLSample.modify('insert &lt;username&gt;EdPollack&lt;\/username&gt; into (\/Log)[2]');\nSELECT @XMLSample;<\/pre>\n<p>This query will add the <code>username<\/code> attribute to the end of the <code>Log<\/code> tag. Specifying <code>[2]<\/code> tells the query to insert the tag (<code>username<\/code>) and its associated value (<code>EdPollack<\/code>) to only the second <code>Log<\/code> entry in the XML document. The result is as follows:<\/p>\n<pre class=\"lang:none theme:none\">&lt;Log&gt;\n  &lt;DatabaseLogID&gt;89&lt;\/DatabaseLogID&gt;\n  &lt;PostTime&gt;2017-10-27T14:36:34.113&lt;\/PostTime&gt;\n  &lt;Schema&gt;dbo&lt;\/Schema&gt;\n  &lt;Object&gt;FactResellerSales&lt;\/Object&gt;\n&lt;\/Log&gt;\n&lt;Log&gt;\n  &lt;DatabaseLogID&gt;90&lt;\/DatabaseLogID&gt;\n  &lt;PostTime&gt;2017-10-27T14:36:34.120&lt;\/PostTime&gt;\n  &lt;Schema&gt;dbo&lt;\/Schema&gt;\n  &lt;Object&gt;FactSalesQuota&lt;\/Object&gt;\n  &lt;username&gt;EdPollack&lt;\/username&gt;\n&lt;\/Log&gt;<\/pre>\n<p>If the numeric index provided does not correspond to a value in the XML document, the query will still succeed without an error, but no changes will be made. This can be handy when there is a need to conditionally add a tag, but only if a list is of a certain length.<\/p>\n<p>A variable or parameter may also be passed into <code>modify()<\/code><em>, <\/em>using a slightly different syntax:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">DECLARE @UserName VARCHAR(100) = 'EdPollack';\nSET @XMLSample.modify('insert &lt;username&gt;{sql:variable(\"@UserName\")}&lt;\/username&gt; into (\/Log)[2]');<\/pre>\n<p>Instead of providing an explicit value, the variable <code>@UserName<\/code> is used instead. This allows parameters and other scalar data to be added to an XML document without the need for dynamic SQL or some other messier solution. The result for this query is identical to the previous example.<\/p>\n<p>This syntax may also be used as part of an <code>UPDATE<\/code> statement against a table, allowing one or many values to be updated in a single batch. Note that the query does not have an equals operator in it:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">UPDATE DatabaseLog\n\tSET XmlEvent.modify('insert &lt;color&gt;Orange&lt;\/color&gt; after (\/EVENT_INSTANCE\/EventType)[1]')\nFROM dbo.DatabaseLog\nWHERE DatabaseLogID = 90;<\/pre>\n<p>This example used <code>after<\/code> instead of <code>into<\/code>, allowing the query to add the new element directly after the first instance of <code>EventType<\/code> within <code>EVENT_INSTANCE<\/code>. The results look like this:<\/p>\n<pre class=\"lang:none theme:none\">&lt;EVENT_INSTANCE&gt;\n  &lt;EventType&gt;ALTER_TABLE&lt;\/EventType&gt;\n  &lt;color&gt;Orange&lt;\/color&gt;\n  &lt;PostTime&gt;2017-10-27T14:36:34.120&lt;\/PostTime&gt;\n  &lt;SPID&gt;62&lt;\/SPID&gt;<\/pre>\n<p>Conditionals may be embedded into a <code>modify()<\/code> call, allowing for changes to be made based on column data or variable values. An example of this would be to check if the color \u201c<code>Orange<\/code>\u201d from the prior example is in the XML document in the expected location. Based on that condition, different values could be inserted. Alternatively, a change could be made under one condition, but not another.<\/p>\n<p>The following example checks to see if the <code>color<\/code> \u201c<code>Orange<\/code>\u201d is found in <code>EVENT_INSTANCE<\/code>. If so, then create a new element with the <code>flavor<\/code> \u201c<code>spicy<\/code>\u201d, otherwise add the <code>flavor<\/code> \u201c<code>sweet<\/code>\u201d:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">UPDATE DatabaseLog\n\tSET XmlEvent.modify('insert if\n\t\t\t\t(\/EVENT_INSTANCE\/color = \"Orange\")\n\t\t\t\tthen element flavor {\"spicy\"}\n\t\t\t\telse element flavor {\"sweet\"}\n\t\t\t\tas first into (\/EVENT_INSTANCE)[1]')\nFROM dbo.DatabaseLog\nWHERE DatabaseLogID = 90;<\/pre>\n<p>I intentionally used some silly examples here as they are very easy to spot in the resulting XML. The results look like this:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">&lt;EVENT_INSTANCE&gt;\n  &lt;flavor&gt;spicy&lt;\/flavor&gt;\n  &lt;EventType&gt;ALTER_TABLE&lt;\/EventType&gt;\n  &lt;color&gt;Orange&lt;\/color&gt;\n  &lt;PostTime&gt;2017-10-27T14:36:34.120&lt;\/PostTime&gt;\n  &lt;SPID&gt;62&lt;\/SPID&gt;<\/pre>\n<p>Note that the text used within the conditional statement is case-sensitive by default and does not follow the object collation that is defined within a database. Even if the database collation is case insensitive, the conditional text will validate an exact match, including case.<\/p>\n<p><em>Note that if you execute this multiple times, you will get multiple flavor tags in the XML.<\/em><\/p>\n<p>Values can be updated within XML using <em>modify()<\/em> in a similar fashion by using the replace option. This example changes the color from its previous value to \u201cPurple\u201d:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">UPDATE DatabaseLog\n\tSET XmlEvent.modify('replace value of (\/EVENT_INSTANCE\/color\/text())[1] with \"Purple\"')\nFROM dbo.DatabaseLog\nWHERE DatabaseLogID = 90;<\/pre>\n<p>In the event that there are multiple elements with the same name, an index may be added to specify which to update:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">UPDATE DatabaseLog\n\tSET XmlEvent.modify('replace value of (\/EVENT_INSTANCE\/color[1]\/text())[1] with \"Purple\"')\nFROM dbo.DatabaseLog\nWHERE DatabaseLogID = 90;<\/pre>\n<p>Since only a single element exists for <code>color<\/code> in this example, the results of both queries are the same.<\/p>\n<p><code>modify()<\/code> can delete from XML documents, as well. The following queries show how the data previously added to <code>XmlEvent<\/code> can be carefully removed.<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">UPDATE DatabaseLog\n\tSET XmlEvent.modify('delete \/EVENT_INSTANCE\/flavor')\nFROM dbo.DatabaseLog\nWHERE DatabaseLogID = 90;\nUPDATE DatabaseLog\n\tSET XmlEvent.modify('delete \/EVENT_INSTANCE\/color')\nFROM dbo.DatabaseLog\nWHERE DatabaseLogID = 90;<\/pre>\n<p>The first <code>UPDATE<\/code> statement deletes the element \u201c<code>flavor<\/code>\u201d and the second query removes the element \u201c<code>color<\/code>\u201d from the XML document. Once executed, the resulting XML looks like it did before any changes had been made to it in this article:<\/p>\n<pre class=\"lang:none theme:none\">&lt;EVENT_INSTANCE&gt;\n  &lt;EventType&gt;ALTER_TABLE&lt;\/EventType&gt;\n  &lt;PostTime&gt;2017-10-27T14:36:34.120&lt;\/PostTime&gt;\n  &lt;SPID&gt;62&lt;\/SPID&gt;\n  &lt;ServerName&gt;BARBKESS24\\MSSQL2017RTM&lt;\/ServerName&gt;\n  &lt;LoginName&gt;REDMOND\\barbkess&lt;\/LoginName&gt;<\/pre>\n<p>If needed, an entire hierarchy of XML elements can be deleted at one time. For example, if the TSQL commands stored in these XML documents were rarely used and were consuming significant space, then they could be deleted using <code>modify()<\/code><em>.<\/em> This script removes <code>TSQLCommand<\/code> for one specific row:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">UPDATE DatabaseLog\n\tSET XmlEvent.modify('delete \/EVENT_INSTANCE\/TSQLCommand\/*')\nFROM dbo.DatabaseLog\nWHERE DatabaseLogID = 90;<\/pre>\n<p>Removing the <code>WHERE<\/code> clause would allow this deletion to occur for all <code>XmlEvent<\/code> XML documents in the table, if needed.<\/p>\n<p>The XQuery method <code>modify()<\/code> has quite a bit of functionality associated with. Full details for how it can be used in SQL Server can be found here:<\/p>\n<p><a href=\"https:\/\/learn.microsoft.com\/en-us\/sql\/t-sql\/xml\/modify-method-xml-data-type?view=sql-server-ver16\">modify() Method (xml Data Type) &#8211; SQL Server | Microsoft Learn<\/a><\/p>\n<p>That article has subsections for various use-cases and method parameters\/details.<\/p>\n<h2>Conclusion<\/h2>\n<p>In part one of this series on XML in SQL Server, we covered the basics of how to parse and query XML, as well as how to modify the data in the XML data.<\/p>\n<p>XML is a bit of a double-edged sword in SQL Server. On the one hand, XML is a commonly used tool for storing and moving data between applications. It is an easy-to-use open standard that has an extensive amount of functionality built-in for querying and updating it. SQL Server provides many ways to read and write XML data, as well as optimizations to improve search speeds and reduce its storage footprint.<\/p>\n<p>On the other edge, despite its convenience, XML is not a relational data format and is not naturally compatible with a transactional database engine such as SQL Server. While storing and retrieving XML is not computationally expensive, searching and updating XML can be slow, arduous processes. The ideal use of XML in SQL Server is as a pass-through to allow data to be written, stored, and retrieved when needed by an application.<\/p>\n<p>If filtering or heavy data manipulation is needed, consider normalizing commonly used XML elements into table columns, allowing for SQL Server to do what SQL Server is best at: managing transactional data. Alternatively, if XML can be stored outside of a transactional database, then doing so may be beneficial.<\/p>\n\n\n<section id=\"my-first-block-block_2baccac2d6639620d1781c9f620de9ee\" class=\"my-first-block alignwide\">\n    <div class=\"bg-brand-600 text-base-white py-5xl px-4xl rounded-sm bg-gradient-to-r from-brand-600 to-brand-500 red\">\n        <div class=\"gap-4xl items-start md:items-center flex flex-col md:flex-row justify-between\">\n            <div class=\"flex-1 col-span-10 lg:col-span-7\">\n                <h3 class=\"mt-0 font-display mb-2 text-display-sm\">Fast, reliable and consistent SQL Server development&#8230;<\/h3>\n                <div class=\"child:last-of-type:mb-0\">\n                                            &#8230;with SQL Toolbelt Essentials. 10 ingeniously simple tools for accelerating development, reducing risk, and standardizing workflows.                                    <\/div>\n            <\/div>\n                                            <a href=\"https:\/\/www.red-gate.com\/products\/sql-toolbelt-essentials\/\" class=\"btn btn--secondary btn--lg\" aria-label=\"Learn more &amp; try for free: Fast, reliable and consistent SQL Server development...\">Learn more &amp; try for free<\/a>\n                    <\/div>\n    <\/div>\n<\/section>\n\n\n<section id=\"faq\" class=\"faq-block my-5xl\">\n    <h2>FAQs: How to store and parse XML in SQL Server<\/h2>\n\n                        <h3 class=\"mt-4xl\">1. How do you convert XML to a string in SQL Server?<\/h3>\n            <div class=\"faq-answer\">\n                <p>Use CAST or CONVERT to transform an XML-typed value to a string: SELECT CAST(XmlColumn AS NVARCHAR(MAX)) FROM YourTable. For the reverse (string to XML), use CAST(StringColumn AS XML). Note that CAST to NVARCHAR(MAX) preserves the full XML content, while NVARCHAR(4000) may truncate large documents. You can also use the .value() method with a specific XPath to extract individual text values directly.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">2. What is the fastest way to parse XML in SQL Server?<\/h3>\n            <div class=\"faq-answer\">\n                <p>For querying individual values from XML, the .value() XQuery method is generally fastest. For shredding XML into rows, the .nodes() method outperforms OPENXML for most use cases. OPENXML with sp_xml_preparedocument is better suited for very large documents processed once. For repeated queries against the same XML column, create a primary XML index followed by secondary indexes (PATH, VALUE, or PROPERTY) matching your query patterns. Avoid repeated CAST operations between XML and string types.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">3. Should you store XML in an XML column or VARCHAR in SQL Server?<\/h3>\n            <div class=\"faq-answer\">\n                <p>Always prefer the native XML data type over VARCHAR. XML-typed columns provide automatic validation (ensuring only well-formed XML is stored), support for XQuery methods (.value, .query, .nodes, .modify), XML-specific indexing, and schema collection binding. Storing XML as VARCHAR loses all these benefits and forces frequent CAST operations. The only exception is when XML content will never be queried by SQL Server and is purely written\/read by the application.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">4. For JSON handling in SQL Server, what are the equivalent approaches?<\/h3>\n            <div class=\"faq-answer\">\n                <p>SQL Server stores JSON in NVARCHAR columns (or the native JSON type in Azure SQL). The equivalents are: OPENJSON (like OPENXML), JSON_VALUE (like .value()), JSON_QUERY (like .query()), JSON_MODIFY (like .modify()), and FOR JSON (like FOR XML). For a detailed guide, see the companion article on storing and parsing JSON in SQL Server.<\/p>\n            <\/div>\n            <\/section>\n","protected":false},"excerpt":{"rendered":"<p>Master XML handling in SQL Server: store XML in typed columns, parse with XQuery and OPENXML, convert between XML and string types, create XML indexes, and validate XML schemas.&hellip;<\/p>\n","protected":false},"author":329827,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":true,"footnotes":""},"categories":[53,143531],"tags":[4151,4217],"coauthors":[101655],"class_list":["post-102257","post","type-post","status-publish","format-standard","hentry","category-featured","category-t-sql-programming-sql-server","tag-sql-server","tag-xml"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/102257","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/329827"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=102257"}],"version-history":[{"count":6,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/102257\/revisions"}],"predecessor-version":[{"id":108787,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/102257\/revisions\/108787"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=102257"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=102257"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=102257"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=102257"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}