How to show table relationships in xml schema derived from tsql query? - sql-server

I found this tsql query that shows the xml schema of a table and it works very well.
SELECT * FROM TableA
FOR XML AUTO, XMLSCHEMA
I am now attempting to show the relationship between tables via this query. I am not sure if this is possible. I have mocked up several test tables and I have not had any luck. Basically, I am creating a one to many relationship between tableA and tableB, then in the xml schema it would show tableA as a parent and tableB would be a child within the schema.
This is what I have been running. :
SELECT * FROM TableA,
dbo.TableB
FOR XML AUTO, XMLSCHEMA
Here is an example xml schema file that I am getting from the above query.
<xsd:schema targetNamespace="urn:schemas-microsoft-com:sql:SqlRowSet7" xmlns:schema="urn:schemas-microsoft-com:sql:SqlRowSet7" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:sqltypes="http://schemas.microsoft.com/sqlserver/2004/sqltypes" elementFormDefault="qualified">
<xsd:import namespace="http://schemas.microsoft.com/sqlserver/2004/sqltypes" schemaLocation="http://schemas.microsoft.com/sqlserver/2004/sqltypes/sqltypes.xsd" />
<xsd:element name="TableA">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="columnA" type="sqltypes:int" />
<xsd:element name="columnB">
<xsd:simpleType>
<xsd:restriction base="sqltypes:varchar" sqltypes:localeId="1033" sqltypes:sqlCompareOptions="BinarySort">
<xsd:maxLength value="50" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element ref="schema:dbo.tylersTestTable2" minOccurs="0" maxOccurs="unbounded" />
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="TableB">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="columnA" type="sqltypes:int" />
<xsd:element name="columnB">
<xsd:simpleType>
<xsd:restriction base="sqltypes:varchar" sqltypes:localeId="1033" sqltypes:sqlCompareOptions="BinarySort">
<xsd:maxLength value="50" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="fkToTableA" type="sqltypes:int" />
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:schema>
As you can see it is displaying the tables as equal levels within the schema and not really displaying a parent child relationship.
I am kind of at a loss here as I am no dba, but I have to do this for a ton of tables and I don't want to have to write them all by hand.
Any ideas on this one?

Tables in TSQL do not have true parent-child relationships. Each table is at the same level and foreign key relationships may cause constraints on data in one or both tables. Logically, one table may be a parent and one a child, but in no way does that mean they are a physical hierarchy. You may be able to generate XML like you're asking for with a modeling tool like ERwin that interprets the physical model into a logical one but TSQL really only sees the physical model.

Related

How to use datetime2 type in XML SCHEMA?

I'm trying to use SQL Server XML SCHEMA types like datetime2 in collections and tables
CREATE XML SCHEMA COLLECTION [XmlValuesSchemaCollection_datetime2] AS
'<?xml version="1.0"?>
<xsd:schema
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:sqltypes="http://schemas.microsoft.com/sqlserver/2004/sqltypes/sql2008/SqlTypes.xsd"
attributeFormDefault="unqualified" elementFormDefault="qualified">
<xsd:element name="datetime2" type="xsd:datetime2"/>
</xsd:schema>';
GO
CREATE TABLE XmlValuesTable_datetime2 (
[uid] [int] IDENTITY PRIMARY KEY,
v XML(XmlValuesSchemaCollection_datetime2) NOT NULL
);
GO
INSERT INTO XmlValuesTable_datetime2 (v)
VALUES (N'<datetime2 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">2014-06-18 06:39:05.190</datetime2>');
GO
but I have error Reference to an undefined name 'datetime2' within namespace 'http://www.w3.org/2001/XMLSchema'. Same with type="xsd:datetime2" - error
Reference to an undefined name 'datetime2' within namespace 'http://schemas.microsoft.com/sqlserver/2004/sqltypes/sql2008/SqlTypes.xsd'
It suppose to work somehow, types described in https://learn.microsoft.com/en-us/previous-versions/sql/sql-server-2008-r2/bb677236(v=sql.105)?redirectedfrom=MSDN but unfortunately I have no clue what is wrong.
Erland Sommarskog answered at msdn
Solution is
CREATE XML SCHEMA COLLECTION [XmlValuesSchemaCollection_datetime2] AS
'<?xml version="1.0"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:sqltypes="http://schemas.microsoft.com/sqlserver/2004/sqltypes" attributeFormDefault="unqualified" elementFormDefault="qualified">
<xsd:import namespace="http://schemas.microsoft.com/sqlserver/2004/sqltypes" schemaLocation="http://schemas.microsoft.com/sqlserver/2004/sqltypes/sql2008/sqltypes.xsd"/>
<xsd:element name="datetime2" type="sqltypes:datetime2"/>
</xsd:schema>';

