Handling namespaces in SQL XML Bulk Load can be a headache as there’s not really much documentation about it. However while laborious, it is basically following the normal XML Schema structure.
I’ll not go deep into the SQL XML Bulk Load syntax in this blog post, if you’re looking for that, I’ll advice you to read my SQL XML Bulk Load – basic XSD syntax or SQL XML Bulk Load – more XSD syntax blog posts before this one.

I’ve made this simplified example to illustrate. Suppose you have an XML file something akin to this:

<?xml version="1.0" encoding="utf-8"?>
<Catalogue xmlns:sdt="urn:oasis:names:specification:ubl:schema:xsd:SpecializedDatatypes-2"
           xmlns="urn:oasis:names:specification:ubl:schema:xsd:Catalogue-2"
           xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2"
           xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="urn:oasis:names:specification:ubl:schema:xsd:Catalogue-2 UBL-Catalogue-2.0.xsd">
  <cbc:UBLVersionID>2.0</cbc:UBLVersionID>
   <cac:ValidityPeriod>
    <cbc:StartDate>2011-03-02</cbc:StartDate>
  </cac:ValidityPeriod>
   <cac:CatalogueLine>
    <cbc:ID>11002</cbc:ID>    
  </cac:CatalogueLine>
   <cac:CatalogueLine>
    <cbc:ID>11003</cbc:ID>
  </cac:CatalogueLine>
</Catalogue>

I have two custom namespaces, and a mixture of the elements within each namespace. This is simplified, but once the logic is known, it is just expanding it with more namespaces, and more mapping.

First we need to make the base XML schema/XSD file. A way to handle namespaces in the XSD file is to use import statements and split each namespace element in to its own separate file. And that technique works for SQL XML Bulk Load as well.
So we’ll make the base XSD like this

<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"            
           xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2"
           xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2"
           xmlns:xs="http://www.w3.org/2001/XMLSchema"
           xmlns:sql="urn:schemas-microsoft-com:mapping-schema">

  <xs:annotation>
    <xs:appinfo>
      <sql:relationship name="SomeName"
                        parent="Import"
                        parent-key="PK"
                        child="Import2"
                        child-key="FK" />
    </xs:appinfo>
  </xs:annotation>

  <xs:import namespace="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2" schemaLocation="cbc.xsd" />
  <xs:import namespace="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2" schemaLocation="cac.xsd" />

  <xs:element name="Catalogue" sql:relation="Import">
    <xs:complexType>
      <xs:sequence>
        <xs:element ref="cbc:UBLVersionID" />
        <xs:element ref="cac:ValidityPeriod" />
        <xs:element maxOccurs="unbounded" ref="cac:CatalogueLine" />
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>

Notice that each of the 3 elements simply has a reference to another element. So the XML tag cbc:UBLVersionID will in the XSD simply be element which ref=”cbc:UBLVersionID”, just as cac:ValidityPeriod element is also just a reference.
The key here however is that I’ve imported two namespaces with a schema location

 <xs:import namespace="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2" schemaLocation="cbc.xsd" />
<xs:import namespace="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2" schemaLocation="cac.xsd" />

These lines will tell the base schema file where to look for the specific definitions for the namespaces.

Now, I can make a specific schema file for the cbc namespace and the cac namespace.
The cac one looks like

<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:tns="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2"
                attributeFormDefault="unqualified" elementFormDefault="qualified"
                targetNamespace="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2"
           xmlns:sql="urn:schemas-microsoft-com:mapping-schema"
           xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2"
           xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <xs:import namespace="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2" schemaLocation="cbc.xsd" />

  <xs:element name="ValidityPeriod" sql:is-constant="1" >
    <xs:complexType>
      <xs:sequence>
        <xs:element ref="cbc:StartDate" />
      </xs:sequence>
    </xs:complexType>
  </xs:element>
  <xs:element name="CatalogueLine" sql:relationship="SomeName" sql:relation="Import2">
    <xs:complexType>
      <xs:sequence>
        <xs:element ref="cbc:ID"  />
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>

Here the same principle of using import statement to map a namespace

<xs:import namespace="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2" schemaLocation="cbc.xsd" />

is used similar to the base file.

