I have following XML, and i wish to save its data in my SQL table. I have a table named as tblDummy it has three columns "JobID" "ItemID" "SubitemID". there can be multiple subitemsid for particular combination of Jobid and Itemid. How can i do this?
<jobs>
<job>
<jobid>4711</jobid>
<items>
<itemid>1</itemid>
<subitems>
<subitemid>1</subitemid>
<subitemid>2</subitemid>
</subitems>
<itemid>2</itemid>
<subitems>
<subitemid>7</subitemid>
<subitemid>10</subitemid>
</subitems>
<itemid>9</itemid>
<subitems>
<subitemid>12</subitemid>
<subitemid>16</subitemid>
</subitems>
</items>
</job>
</jobs>
As this XML is, you cannot properly parse it. You would need to change it - you should put each item with its itemid and subitems into a separate <item> node - otherwise you just have a long list of <itemid> and <subitems> nodes under your <items> main node, but you have no means of telling which <itemid> and <subitems> nodes belong together ....
You need to change your XML to be something like this:
<job>
<jobid>4711</jobid>
<items>
<item>
<itemid>1</itemid>
<subitems>
<subitemid>1</subitemid>
<subitemid>2</subitemid>
</subitems>
</item>
<item>
<itemid>2</itemid>
<subitems>
......
</subitems>
</item>
... (possibly more <item> nodes) ....
</items>
</job>
THEN you could use basically the same code I had for your previous question - extended to cover three levels:
CREATE PROCEDURE dbo.SaveJobs (#input XML)
AS BEGIN
;WITH JobsData AS
(
SELECT
JobID = JobNode.value('(jobid)[1]', 'int'),
ItemID = ItemNode.value('(itemid)[1]', 'int'),
SubItemID = SubItemNode.value('.', 'int')
FROM
#input.nodes('/jobs/job') AS TblJobs(JobNode)
CROSS APPLY
JobNode.nodes('items/item') AS TblItems(ItemNode)
CROSS APPLY
ItemNode.nodes('subitems/subitem') AS TblSubItems(SubItemNode)
)
INSERT INTO dbo.tblDummy(JobID, ItemID, SubItemID)
SELECT JobID, ItemID, SubItemID
FROM JobsData
END
Basically, you need three "lists" of XML nodes:
first you need the list of all <jobs>/<job> nodes to get the jobid values
for each of those job nodes, you will also need to get their list of nested <items>/<item> to get the itemid value
from each node, you then also get the list of <subitems>/<subitem>
This will most likely work - but most likely, it will be rather slow (three nested calls to the .nodes() function!).
Update:
OK, so the first call #input.nodes('/jobs/job') AS TblJobs(JobNode) basically creates a "pseudo" table TblJobs with a single column JobNode and each <job> element in your XML is being stored into a row in that pseudo table - so the first row will contain this XML in it's JobNode column:
<job>
<jobid>4711</jobid>
<items>
<item>
<itemid>1</itemid>
<subitems>
<subitemid>1</subitemid>
<subitemid>2</subitemid>
</subitems>
</item>
<item>
<itemid>2</itemid>
<subitems>
......
</subitems>
</item>
... (possibly more <item> nodes) ....
</items>
</job>
and each further row will contain the additional XML fragments for each subsequent <job> element inside <jobs>
From each of those XML fragments, the second call
CROSS APPLY JobNode.nodes('items/item') AS TblItems(ItemNode)
again selects a list of XML fragments into a pseudo table (TblItems) with a single column ItemNode that contains the XML fragment for each <item> node inside that <job> node we're dealing with currently.
So the first row in this pseudo-table contains:
<item>
<itemid>1</itemid>
<subitems>
<subitemid>1</subitemid>
<subitemid>2</subitemid>
</subitems>
</item>
and the second row will contain
<item>
<itemid>2</itemid>
<subitems>
......
</subitems>
</item>
and so on.
And then the third call - you've guessed it - again extracts a list of XML elements as rows into a pseudo-table - one entry for each <subitem> node in your XML fragment.
Update #2:
I'm new to "JobID = JobNode.value('(jobid)[1]', 'int')" line of code
OK - given the <Job> XML fragment that you have:
<job>
<jobid>4711</jobid>
<items>
......
</items>
</job>
the .value() call just executes this XPath expression (jobid) on that XML and basically gets back the <jobid>4711</jobid> snippet. It then extracts the value of that node (the inner text), and the second parameter of the .value() call defines what SQL data type to interpret this as - so it basically grabs the 4711 from the <jobid> node and interprets it as an int
You can take a composite key of Jobid and Itemid as primary key.
Related
I have an XML inventory of items.
The inventory lists how much if Item with ID 1 I have, how much of Item with ID 2, etc (think each item ID represents 1 product).
The list, however, is subdivided, depending on what quantity of items of particular type have a particular mark on them.
So, I have let us say:
Item ID Marked? Qty
1 (no) 500
1 ABC 100
1 (no) 50
1 FFFF 333
2 (no) 10000
....
This ir represented in a structure like this:
<Base>
<Item>
<Id>1</Id>
<Qty>500</Qty>
</Item>
<Item>
<Id>1</Id>
<Qty>100</Qty>
<Mark>ABC</Mark>
</Item>
<Item>
<Id>1</Id>
<Qty>50</Qty>
</Item>
<Item>
<Id>1</Id>
<Qty>333</Qty>
<Mark>FFFF</Mark>
</Item>
<Item>
<Id>2</Id>
<Qty>10000</Qty>
</Item>
...
</Base>
Using XQuery transformation I wish to produce, instead of multiple rows per item ID, 1 aggregate row for each item ID, which would list both total sum of quantities per item (both marked and unmarked), and a separate sum for only those quantities of item that are marked (have subtag. I am not concerned about the contents of the individual tags).
In table form what I want would be this:
Item ID Marked Qty Total Qty
1 433 983
2 0 10000
etc.
In actual XML form the transformation should produce something like this:
<Item><Id>1</Id><MarkedQuantity>433</MarkedQuantity><Total>983</Total></Item>
<Item><Id>2</Id><Total>10000</Total></Item>
....
UPD: Edited for clarity.
If you use
for $item in Base/Item
group by $id := $item/Id
order by $id
return <Item>
<Id>{$id}</Id>
{for $marked in $item[Mark]
group by $mark := $marked/Mark
return <Marked name="{$mark}">
{sum($marked/Qty)}
</Marked>
}
<Total>{sum($item/Qty)}</Total>
</Item>
you will get
<Item>
<Id>1</Id>
<Marked name="ABC">100</Marked>
<Total>650</Total>
</Item>
<Item>
<Id>2</Id>
<Total>10000</Total>
</Item>
I am not quite sure you need or want the inner grouping as I am not sure whether there can be different marks you want to distinguish but hopefully it gives you an idea.
If you simply want a total of Marked Items then I think
for $item in Base/Item
group by $id := $item/Id
order by $id
return <Item>
<Id>{$id}</Id>
{if ($item[Mark])
then <MarkedQuantity>{sum($item[Mark]/Qty)}</MarkedQuantity>
else ()
}
<Total>{sum($item/Qty)}</Total>
</Item>
does that.
Turns out that the answer was way easier (as I suspected):
for $item in //Item
let $d := $item/Id
group by $d
order by $d
return <Marked id="{$d}" Total="{ sum($item/Qty) }">{ sum($item[Mark]/Qty) }</Marked>
Basically, give 2 different sums for the same FOR clause: 1 with total sum of items, another with sum of filtered items (those that have Mark child tag in them).
I still do not understand though, why I can't use <Id>{ $d }</Id><Marked>{ sum($item[Mark]/Qty) }</Marked><Total>{ sum($item/Qty) }</Total> - in which case basex complains of finding } where it wanted >
But basically it works.
I have the following structures:
Strcuture A:
<itemlist>
<item>
<id>123</id>
<price>1</price>
<quantity>1</quantity>
</item>
<item>
<id>124</id>
<price>2</price>
<quantity>1</quantity>
</item>
<item>
<id>125</id>
<price>3</price>
<quantity>1</quantity>
</item>
<itemlist>
Structure B:
<totals>
<total>
<totalPrice>3</totalPrice>
</total>
</totals>
If I want a sumation of all the field multiplied
by the field in structure A to be placed into the totalprice field of structure B, would that be possible in the DataMapper.
If it is possible, how would you do it?
Thanks
Yes it is, in datamapper find the mapping of the field 1 and then go to script view. There you will find a datamapper MEL script. Find output.total = input.field1 and add * input.field2.
In XQuery 3.0, how is it possible to detect the last post-grouping tuple (defined by XQuery 3.0 spec) produced by the return clause? Is it possible to use something like position() = last()? What would be the context for the position function?
For example, say I want to generate CSV output via XQuery. In order to separate lines in the CSV output, I append a new-line after each tuple produced by the return clause:
xquery version "3.0";
declare option saxon:output "omit-xml-declaration=yes";
declare option saxon:output "method=text";
declare variable $data := (
<items>
<item>
<property name="a"/>
<property name="a"/>
<property name="b"/>
</item>
<item>
<property name="a"/>
</item>
<item>
<property name="b"/>
<property name="c"/>
<property name="d"/>
</item>
<item>
<property name="b"/>
<property name="c"/>
</item>
</items>
);
for $item in $data/item,
$name in $item/property/#name
group by $name
return (
text{string-join(($name, string(count($item))), ",")},
text{"
"}
)
However, this leaves an empty line after the last tuple. If I could test for the tuple position, I could avoid appending new-line after the last tuple.
The XQuery specs indeed seem to be missing something like group by $variable at $position here, similar what would be allowed in the for clause. Reading up the XQuery 3.0 specs again, I couldn't find anything that would help, either. position() and last() require a node context, which is not available within loops.
But regarding your underlying problem: why not use another string-join(...) to concatenate the items with newlines in-between, similar to like you did for the counts?
string-join(
for $item in $data/item,
$name in $item/property/#name
group by $name
return (
text{string-join(($name, string(count($item))), ",")}
),
text{"
"}
)
I am using a XQuery to query database in an OSB project. Consider the
following table:
userId Name Category
------ ------- --------
1 Dheepan Student
2 Raju Student
and the XQuery
let $userName:=fn-bea:execute-sql(
$dataSourceJndiName,
xs:string("NAME"),
xs:string("select NAME from USER where CATEGORY= 'Student'")
)/*:NAME[1]
return <root> {data($userName)} </root>
For this query I am getting the result as <root>Dheepan Raju</root>. But I
need to return only one row even the query returns more than one row like the
following <root>Dheepan</root>. I have used predicate [1] in the query but
no clue why it concatenates the values and returning. Can anybody tell me how
to return only the first row when more than one row is returned.
You need to use proper paranthesis:
let $userName:=(fn-bea:execute-sql(
$dataSourceJndiName,
xs:string("NAME"),
xs:string("select NAME from USER where CATEGORY= 'Student'")
)/*:NAME)[1]
return <root> {data($userName)} </root>
I have a database which has a table with an XML column. The XML data has a bunch of child nodes which look something like this:
<test>
<result id="1234">
<data elementname="Message">some error message</data>
<data elementname="Cat">Cat01</data>
<data elementname="Type">WARNING</data>
</result>
<result id="5678">
<data elementname="Message">some error message</data>
<data elementname="Cat">Cat01</data>
<data elementname="Type">WARNING</data>
</result>
</test>
The Cat element can have a number of different values. I'm trying to create reports on this data, so one thing I'd like to do is get a list of all the categories througout our data. This is my query:
Select Id, XmlData.query('/test/result/data[#elementname = ''Cat''] ') AS Message
From Table
WHERE XmlData.exist('/test/result/data[#elementname = ''Cat'']') = 1
ORDER BY FriendlyName
This correctly gets all the rows in my table with this type of categorization (there'll be other results in the same table without that element), but the categories are all combined into one column for each table record:
Id1, <data elementname="Cat">Cat01</data><data elementname="Cat">Cat01</data>
Id2, <data elementname="Cat">Cat01</data><data elementname="Cat">Cat01</data>
I'm including the Id column so it's easy to see where the data is coming from, the main problem is that I can only get it to concatenate the values for each row - I need each of those data elements to have its own row, then maybe do a Select Distinct on the result.
Is there a way I can do that?
Thanks
Always the Google after you post your question....
Think I found the answer here: http://blogs.msdn.com/b/simonince/archive/2009/04/24/flattening-xml-data-in-sql-server.aspx
SELECT DISTINCT cref.value('(text())[1]', 'varchar(50)') as Cat
FROM
SGIS CROSS APPLY
Data.nodes('/test/result') AS Results(rref) CROSS APPLY
rref.nodes('data[#elementname = ''Cat'']') AS Categories(cref)
Seems the key is the Cross Apply keywords