How to write `select ... FOR XML` queries that generates an xml and an xsd so, that they are kitchen ready for an SQLXMLBulkLoad?

I'm looking for the most easy way to export a table (or a part of it) to an xml file and then import this xml file into a corresponding table in some other database.
The principle I found is very simple:
Export: on the source database I generate an xml string and also an xsd schema string by adding a FOR XML root('Data') and FOR XML, XMLSCHEMA clauses to the select query.
Import: on the target database I bulkload the generated xml file by SQLXMLBulkLoad using the generated xsd.
But I can't do exactly this. Between the export and the import I have to make some minor modifications in the xsd schema.
For example, I generate the xml and the xsd strings by the following queries:
select top 3 * FROM myTable
FOR XML AUTO, ELEMENTS
,Root('Data')
and
SELECT top 0 * FROM myTable
FOR XML AUTO, ELEMENTS
,XMLSCHEMA
The resulting generated.xml and generated.xsd look so:
<Data>
<myTable>
<field1>value11</field1>
...
<field1>value1n</field1>
</myTable>
<myTable>
<field1>value21</field1>
...
<field1>value2n</field1>
</myTable>
<myTable>
<field1>value31</field1>
...
<field1>value3n</field1>
</myTable>
</Data>
and
<xsd:schema
targetNamespace="urn:schemas-microsoft-com:sql:SqlRowSet1"
xmlns:schema="urn:schemas-microsoft-com:sql:SqlRowSet1"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:sqltypes="http://schemas.microsoft.com/sqlserver/2004/sqltypes"
elementFormDefault="qualified">
<xsd:import namespace="http://schemas.microsoft.com/sqlserver/2004/sqltypes" schemaLocation="http://schemas.microsoft.com/sqlserver/2004/sqltypes/sqltypes.xsd"/>
<xsd:element name="myTable">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="field1" type="..." .../>
...
<xsd:element name="fieldn" type="..." ... />
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:schema>
But if I want to bulkload by a vb script like this
set objBL = CreateObject("SQLXMLBulkLoad.SQLXMLBulkload.4.0")
objBL.ConnectionString = "provider=SQLOLEDB.1;data source=localhost\SQLEXPRESS;database=Testdb;uid=sa;pwd=*****"
objBL.ErrorLogFile = ".\error.xml"
objBL.KeepIdentity = False
objBL.Execute "generated.xsd", "generated.xml"
set objBL=Nothing
then this works only if I make the following modifications in the generated.xsd
remove this xsd:schema attribute: targetNamespace="urn:schemas-microsoft-com:sql:SqlRowSet1"
add this xsd:schema attribute: xmlns:sql="urn:schemas-microsoft-com:mapping-schema"
Replace the <myTable> element by a sequence of <myTable> elements and wrap the whole into a <xsd:element name="Data" sql:is-constant="1"> element
Add the attributes maxOccurs="unbounded" sql:relation="myTable" to the <myTable> element
So, the modified xsd which is really suitable to bulkload the generated xml by SQLXMLBulkLoad looks like this:
<xsd:schema
xmlns:sql="urn:schemas-microsoft-com:mapping-schema"
xmlns:schema="urn:schemas-microsoft-com:sql:SqlRowSet1"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:sqltypes="http://schemas.microsoft.com/sqlserver/2004/sqltypes"
elementFormDefault="qualified">
<xsd:import namespace="http://schemas.microsoft.com/sqlserver/2004/sqltypes" schemaLocation="http://schemas.microsoft.com/sqlserver/2004/sqltypes/sqltypes.xsd"/>
<xsd:element name="Data" sql:is-constant="1">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="myTable" maxOccurs="unbounded" sql:relation="myTable">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="field1" type="..." .../>
...
<xsd:element name="fieldn" type="..." ... />
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:schema>
I wonder if the generating sql queries and/or the vbscript can be modified so, that the generated xml and xsd work with the vbscript without any manual modification?
As your question has asked, the SIMPLEST way, which can be highly portable and compatible, can use simple XML data sets like so. Assuming you have two SQL servers. I have used this method between SQL>Excel, SQL>SQL, SQL>Oracle.
You can do on SQL, as a stored procedure call:
DECLARE #xml xml
SET #XML = (
SELECT field1, field2
FROM table
FOR XML RAW('row'), ROOT('data'), ELEMENTS
)
This will return:
<root>
<row>
<field1>SomeData</field1>
<field2>SomeOtherData</field2>
</row>
</root>
Once you have the XML, you simply read into the TARGET database using something like this:
INSERT INTO TargetDatabase.TargetTable(field1, field2)
SELECT tbl.c.value('field1','varchar(1000)'), tbl.c.value('field2','bigint')
FROM #XML.nodes('/root[1]/row) tbl(c)
You can also run queries, etc on the incoming XML data quite easily if needed:
INSERT INTO TargetTable(field1, field2)
SELECT tbl.c.value('field1','varchar(1000)'), tbl.c.value('field2','bigint')
FROM #XML.nodes('/root[1]/row) tbl(c)
WHERE tbl.c.value('field2','bigint') > 100 or tbl.c.value('field1','varchar(1000)') Like '%fish%'
Very efficient and very fast. No need to mess around with schemas. Only thing is that the field types are hard coded.. so you need to custom build each of the SP read/writes.
If you have some sort of agent (e.g. MS Excel doing this), its just a simple case of writing a stored procedure to receive the XML data in the target database:
CREATE PROCEDURE sp_target_for_XML
#XML xml