The cac file is used to define the contents of all cac namespace elements (the naming of the file is irrelevant, I simply named them this way to help myself keeping track). And where needed in this cac file, a reference is made to an element in the cbc file (in this case cbc:StartDate and cbc:ID).
The cbc file will then qualify all cbc elements

<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:tns="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2"
           attributeFormDefault="unqualified" elementFormDefault="qualified" targetNamespace="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2"
           xmlns:xs="http://www.w3.org/2001/XMLSchema"
           xmlns:sql="urn:schemas-microsoft-com:mapping-schema"

           >
  <xs:element name="UBLVersionID" type="xs:decimal" sql:field="VersionID" />
  <xs:element name="StartDate" type="xs:date" sql:field="DateX" />
  <xs:element name="ID" type="xs:unsignedShort" sql:field="LineID" />
</xs:schema>

As can be seen, the sql:field, sql:relationship and sql:relation are all used across all 3 files.
In the base file, I’ve defined a relationship, and I’m using the relationship in the cac file around a cbc element.

Running this through a SQL XML Bulk Load, and I get my data imported into two tables like this

This was just a simplified example, but I hope it helps to show how the syntax is build up in a XSD and then spice it up with some SQL mapping.

For more insight into SQL XML Bulk Load and the syntax, you can check out my other posts on the subject.

I have posted a couple of times about the XML/XSD syntax used for SQL XML Bulk Load but mostly the syntax have been pretty basic, so I wanted to expand a little on the syntax with a more complex example.

So this time I’ll have an XML file structured like this. As can be seen this XML file consists of a couple of nested tags and is a structure that’s realistic when it comes to data imports. In fact it is a minimization of an existing one I have used for work, where I have removed duplicated structures to provide a cleaner example.
The file consists of repeated details tags with content and attributes used, a product files section containing a repeated tag and a nesting of Product Lists. It should cover most of the situations which one would encounter.
The only thing I do not use in this instance is composite keys, but they’re no different compared to using a singular one. I’ve written about composite keys and SQL XML Bulk Load here.
Anyways, let’s tackle the XSD for this file. (If new to the SQL XML Bulk Load, please read my SQL XML Bulk Load – basic XSD syntax post first)

The first tag, ProductID, is simple enough and covered in the basic syntax blog post.
It requires a database table containing this field. Any subsequent values placed on the same level of the XML file would be included into the same table.
So we’ll make a Product table:
CREATE TABLE Product(
ProductID INT
NOT NULL
)

The XSD field will then be mapped to this table. And because I’ve named the field the same as the tag in the XML file I do not need to map the field.
This gives me the segment:
<xs:element Name=“Product” sql:relation=“Product” maxOccurs=“unbounded”>
<
xs:complexType
>
<
xs:sequence
>
<
xs:element Name=“ProductID” />

The details section is next. For this we need to set up a relationship because the section can be repeated several times, so we need to map it out into a related table.
For this section we’ll need the table:
CREATE TABLE Details(
Name VARCHAR(MAX
) NULL,
[Description] VARCHAR(MAX
) NULL,
LanguageCode VARCHAR(5
) NOT NULL,
ProductID INT
NOT NULL
)

This can then hold the Name and Description tags from the section, and the attribute “LanguageCode” and the key ProductID to map it to the Product table/Product section.
Because we need a relationship we’ll have to map it out:
<sql:relationship Name=“Detail”
parent=
“Product”
parent-key=
“ProductID”
child=
“Details”
child-key=
“ProductID”
/>

And this then provides the following section or the XSD
<xs:element Name=“Details” sql:relation=“Details” sql:relationship=“Detail”>
<
xs:complexType
>
<
xs:sequence
>
<
xs:element Name=“Name”
/>
<
xs:element Name=“Description”
/>
</
xs:sequence
>
<
xs:attribute Name=“LanguageCode” Type=“xs:string” use=“required”
/>
</
xs:complexType
>
</
xs:element
>

As can be seen we map the section to the relationship and the table relation.
For the section of Files, we’ll need to expand a little more. Again we need a relationship to tell the bulk load which keys to use.
First the table to import into:
CREATE TABLE Files (
ProductID INT
NOT NULL,
[Type] VARCHAR(255
) NULL,
[FileName] VARCHAR(255
) NULL
)

