I have a SQL table Products with 2 columns as below.
ID
ProductDetails
2
<XML>
3
<XML>
The XML column holds the following data:
<Products>
<product key="0" description="">Product1</product>
<product key="1" description="">Product2</product>
<product key="2" description="">Product3</product>
<product key="3" description="">Product4</product>
<product key="4" description="">Product5</product>
<product key="5" description="">Product6</product>
<product key="6" description="">Product7</product>
<product key="7" description="">Product8</product>
</Products>
How can I get the relevant node from the ProductDetails for ProductTitle?
For example: if the ID column has 3, I need to query the ProductDetails column and create a new column with just the ProductTitle to be Product3.
ID
ProductDetails
ProductTitle
5
<XML>
Product5
3
<XML>
Product3
Any help would be appreciated.
Please try the following.
SQL
-- DDL and sample data population, start
DECLARE #tbl TABLE (ID INT PRIMARY KEY, ProductDetails XML);
INSERT #tbl (ID, ProductDetails) VALUES
(3, N'<Products>
<product key="0" description="">Product1</product>
<product key="1" description="">Product2</product>
<product key="2" description="">Product3</product>
<product key="3" description="">Product4</product>
<product key="4" description="">Product5</product>
<product key="5" description="">Product6</product>
<product key="6" description="">Product7</product>
<product key="7" description="">Product8</product>
</Products>');
-- DDL and sample data population, end
SELECT ID
, ProductDetails.value('(/Products/product[#key=sql:column("ID")]/text())[1]','VARCHAR(20)') AS ProductTitle
FROM #tbl;
Output
ID
ProductTitle
3
Product4
You need to use the XML .nodes() function to access and filter the "product" XML elements, and then the .value() function to extract the desired text value from that node. Both take xpath parameters to specify the data to be extracted.
Try
SELECT P.*, X.N.value('text()[1]', 'nvarchar(max)')
FROM #Products P
CROSS APPLY #ProductXml.nodes('/Products/product[#key=sql:column("P.ID")]') X(N)
or equivilently:
SELECT P.*, PN.ProductName
FROM #Products P
CROSS APPLY (
SELECT ProductName = X.N.value('text()[1]', 'nvarchar(max)')
FROM #ProductXml.nodes('/Products/product[#key=sql:column("P.ID")]') X(N)
) PN
or even
SELECT P.*, PN.ProductName
FROM #Products P
JOIN (
SELECT
ProductKey = X.N.value('#key', 'nvarchar(max)'),
ProductName = X.N.value('text()[1]', 'nvarchar(max)')
FROM #ProductXml.nodes('/Products/product') X(N)
) PN ON PN.ProductKey = P.ID
The subselect in the latter could also be used to build a temp table for more traditional query access.
In the xpath strings,
/Products/product selects all product nodes
[#key = ...] selects the key attribute to be used as a filter
(equality)
sql:column("P.ID") allows a reference back to a column
in the containing SQL
text() is a special selector that chooses the text within the XML element
[1] filters that down to at most a single value (a singleton)
nvarchar(max) defines the result datatype for the .value() function.
See this db<>fiddle.
Related
I have next below XML message:
<Message xmlns="http://test.org">
<Request Promotion="MULANN">
<ExpireDate>23/10/2020 07:10</ExpireDate>
<Entries>
<Entry>
<Product>
<Product Product="T1" />
</Product>
<Payment>
<Privacy>N</Privacy>
</Payment>
</Entry>
<Entry>
<Product>
<Product Product="T2" />
</Product>
<Payment>
<Privacy>N</Privacy>
</Payment>
</Entry>
<Entry>
<Product>
<Product Product="T3" />
</Product>
<Payment>
<Privacy>Y</Privacy>
</Payment>
</Entry>
</Entries>
</Request>
</Message>
For this xml message, I have three times the xml tag Privacy
How can I get the distinct value of privacy for all of them without fetch the error?.
Now I'm using next below query that contain the error:
select distinct x.Promotion, x.Privacy
from TableName a
cross join XMLTable(XMLNAMESPACES('http://test.org' AS "XML"),
'/XML:Message' passing xmltype(xml_info) columns
Promotion VARCHAR2(20) path 'XML:Request/#Promotion'
Iban_TABD CHAR(1) path 'XML:Request/XML:Entries/XML:Entry/XML:Payment/XML:Privacy')x
I'd like to get something like this:
Promotion Privacy
MULANN N
MULANN Y
The problem is that the XML has multiple levels of nesting.
You solve this by chaining the XMLTABLEs.
If not all information is always available in the nested children, an outer join helps.
SELECT DISTINCT
promotion,
privacy
FROM TableName,
XMLTABLE( XMLNAMESPACES( DEFAULT 'http://test.org'),
'/Message' PASSING XMLTYPE(xml_info)
COLUMNS promotion VARCHAR2(20) PATH 'Request/#Promotion',
entries XMLTYPE PATH 'Request/Entries/Entry'
) x1
LEFT OUTER JOIN (
XMLTABLE( XMLNAMESPACES( DEFAULT 'http://test.org'),
'/Entry' PASSING x1.entries
COLUMNS privacy VARCHAR2(1) PATH 'Payment/Privacy'
)) x2
ON 1=1;
Hello Everyone,
I need a help to fetch comma separated list for same node if exists in XML nodes.
My XML is like this which is stored in my database:-
<Event>
<Detail>
<List>
<Element>
<name>ABC</name>
<salary>150</salary>
</Element>
<Element>
<name>PQR</name>
<salary>250</salary>
</Element>
</List>
</Detail>
</Event>
I need to get comma separated name list (ABC,PQR ) from this using xpath oracle query.
I have tried alot and when i get a way like this :-
NVL(VALUE (line).EXTRACT ('/Event/Detail/List/Element/name/text()') .getstringval (),'') Name List
Then , my output was ABCPQR.
No Space or comma was there. Could anyone please help me out for this .
Expected output is :- ABC,PQR
Thanks in advance :)
You're pulling a list of values out of an XML document, rather than a single value, so I think you'd be better off selecting them all into a table using XMLTABLE and then joining them together with LISTAGG.
Here's an example:
SQL> CREATE TABLE xmltest (id INTEGER, x XMLTYPE);
Table created.
SQL> INSERT INTO xmltest (id, x) VALUES (1, XMLTYPE('<Event>
2 <Detail>
3 <List>
4 <Element>
5 <name>ABC</name>
6 <salary>150</salary>
7 </Element>
8 <Element>
9 <name>PQR</name>
10 <salary>250</salary>
11 </Element>
12 </List>
13 </Detail>
14 </Event>'));
1 row created.
SQL> COMMIT;
Commit complete.
SQL> SELECT t.id, LISTAGG(y.name, ',') WITHIN GROUP (ORDER BY ROWNUM)
2 FROM xmltest t,
3 XMLTABLE('//Element' PASSING t.x COLUMNS name VARCHAR2(1000) PATH 'name') y
4 GROUP BY t.id;
ID
----------
LISTAGG(Y.NAME,',')WITHINGROUP(ORDERBYROWNUM)
--------------------------------------------------------------------------------
1
ABC,PQR
There is a function in XPath 2.0, string-join, which will join the string values together with commas. It would seem to be just what you need, but alas it seems Oracle doesn't support it.
I have a table with two fields of type NUMERIC and one field of type XML. Here is a rough sample:
CREATE TABLE books (
ID INT NOT NULL,
price NUMERIC(4,2),
discount NUMERIC(2,2),
book XML
);
The XML value will look something like, say,
<?xml version="1.0" encoding="UTF-8"?>
<book>
<title>Harry Potter</title>
<author>J K Rowling</author>
<Store>
<Name>Burke and Burkins</Name>
<Address>Some St, Somewhere, Some City</Address>
</Store>
</book>
Now my question is, using xml.modify(), how can I add two xpaths under Store with the price and discount with value from books.price and books.discount?
<?xml version="1.0" encoding="UTF-8"?>
<book>
<title>Harry Potter</title>
<author>J K Rowling</author>
<Store>
<Name>Burke and Burkins</Name>
<Address>Some St, Somewhere, Some City</Address>
<Price>value from books.price from the same row</Price>
<Discount>value from books.discount from the same row</Discount>
</Store>
</book>
This is a rough example, so please don't worry about where the XML data came from. Lets just say the book column has the XML data already present.
I know how to update the table with static values with,
UPDATE books
SET book.modify('insert <Price>10.99</Price><Discount>20.00</Discount> after (/book/Store/Address)[1]')
Performance is not a consideration here.
It is not possible to do two modifications in one statement.
In this case you might trick this out by first combining both values and then insert them at once.
I use an updateable CTE to achieve this:
CREATE TABLE books (
ID INT NOT NULL,
price NUMERIC(4,2),
discount NUMERIC(2,2),
book XML
);
--Fill the table with data
INSERT INTO books VALUES(1,10.5,.5,
'<book>
<title>Harry Potter</title>
<author>J K Rowling</author>
<Store>
<Name>Burke and Burkins</Name>
<Address>Some St, Somewhere, Some City</Address>
</Store>
</book>');
--This is the actual query
WITH CTE AS
(
SELECT *
,(SELECT price AS Price,discount AS Discount FOR XML PATH(''),TYPE) AS XmlNode
FROM books
)
UPDATE CTE SET book.modify('insert sql:column("XmlNode") after (/book/Store/Address)[1]');
--Check the result
SELECT *
FROM books;
--Clean-Up (carefull with real date!)
GO
--DROP TABLE books;
One hint
Your XML column, if it is really XML, will - for sure! - not contain an XML starting with <?xml version="1.0" encoding="UTF-8"?>. The internal encoding is always unicode (ucs-2, which is almost utf-16) and one cannot change this. If you pass in a declaration, it is either ommited or you'll get an error.
UPDATE
Another approach was to first read the XML's values and then to rebuild it:
WITH CTE AS
(
SELECT *
,(SELECT b.value('title[1]','nvarchar(max)') AS [title]
,b.value('author[1]','nvarchar(max)') AS [author]
,b.value('(Store/Name)[1]','nvarchar(max)') AS [Store/Name]
,b.value('(Store/Address)[1]','nvarchar(max)') AS [Store/Address]
,price AS [Store/Price]
,discount AS [Store/Discount]
FROM book.nodes('book') AS A(b)
FOR XML PATH('book'),TYPE
) AS bookNew
FROM books
)
UPDATE CTE SET book=bookNew;
Considering this simple table id (int), name (varchar), customFields (xml)
customFields contains an XML representation of a C# Dictionary. E.g :
<dictionary>
<item>
<key><int>1</int></key>
<value><string>Audi</string></value>
</item>
<item>
<key><int>2</int></key>
<value><string>Red</string></value>
</item>
</dictionary>
How do I select all rows of my table with 3 columns: id, name and 1 column that is the content of the string tag where the value of the int tag is equal 1.
The closest to the result I managed to get is:
SELECT id, name, C.value('(value/string/text())[1]', 'varchar(max)')
FROM myTable
OUTER APPLY customFields.nodes('/dictionary/item') N(C)
WHERE (customFields IS NULL OR C.value('(key/int/text())[1]', 'int') = 1)
Problem is, if xml doesn't have a int tag = 1 the row is not returned at all (I would still like to get a row with id, name and NULL in the 3rd column)
I've created a table the same as yours and this query worked fine:
select id, name,
(select C.value('(value/string/text())[1]','varchar(MAX)')
from xmlTable inr outer apply
customField.nodes('/dictionary/item') N(C)
where
C.value('(key/int/text())[1]','int') = 1
and inr.id = ou.id) as xmlVal
from xmlTable ou
Here is my result:
The reason why your query didn't worked is because it first selects values of "value/string" for all rows from "myTable" and then filters them. Therefore, the null values appear only on empty fields, and on the other fields (which contains any xml value), the "value/string/text()" is displayed. This is what your query without where clause returns:
id,name,xml
1 lol NULL
2 rotfl Audi
2 rotfl Red
3 troll Red
I have a task that requires me to pull in a set of xml files, which are all related, then pick out a subset of records from these files, transform some columns, and export to a a single xml file using a different format.
I'm new to SSIS, and so far I've managed to first import two of the xml files (for simplicity, starting with just two files).
The first file we can call "Item", containing some basic metadata, amongst those an ID, which is used to identify related records in the second file "Milestones". I filter my "valid records" using a lookup transformation in my dataflow - now I have the valid Item ID's to fetch the records I need. I funnel these valid ID's (along with the rest of the columns from Item.xml through a Sort, then into a merge join.
The second file is structured with 2 outputs, one containing two columns (ItemID and RowID). The second containing all of the Milestone related data plus a RowID. I put these through a inner merge join, based on RowID, so I have the ItemID in with the milestone data. Then I do a full outer join merge join on both files, using ItemID.
This gives me data sort of like this:
ItemID[1] - MilestoneData[2]
ItemID[1] - MilestoneData[3]
ItemID[1] - MilestoneData[4]
ItemID[1] - MilestoneData[5]
ItemID[2] - MilestoneData[6]
ItemID[2] - MilestoneData[7]
ItemID[2] - MilestoneData[8]
I can put this data through derived column transformations to create the columns of data I actually need, but I can't see how to structure this in a relational way/normalize it into a different xml format.
The idea is to output something like:
<item id="1">
<Milestone id="2">
<Milestone />
<Milestone id="3">
<Milestone />
</item>
Can anyone point me in the right direction?
UPDATE:
A bit more detailed picture of what I have, and what I'd like to achieve:
Item.xml:
<Items>
<Item ItemID="1">
<Title>
Data
</Title>
</Item>
<Item ItemID="2">
...
</Item>
...
</Items>
Milestone.xml:
<Milestones>
<Item ItemID="2">
<MS id="3">
<MS_DATA>
Data
</MS_DATA>
</MS>
<MS id="4">
<MS_DATA>
Data
</MS_DATA>
</MS>
</Item>
<Item ItemID="3">
<MS id="5">
<MS_DATA>
Data
</MS_DATA>
</MS>
</item>
</Milestones>
The way it's presented in SSIS when I use XML source, is not entirely intuitive, meaning the Item rows and the MS rows are two seperate outputs. I had to run these through a join in order to get the Milestones that corresponds to specific Items. No problem here, then run it through a full outer join with the items, so I get a flattened table with multiple rows containing obviously the same data for an Item and with different data for the MS. Basically I get what I tried to show in my table, lots of redundant Item data, for each unique MilestoneData.
In the end it has to look similar to:
<NewItems>
<myNewItem ItemID="2">
<SomeDataDirectlyFromItem>
e.g. Title
</SomeDataDirectlyFromItem>
<DataConstructedFromMultipleColumnsInItem>
<MyMilestones>
<MS_DATA_TRANSFORMED MSID="3">
data
</MS_DATA_TRANSFORMED>
<MS_DATA_TRANSFORMED MSID="4">
data
</MS_DATA_TRANSFORMED>
</MyMilestones>
</DataConstructedFromMultipleColumnsInItem>
<myNewItem ItemID="3">
<SomeDataDirectlyFromItem>
e.g. Title
</SomeDataDirectlyFromItem>
<DataConstructedFromMultipleColumnsInItem>
<MyMilestones>
<MS_DATA_TRANSFORMED MSID="5">
data
</MS_DATA_TRANSFORMED>
</MyMilestones>
</DataConstructedFromMultipleColumnsInItem>
</myNewItem>
<myNewItem ItemID="4">
<SomeDataDirectlyFromItem>
e.g. Title
</SomeDataDirectlyFromItem>
<DataConstructedFromMultipleColumnsInItem>
<MyMilestones></MyMilestones>
</DataConstructedFromMultipleColumnsInItem>
</myNewItem>
</NewItems>
I would try to handle this using a script component with the component type transformation. As you are new to ssis, i assume you haven't used this before. So basically you
define input columns, your component will expect (i.e. column input_xml containing ItemID[1] - MilestoneData[2];...
use c# to create a logic which cuts and sticks together
define output columns your component will use to deliver the transformed row
You will face the problem that one row will probably be used two times in the end, like i.e.
ItemID[1] - MilestoneData[2]
will result in
<item id="1">
<Milestone id="2">
I have done something pretty similar using Pentaho kettle, even without using something like a script component in which you define own logic. But i guess ssis has a lack of tasks here.
How about importing the XML into relational tables ( eg in tempdb ) then using FOR XML PATH to reconstruct the XML? FOR XML PATH offers a high degree of control over how you want the XML to look. A very simple example below:
CREATE TABLE #items ( itemId INT PRIMARY KEY, title VARCHAR(50) NULL )
CREATE TABLE #milestones ( itemId INT, msId INT, msData VARCHAR(50) NOT NULL, PRIMARY KEY ( itemId, msId ) )
GO
DECLARE #itemsXML XML
SELECT #itemsXML = x.y
FROM OPENROWSET( BULK 'c:\temp\items.xml', SINGLE_CLOB ) x(y)
INSERT INTO #items ( itemId, title )
SELECT
i.c.value('#ItemID', 'INT' ),
i.c.value('(Title/text())[1]', 'VARCHAR(50)' )
FROM #itemsXML.nodes('Items/Item') i(c)
GO
DECLARE #milestoneXML XML
SELECT #milestoneXML = x.y
FROM OPENROWSET( BULK 'c:\temp\milestone.xml', SINGLE_CLOB ) x(y)
INSERT INTO #milestones ( itemId, msId, msData )
SELECT
i.c.value('#ItemID', 'INT' ),
i.c.value('(MS/#id)[1]', 'VARCHAR(50)' ) msId,
i.c.value('(MS/MS_DATA/text())[1]', 'VARCHAR(50)' ) msData
FROM #milestoneXML.nodes('Milestones/Item') i(c)
GO
SELECT
i.itemId AS "#ItemID"
FROM #items i
INNER JOIN #milestones ms ON i.itemId = ms.itemId
FOR XML PATH('myNewItem'), ROOT('NewItems'), TYPE
DROP TABLE #items
DROP TABLE #milestones