SQL Server: is it possible to change the value of a complex XML element?

I need to change a value within a XML element - for this untyped version it works this way:
declare #X xml=
'<translations>
<value lang="en-US">example</value>
<value lang="de-DE">Beispiel</value>
</translations>';
set #X.modify('replace value of (/translations/value[#lang="en-US"]/text())[1] with "replacedValue"');
select #X.value('(/translations/value[#lang="en-US"])[1]','varchar(max)');
The select return the "replacedValue" for the "value" element with attribute lang="en-US".
Unfortunately I have to do this for a XML attribute in a database which is typed which the following XML schema:
CREATE XML SCHEMA COLLECTION [dbo].[LocaleSchema] AS N'<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"><xsd:element name="translations"><xsd:complexType><xsd:complexContent><xsd:restriction base="xsd:anyType"><xsd:sequence minOccurs="0" maxOccurs="unbounded"><xsd:element name="value"><xsd:complexType><xsd:simpleContent><xsd:extension base="xsd:string"><xsd:attribute name="lang" type="language" /></xsd:extension></xsd:simpleContent></xsd:complexType></xsd:element></xsd:sequence></xsd:restriction></xsd:complexContent></xsd:complexType></xsd:element><xsd:simpleType name="language"><xsd:restriction base="xsd:string"><xsd:enumeration value="de-DE" /><xsd:enumeration value="en-US" /></xsd:restriction></xsd:simpleType></xsd:schema>'
For better readability afterwards only the XML schema pretty-printed:
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:element name="translations">
<xsd:complexType>
<xsd:complexContent>
<xsd:restriction base="xsd:anyType">
<xsd:sequence minOccurs="0" maxOccurs="unbounded">
<xsd:element name="value">
<xsd:complexType>
<xsd:simpleContent>
<xsd:extension base="xsd:string">
<xsd:attribute name="lang" type="language" />
</xsd:extension>
</xsd:simpleContent>
</xsd:complexType>
</xsd:element>
</xsd:sequence>
</xsd:restriction>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
<xsd:simpleType name="language">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="de-DE" />
<xsd:enumeration value="en-US" />
</xsd:restriction>
</xsd:simpleType>
</xsd:schema>
As you can see here the "value" XML element is a complex type. According to the documentation the modify() function is only valid for simple types. (besides also the text() function is only valid for simple types)
So afterwards the SQL statements from above for the typed content - which causes an error when trying to modify:
declare #X xml (CONTENT [dbo].[LocaleSchema])=
'<translations>
<value lang="en-US">example</value>
<value lang="de-DE">Beispiel</value>
</translations>';
set #X.modify('replace value of (/translations/value[#lang="en-US"]/text())[1] with "replacedValue"');
Is there a suggestion to work around? Or any other possibility to change the XML attribute? (I need to use this in an UPDATE statement in real life of course)
Thank you in advance!
This works...
CREATE XML SCHEMA COLLECTION [dbo].[LocaleSchema] AS N'<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"><xsd:element name="translations"><xsd:complexType><xsd:complexContent><xsd:restriction base="xsd:anyType"><xsd:sequence minOccurs="0" maxOccurs="unbounded"><xsd:element name="value"><xsd:complexType><xsd:simpleContent><xsd:extension base="xsd:string"><xsd:attribute name="lang" type="language" /></xsd:extension></xsd:simpleContent></xsd:complexType></xsd:element></xsd:sequence></xsd:restriction></xsd:complexContent></xsd:complexType></xsd:element><xsd:simpleType name="language"><xsd:restriction base="xsd:string"><xsd:enumeration value="de-DE" /><xsd:enumeration value="en-US" /></xsd:restriction></xsd:simpleType></xsd:schema>';
GO
declare #X xml (CONTENT [dbo].[LocaleSchema])=
'<translations>
<value lang="en-US">example</value>
<value lang="de-DE">Beispiel</value>
</translations>';
set #X.modify('replace value of (/translations/value[#lang="en-US"])[1] with "replacedValue"');
SELECT #x;
GO
--clean up
--DROP XML SCHEMA COLLECTION dbo.LocaleSchema;