Then the relationship in the XSD file
<sql:relationship Name=“ProductFiles”
parent=
“Product”
parent-key=
“ProductID”
child=
“Files”
child-key=
“ProductID”
/>

Just like the previous one.

The XSD section will then look like the following:
<xs:element Name=“ProductFiles” sql:is-constant=“1″>
<
xs:complexType
>
<
xs:sequence
>
<
xs:element maxOccurs=“unbounded” Name=“FileName”
sql:relationship=“ProductFiles” sql:relation=“Files”>
<
xs:complexType
>
<
xs:simpleContent
>
<
xs:extension base=“xs:string”
>
<
xs:attribute Name=“type” Type=“xs:string” use=“required”
/>
</
xs:extension
>
</
xs:simpleContent
>
</
xs:complexType
>
</
xs:element
>
</
xs:sequence
>
</
xs:complexType
>
</
xs:element>


We have the is-constant=”1” because this section can only exists once. The relationship and relation is put on the “FileName” element. The “tricky” part here is the xs:extension which is needed because otherwise the Bulk Load will throw an error that you’re trying to map multiple FileName elements. <ERROR>

On to the List section.
Now this is a more difficult one, because here we’ll need a double relationship. We need to map the ProductID to the Type and then the Type to the multiple ProductID’s within that section. So we need the following tables:
CREATE TABLE ProductList (
[ID] INT IDENTITY(1,1
) NOT NULL,
ProductID INT
NOT NULL,
Header VARCHAR(255
) NULL
)
CREATE TABLE ProductListProduct
(
ProductListID INT
NOT NULL,
ProductID
INT
)

Here we make an Identity ID in the ProductList which will be the foreign key in the ProductListProduct:ProductListID column.
So we need the two following relationships in our XSD
<sql:relationship Name=“ProductToList”
parent=
“Product”
parent-key=
“ProductID”
child=
“ProductList”
child-key=
“ProductID”
/>

<sql:relationship Name=“ListToProduct”
parent=
“ProductList”
parent-key=
“ID”
child=
“ProductListProduct”
child-key=
“ProductListID”
/>

To map the relationship. The only difference here is that the parent-key for ListToProduct is the identity column from the ProductList table, and it will be used as the child-key in the column ProductListID.
And this gives the following XSD section

<xs:element Name=“ProductList” sql:is-constant=“1″>
<
xs:complexType
>
<
xs:sequence
>
<
xs:element Name=“List” sql:relationship=“ProductToList” sql:relation=“ProductList”
>
<
xs:complexType
>
<
xs:sequence
>
<
xs:element maxOccurs=“unbounded” Name=“ProductID” Type=“xs:integer”
sql:relationship=“ListToProduct” sql:relation=“ProductListProduct”
/>
</
xs:sequence
>
<
xs:attribute Name=“header” Type=“xs:string” use=“required”
/>
</
xs:complexType
>
</
xs:element
>
</
xs:sequence
>
</
xs:complexType
>
</
xs:element
>

Again we have the is-constant annotation.
We then map the element List to the relationship ProductToList as defined previously and to the table ProductList. Because header is then an attribute named the same as the field in the database, it’ll map automatically as usual.
However we’ll then map the inner element “ProductID” within the List element to the next relationship ListToProduct and the table ProductListProduct.

The SQL XML Bulk Load will then automatically handle the identity column in the ProductList table and map it to the ProductListID column in the ProductListProduct table.

This gives us this following complete XSD file

And when running the bulk load with this XML and this XSD we get the following result:

Result for importing SQL XML Bulk Load

A little quick note, when needing composite keys in your relationship in SQL XML Bulk Load the syntax is as follow:

<sql:relationship name=“Detail”
parent
=
“Import.Product”
parent
-KEY=
“ProductID FileID”
child
=
“Import.ProductDetails”
child
-KEY=“ProductID FileID”/>

meaning they’re simply space separated in the relationship annotation. The composite key in this aspect are the columns ProductID and FileID.
As can also be seen in this annotation, I’m using a schema qualifiaction with Import, so if you have other schemas, you can simply write them in the parent/child.

For more syntax, please refer to the blog post SQL XML Bulk Load – basic XSD syntax