How to get rid of XML "_x0040_" attribute prefix in SQL?

I'm trying to export a table's content to XML, like this:
CREATE TABLE TEMPTABLE_Results ([#Id] int)
INSERT INTO TEMPTABLE_Results
SELECT 1 as [#Id];
SELECT * FROM TEMPTABLE_Results
FOR XML RAW('Node'), TYPE, XMLSCHEMA('Node')
DROP TABLE TEMPTABLE_Results
But the result has an annoying "x0040" prefix in the attribute:
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:sqltypes="http://schemas.microsoft.com/sqlserver/2004/sqltypes" targetNamespace="Node" elementFormDefault="qualified">
<xsd:import namespace="http://schemas.microsoft.com/sqlserver/2004/sqltypes" schemaLocation="http://schemas.microsoft.com/sqlserver/2004/sqltypes/sqltypes.xsd" />
<xsd:element name="Node">
<xsd:complexType>
<xsd:attribute name="_x0040_Id" type="sqltypes:int" />
</xsd:complexType>
</xsd:element>
</xsd:schema>
<Node xmlns="Node" _x0040_Id="1" />
I was previously using a table-type variable, but according to this answer, the prefix is added when the table is temporary; but even with a regular table, the prefix is still there.
Any help is much appreciated.
Found: no need to specify "#" for field in order to get it as an attribute instead of an element.
This yields the desired result:
CREATE TABLE TEMPTABLE_Results (Id int)
INSERT INTO TEMPTABLE_Results
SELECT 1 as Id;
SELECT * FROM TEMPTABLE_Results
FOR XML RAW('Node'), TYPE, XMLSCHEMA('Node')
DROP TABLE TEMPTABLE_Results
The resulting XML is:
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:sqltypes="http://schemas.microsoft.com/sqlserver/2004/sqltypes" targetNamespace="Node" elementFormDefault="qualified">
<xsd:import namespace="http://schemas.microsoft.com/sqlserver/2004/sqltypes" schemaLocation="http://schemas.microsoft.com/sqlserver/2004/sqltypes/sqltypes.xsd" />
<xsd:element name="Node">
<xsd:complexType>
<xsd:attribute name="Id" type="sqltypes:int" />
</xsd:complexType>
</xsd:element>
</xsd:schema>
<Node xmlns="Node" Id="1" />

Use XQuery to get at this data

I am new to XQuery and am having some problems with it. Here is my example.
I have this variable:
declare #xmlDoc XML
it has the following xml stored in it:
<?xml version="1.0" encoding="utf-8"?>
<NewDataSet>
<xs:schema id="NewDataSet" xmlns="" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xs:element name="NewDataSet" msdata:IsDataSet="true" msdata:Locale="">
<xs:complexType>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="Table1">
<xs:complexType>
<xs:sequence>
<xs:element name="Sharedparam" type="xs:string" minOccurs="0" />
<xs:element name="Antoher" type="xs:string" minOccurs="0" />
<xs:element name="RandomParam2" type="xs:string" minOccurs="0" />
<xs:element name="MoreParam" type="xs:string" minOccurs="0" />
<xs:element name="ResultsParam" type="xs:string" minOccurs="0" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:choice>
</xs:complexType>
</xs:element>
</xs:schema>
<Table1>
<Sharedparam>shared</Sharedparam>
<Antoher>sahre</Antoher>
<RandomParam2>Good stuff</RandomParam2>
<MoreParam>and more</MoreParam>
<ResultsParam>2</ResultsParam>
</Table1>
<Table1>
<Sharedparam>Hey</Sharedparam>
<Antoher>what </Antoher>
<RandomParam2>do you</RandomParam2>
<MoreParam>think</MoreParam>
<ResultsParam>2</ResultsParam>
</Table1>
<Table1 />
</NewDataSet>
How can I select all the values of Sharedparam? (Or really any decent query that returns values (not xml) would be great.)
What I am really looking to do is get a result set like this:
Name Value1 Value2 Value3 Value4
Sharedparam shared Hey Null Null
Another share what Null Null
....
This would have me ignoring any data beyond "Value4" (and that is acceptable for my use of this data).
Try something like this:
SELECT
TBL.SParam.value('(.)[1]', 'varchar(50)')
FROM
#xmldoc.nodes('/NewDataSet/Table1/Sharedparam') AS TBL(SParam)
Gives me an output of:
(No column name)
shared
Hey
Update: if you want to get at all the XML elements and their values inside the <Table1> elements, you can use this XQuery:
SELECT
TBL.SParam.value('local-name(.)[1]', 'varchar(50)') 'Attribute',
TBL.SParam.value('(.)[1]', 'varchar(50)') 'Value'
FROM
#xmldoc.nodes('/NewDataSet/Table1/*') AS TBL(SParam)
which outputs:
Attribute Value
Sharedparam shared
Antoher sahre
RandomParam2 Good stuff
MoreParam and more
ResultsParam 2
Sharedparam Hey
Antoher what
RandomParam2 do you
MoreParam think
ResultsParam 2
Update #2: to get the values of the first <Table1> and the second <Table1> XML node next to one another, you need to do two calls to .nodes() - once retrieving the first node, the other time the second one. It gets a bit hairy, especially if you want to extend that even further - and performance is going to be abysmal - but it works :-)
SELECT
TBL.SParam.value('local-name(.)[1]', 'varchar(50)') 'Attribute',
TBL.SParam.value('(.)[1]', 'varchar(50)') 'Value 1',
TBL2.SParam2.value('(.)[1]', 'varchar(50)') 'Value 2'
FROM
#xmldoc.nodes('/NewDataSet/Table1[1]/*') AS TBL(SParam)
INNER JOIN
#xmldoc.nodes('/NewDataSet/Table1[2]/*') AS TBL2(SParam2) ON TBL.SParam.value('local-name(.)[1]', 'varchar(50)') = TBL2.SParam2.value('local-name(.)[1]', 'varchar(50)')
Gives an output of:
Attribute Value 1 Value 2
Sharedparam shared Hey
ResultsParam 2 2
RandomParam2 Good stuff do you
Antoher sahre what
MoreParam and more think
That's an odd layout. You expect columns Sharedparam,Antoher etc: not rows.
And if I read that right, Table1 maxOccurs="unbounded" which means an variable number of columns = not SQL which is fixed column type and number
To read each tag as a column (which are fixed and finite) you'd do this:
SELECT
x.item.value('(Sharedparam)[1]', 'varchar(100)') AS Sharedparam,
x.item.value('(Antoher)[1]', 'varchar(100)') AS Antoher,
x.item.value('(SharedRandomParam2param)[1]', 'varchar(100)') AS RandomParam2,
...
FROM
#xmlDoc.nodes('/NewDataSet/Table1') x(item)

Resources