Grouping the nodes at three levels and summing one node - sum

Please follow the comments in the sample xml it explains everything I need, it is having three scenerio's check each scenerio in the output xml in the same sequence.
Input XML
<!-- In the below xml there will be one ASNInDesc with multiple ASNInPO's
and each ASNInPO contains one ASNInCtn and each ASNInCtn contains one ASNInItem -->
<ASNInDesc>
<asn_nbr>ASN-1</asn_nbr>
<!-- In the below two ASNInPO's po_nbr is same container_id under ASNInCtn is same and item_id under
ASNInItem is same. In this case two ASNInPO's has to be merged and two ASNInCtn's has to be merged
into one tag(since the container_id is same) and two ASNInItem's has to be merged into one tag and unit_qty is to be added -->
<ASNInPO>
<po_nbr>PO-1</po_nbr>
<ASNInCtn>
<container_id>CONTAINER-1</container_id>
<ASNInItem>
<item_id>ITEM-1</item_id>
<unit_qty>2</unit_qty>
</ASNInItem>
</ASNInCtn>
</ASNInPO>
<ASNInPO>
<po_nbr>PO-1</po_nbr>
<ASNInCtn>
<container_id>CONTAINER-1</container_id>
<ASNInItem>
<item_id>ITEM-1</item_id>
<unit_qty>2</unit_qty>
</ASNInItem>
</ASNInCtn>
</ASNInPO>
<!-- In the below two ASNInPO's po_nbr is same container_id under ASNInCtn is same and item_id under
ASNInItem is different. In this case two ASNInPO's has to be merged and two ASNInCtn's has to be merged into one tag and
two different ASNInItem's for the two different items -->
<ASNInPO>
<po_nbr>PO-2</po_nbr>
<ASNInCtn>
<container_id>CONTAINER-2</container_id>
<ASNInItem>
<item_id>ITEM-2</item_id>
<unit_qty>3</unit_qty>
</ASNInItem>
</ASNInCtn>
</ASNInPO>
<ASNInPO>
<po_nbr>PO-2</po_nbr>
<ASNInCtn>
<container_id>CONTAINER-2</container_id>
<ASNInItem>
<item_id>ITEM-3</item_id>
<unit_qty>3</unit_qty>
</ASNInItem>
</ASNInCtn>
</ASNInPO>
<!-- In the below two ASNInPO's po_nbr is same container_id under ASNInCtn is different and item_id under
ASNInItem is different. In this case two ASNInPO's has to be merged into one tag with
two different ASNInCtn's each having their own ASNInItem -->
<ASNInPO>
<po_nbr>PO-3</po_nbr>
<ASNInCtn>
<container_id>CONTAINER-3</container_id>
<ASNInItem>
<item_id>ITEM-3</item_id>
<unit_qty>2</unit_qty>
</ASNInItem>
</ASNInCtn>
</ASNInPO>
<ASNInPO>
<po_nbr>PO-3</po_nbr>
<ASNInCtn>
<container_id>CONTAINER-4</container_id>
<ASNInItem>
<item_id>ITEM-3</item_id>
<unit_qty>2</unit_qty>
</ASNInItem>
</ASNInCtn>
</ASNInPO>
</ASNInDesc>
Output XML
<?xml version = '1.0' encoding = 'UTF-8'?>
<ASNInDesc>
<asn_nbr>ASN-1</asn_nbr>
<!-- Scenerio-1 -->
<ASNInPO>
<po_nbr>PO-1</po_nbr>
<ASNInCtn>
<container_id>CONTAINER-1</container_id>
<ASNInItem>
<item_id>ITEM-1</item_id>
<unit_qty>4</unit_qty>
</ASNInItem>
</ASNInCtn>
</ASNInPO>
<!-- Scenerio-2 -->
<ASNInPO>
<po_nbr>PO-2</po_nbr>
<ASNInCtn>
<container_id>CONTAINER-2</container_id>
<ASNInItem>
<item_id>ITEM-2</item_id>
<unit_qty>3</unit_qty>
</ASNInItem>
<ASNInItem>
<item_id>ITEM-3</item_id>
<unit_qty>3</unit_qty>
</ASNInItem>
</ASNInCtn>
</ASNInPO>
<!-- Scenerio-3 -->
<ASNInPO>
<po_nbr>PO-3</po_nbr>
<ASNInCtn>
<container_id>CONTAINER-3</container_id>
<ASNInItem>
<item_id>ITEM-3</item_id>
<unit_qty>2</unit_qty>
</ASNInItem>
</ASNInCtn>
<ASNInCtn>
<container_id>CONTAINER-4</container_id>
<ASNInItem>
<item_id>ITEM-3</item_id>
<unit_qty>2</unit_qty>
</ASNInItem>
</ASNInCtn>
</ASNInPO>
I have tried with my own xsl, but couldn't handle all the scenerios. Please help me in solving this.
Thanks in Advance.

Here's a XSLT 1.0 stylesheet using Muenchian grouping. This is based on my answer to your other question.
This is the same approach, but with adjusted keys where we use a concatenation of the element's own id and the parent elements' id to look up elements for grouping. For example, ASNInItem elements are looked up using the following concatenated key: concat(../../po_nbr, '|', ../container_id, '|', item_id). The choice of separator is not important, but you need to choose a character which does not appear in the ids.
Also the summing of unit quantities has been added.
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" encoding="UTF-8"/>
<xsl:key name="ASNInPO" match="ASNInPO"
use="po_nbr"/>
<xsl:key name="ASNInCtn" match="ASNInCtn"
use="concat(../po_nbr, '|',
container_id)"/>
<xsl:key name="ASNInItem" match="ASNInItem"
use="concat(../../po_nbr, '|',
../container_id, '|',
item_id)"/>
<xsl:template match="ASNInDesc">
<xsl:copy>
<xsl:copy-of select="asn_nbr"/>
<xsl:apply-templates
select="ASNInPO[generate-id() =
generate-id(key('ASNInPO',
po_nbr)[1])]"/>
</xsl:copy>
</xsl:template>
<xsl:template match="ASNInPO">
<xsl:copy>
<xsl:copy-of select="po_nbr"/>
<xsl:apply-templates
select="key('ASNInPO', po_nbr)/
ASNInCtn[generate-id() =
generate-id(key('ASNInCtn',
concat(../po_nbr, '|',
container_id))[1])]"/>
</xsl:copy>
</xsl:template>
<xsl:template match="ASNInCtn">
<xsl:copy>
<xsl:copy-of select="container_id"/>
<xsl:apply-templates
select="key('ASNInCtn',
concat(../po_nbr, '|',
container_id))/
ASNInItem[generate-id() =
generate-id(key('ASNInItem',
concat(../../po_nbr, '|',
../container_id, '|',
item_id))[1])]"/>
</xsl:copy>
</xsl:template>
<xsl:template match="ASNInItem">
<xsl:copy>
<xsl:copy-of select="item_id"/>
<unit_qty>
<xsl:value-of
select="sum(key('ASNInItem',
concat(../../po_nbr, '|',
../container_id, '|',
item_id))/unit_qty)"/>
</unit_qty>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

Related

Creating SQL INSERT statement out of XML file data

I want to generate an SQL INSERT statement from data encoded in XML files, using an XSLT.
My XML files include, for example, the following tags (describing a tombstone):
<?xml version="1.0" encoding="UTF-8"?>
<?xml-model href="http://www.stoa.org/epidoc/schema/latest/tei-epidoc.rng" type="application/xml" schematypens="http://relaxng.org/ns/structure/1.0"?>
<TEI xmlns="http://www.tei-c.org/ns/1.0" xml:lang="en">
<teiHeader>
<fileDesc>
<titleStmt>
<title>Funerary inscription for Marcellus, a smith</title>
</titleStmt>
<publicationStmt>
<authority>I.Sicily</authority>
<idno type="TM">491539</idno>
</publicationStmt>
<sourceDesc>
<msDesc>
<msIdentifier>
<country>Italy</country>
<region>Sicily</region>
<settlement>Catania</settlement>
<repository role="museum" ref="http://sicily.classics.ox.ac.uk/museum/018"
>Museo Civico di Catania </repository>
<idno type="inventory">390</idno>
<altIdentifier>
<settlement/>
<repository/>
<idno type="old"/>
</altIdentifier>
</msIdentifier>
<msContents>
<textLang mainLang="la">Latin </textLang>
</msContents>
<physDesc>
<objectDesc>
<supportDesc>
<support>
<material n="marble"
ref="http://www.eagle-network.eu/voc/material/lod/48.html"
>marble </material>
<objectType n="tabula"
ref="http://www.eagle-network.eu/voc/objtyp/lod/257">tablet </objectType>
<dimensions>
<height unit="cm">29</height>
<width unit="cm">33.5</width>
<depth unit="cm">2.1</depth>
</dimensions>
</support>
</supportDesc>
</objectDesc>
</physDesc>
</msDesc>
</sourceDesc>
</fileDesc>
</teiHeader>
<!-- lots more content that I cut away here -->
</TEI>
I would like to extract information from this and insert it into an SQL table.
My desired output would look like this:
INSERT INTO tblObjects
(ObjectID, Title, TMid, Material, ObjectType, Height)
VALUES
('Funerary inscription for Marcellus, a smith', 491539, 'marble', 'tabula', 29);
My original - now solved - problem was this:
I tried setting the output method to text after some examples I found
online, but this is giving me the error message "Non-whitespace
characters are not allowed in schema elements other than 'xs:appinfo'
and 'xs:documentation'."
I changed the file format to xsl, now it's no longer complaining about the non-whitespace characters. I can now put text into the final output. The following (inspired by your answers) is the current state of my XSLT, for now I want to try to just insert the value of the title, the position of which is "TEI/teiHeader/fileDesc/titleStmt/title":
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"
encoding="UTF-8"
omit-xml-declaration="yes"
indent="no"/>
<xsl:template match="/">
<xsl:text>INSERT INTO tblObjects</xsl:text>
<xsl:text>(ObjectID, Title, TMid, Material, ObjectType, Height) VALUES (</xsl:text>
<xsl:apply-templates select="root"/>
<xsl:text>);</xsl:text>
</xsl:template>
<xsl:template match="root">
<xsl:value-of select="TEI/teiHeader/fileDesc/titleStmt/title"/>
</xsl:template>
</xsl:stylesheet>
This gives me the following output:
INSERT INTO tblObjects(ObjectID, Title, TMid, Material, ObjectType, Height) VALUES ();
Yet, as you can see, it does not insert the value of the title. I am not sure why that's not working (only trying the title for now).
Simply walk down the tree with templates and concatenate values with needed quotes and commas for SQL query's VALUES clause. Due to the many nested structure of XML, ancestor::* and descendant::* paths are used for specific node value extraction.
Note: This solution works for XML files for one teiHeader. You will need to tailor this solution or run other XSLT scripts for other types.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:doc="http://www.tei-c.org/ns/1.0">
<xsl:output method="text" encoding="UTF-8" omit-xml-declaration="yes" indent="no"/>
<xsl:template match="/doc:TEI">
<xsl:text>INSERT INTO tblObjects</xsl:text>
<xsl:text>(ObjectID, Title, TMid, Material, ObjectType, Height)
VALUES
</xsl:text>
<xsl:apply-templates select="doc:teiHeader/doc:fileDesc/doc:sourceDesc/doc:msDesc/doc:physDesc"/>
</xsl:template>
<xsl:template match="doc:physDesc">
<xsl:variable name="quote">&apos;</xsl:variable>
<xsl:value-of select="concat('(',
$quote, ancestor::doc:fileDesc/doc:titleStmt/doc:title, $quote, ', ',
ancestor::doc:fileDesc/doc:publicationStmt/doc:idno[#type='TM'], ', ',
$quote, normalize-space(descendant::doc:material), $quote, ', ',
$quote, normalize-space(descendant::doc:objectType), $quote, ', ',
descendant::doc:dimensions/doc:height,
')'
)"/>
</xsl:template>
</xsl:stylesheet>
Online Demo

xslt : xml transformation, need soql query as output

Thanks for the solution.
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns="urn:partner.soap.sforce.com"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Header>
<LimitInfoHeader>
<limitInfo>
<current>18029</current>
<limit>5000000</limit>
<type>API REQUESTS</type>
</limitInfo>
</LimitInfoHeader>
</soapenv:Header>
<soapenv:Body>
<upsertResponse>
<result>
<created>false</created>
<id xsi:nil="true"/>
<success>false</success>
</result>
<result>
<created>false</created>
<id xsi:nil="true"/>
<success>false</success>
</result>
<result>
<created>false</created>
<id xsi:nil="true"/>
<success>false</success>
</result>
</upsertResponse>
</soapenv:Body>
</soapenv:Envelope>
for the above xml as input to xslt, the output is below one
<?xml version="1.0" encoding="utf-8"?><soql>select (select Id from contacts where AccountId in (SELECT AccountId from Opportunity where id in ())), (select Id from opportunities where id in()) from account where id in (SELECT AccountId from Opportunity where id in())</soql>
if all the success tag values are false in the given input xml, the <soql> tag should not be generated. i mean output should be empty
eventually what i need is valid SOQL, so that i can query sales force, for the above given xml input the output generated is not a valid SOQL. henceforth i cannot query sales force.
so where the changes in xslt needs to be done, need your help
Thanks
Why can't you do simply:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns="urn:partner.soap.sforce.com"
exclude-result-prefixes="ns">
<xsl:variable name="ids">
<xsl:for-each select="//ns:result[ns:success='true']">
<xsl:text>'</xsl:text>
<xsl:value-of select="ns:id" />
<xsl:text>'</xsl:text>
<xsl:if test="position()!=last()">
<xsl:text>,</xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<xsl:template match="/">
<soql>
<xsl:text>select (select Id from contacts where AccountId in (SELECT AccountId from Opportunity where id in (</xsl:text>
<xsl:value-of select="$ids"/>
<xsl:text>))), (select Id from opportunities where id in(</xsl:text>
<xsl:value-of select="$ids"/>
<xsl:text>)) from account where id in (SELECT AccountId from Opportunity where id in(</xsl:text>
<xsl:value-of select="$ids"/>
<xsl:text>))</xsl:text>
</soql>
</xsl:template>
</xsl:stylesheet>

How to sum the amounts in a xslt for same ids appearing in XML

I need to produce a text file in the output. I have to first check if a perosn has aby of the following node PreTaxAmt,MatchAmt,RothAmt, then produce a single line in output with required values and then I have to check if the perosn has LoanAmt node, if they have then I have to create another entry for that person. I was able to code till this point but now I am having a requiremnet where if the same person is repated more than once for example below laurel Alex , I need to produce just one entry for single participant id and the PreTaxAmt,MatchAmt,RothAmt amount should be summed up in the first entry in the output and in the in the loan entry row the LoanAmt should be summed up.
Here is my xml.
<Report_Data >
<Report_Entry>
<Participant_ID>03300</Participant_ID>
<Workers>
<FULL_PART_Time_Indicator>1</FULL_PART_Time_Indicator>
<Last_Name>Mary</Last_Name>
<First_Name>Mehul</First_Name>
</Workers>
<End_Date_from_Pay_Period>2016-01-03-08:00</End_Date_from_Pay_Period>
<PreTaxAmt>100.8</PreTaxAmt>
<MatchAmt>100.8</MatchAmt>
<RothAmt>0</RothAmt>
<LoanAmt>0</LoanAmt>
</Report_Entry>
<Report_Entry>
<Participant_ID>10600</Participant_ID>
<Workers>
<FULL_PART_Time_Indicator>1</FULL_PART_Time_Indicator>
<Last_Name>Laurel</Last_Name>
<First_Name>Alex</First_Name>
</Workers>
<End_Date_from_Pay_Period>2016-01-03-08:00</End_Date_from_Pay_Period>
<PreTaxAmt>61.48</PreTaxAmt>
<MatchAmt>61.47</MatchAmt>
<RothAmt>0</RothAmt>
<LoanAmt>0</LoanAmt>
</Report_Entry>
<Report_Entry>
<Participant_ID>10600</Participant_ID>
<Workers>
<FULL_PART_Time_Indicator>1</FULL_PART_Time_Indicator>
<Last_Name>Laurel</Last_Name>
<First_Name>Alex</First_Name>
<Middle_Name>S</Middle_Name>
</Workers>
<End_Date_from_Pay_Period>2016-01-03-08:00</End_Date_from_Pay_Period>
<PreTaxAmt>0</PreTaxAmt>
<MatchAmt>0</MatchAmt>
<RothAmt>0</RothAmt>
<LoanAmt>0</LoanAmt>
</Report_Entry>
<Report_Entry>
<Participant_ID>13100</Participant_ID>
<Workers>
<FULL_PART_Time_Indicator>1</FULL_PART_Time_Indicator>
<Last_Name>Smita</Last_Name>
<First_Name>Paul</First_Name>
</Workers>
<End_Date_from_Pay_Period>2016-01-03-08:00</End_Date_from_Pay_Period>
<PreTaxAmt>0</PreTaxAmt>
<MatchAmt>0</MatchAmt>
<RothAmt>0</RothAmt>
<LoanAmt>0</LoanAmt>
</Report_Entry>
Here is my xslt :
<xsl:template match="/">
<xsl:for-each select="Report_Data/Report_Entry">
<xsl:if test="(PreTaxAmt) or(MatchAmt) or (RothAmt) ">
<xsl:call-template name="DataRecords"/>
</xsl:if>
</xsl:for-each>
<xsl:for-each select="Report_Data/Report_Entry">
<xsl:if test="LoanAmt">
<xsl:call-template name="LoanDataRecord"/>
</xsl:if>
</xsl:for-each>
</xsl:template>
<xsl:template name="DataRecords">
<xsl:for-each-group select="." group-by="wd:Participant_ID">
<xsl:variable name="ParticpantLoan" select="current-grouping-key()"/>
!-- Transaction Code -->
<xsl:value-of select="'Contribution'"/>
<!-- Participant Number -->
<xsl:value-of select="Participant_ID,$filler),1,9"/>
<!-- FT/PT Indicator -->
<xsl:value-of select="substring(concat(Workers/FULL_PART_Time_Indicator,$filler),1,1)"/>
<!-- Contribution Amount1 -->
<xsl:value-of select="(sum(current-group()/PreTaxAmt))"/>
<!-- Contribution Amount2 -->
<xsl:value-of select="(sum(current-group()/MatchAmt))"/>
<!-- Contribution Amount3 -->
<xsl:value-of select="(sum(current-group()/RothAmt))"/>
<xsl:value-of select="$linefeed"/>
</xsl:for-each-group>
</xsl:template>
<xsl:template name="LoanDataRecord">
<xsl:for-each-group select="." group-by="Participant_ID">
<xsl:variable name="ParticpantLoan" select="current-grouping-key()"/>
<!-- Transaction Code -->
<xsl:value-of select="substring(concat('LoanData',$filler),1,3)"/>
<!-- Participant Number -->
<xsl:value-of select="substring(concat(Participant_ID,$filler),1,9)"/>
<!-- Loan Amt -->
<xsl:value-of select="(sum(current-group()/LoanAmt))"/>
<xsl:value-of select="$linefeed"/>
</xsl:for-each-group>
</xsl:template>
The output should have six lines.
Also, how can I calculate the total count of the records in my output file. Earlier I was using below code but now since there would be duplicate SSN how should I modify my existing code for counting ..
0 ] or Report_Data/Report_Entry/MatchAmt[number(.) > 0 ] or Report_Data/Report_Entry/RothAmt[number(.) > 0 ])+
count(Report_Data/Report_Entry/LoanAmt[number(.) > 0 ])

Get element value from XML datatype field

I want to retrieve element value from XML data field. Please check below code snippet for more detail:
SQL Script:
CREATE TABLE #Temp1 (ConfigXSLT XML);
INSERT INTO #Temp1 VALUES('<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes" />
<xsl:variable name="transport">
<transport protocol="FILE" direction="get" filename="file1*.csv" />
</xsl:variable>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>')
-- I want only filename element value 'file1*.csv' in select statement
-- I have tried with below query but it not works
SELECT ConfigXSLT.value('declare namespace PD="http://www.w3.org/1999/XSL/Transform"; (//PD:variable/PD:transport/PD:filename)[1]','VARCHAR(MAX)')
FROM #Temp1
DROP TABLE #Temp1
You went overboard with using PD namespace (why PD btw?), the "transport" telement is without a namespace. Also, to select attribute you use # sign before the attributes name :)
Try this:
SELECT ConfigXSLT.value('declare namespace PD="http://www.w3.org/1999/XSL/Transform"; (//PD:variable/transport/#filename)[1]','VARCHAR(MAX)')
FROM #Temp1
Result:
file1*.csv
select #xml.value('(/stylesheet/variable/transport/#filename)[1]', 'nvarchar(max)')
Maybe something like that?

XSLT variable in predicate not working

my xml looks something like this:
<units>
<unit>
<unitnum></unitnum>
<Year></Year>
<Qty></Qty>
</unit>
</units>
I create a key to capture all the possible years. Like this:
<xsl:key name="yr" match="//Year/text()" use="." />
Then I for-each loop through the keys to get unique years. My ultimate goal is to sum the quantites like this:
<xsl:for-each select="//Year/text()[generate-id()=generate-id(key('yr',.)[1])]">
<li>
<xsl:variable name="varjobyear" select="."/>
<xsl:value-of select="$varjobyear"/> - sum -
<xsl:value-of select="sum(//unit[Year=$varjobyear]/Qty)"/>
</li>
</xsl:for-each>
For some reason my output looks like this:
2010 - sum - 0
2011 - sum - 0
2012 - sum - 0
But what I want is for those 0's to be the actual sum of the quantities for each year.
My best guess is that there is something wrong with the predicate [Year=$varjobyear], but I can't figure out what it is. I have also tried [Year=string($varjobyear)] and also [Year=number($varjobyear)].
What am I doing wrong? Thanks!
You want something like this:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="kYearByVal" match="Year" use="."/>
<xsl:template match="Year[generate-id()=generate-id(key('kYearByVal',.)[1])]">
<li>
<xsl:value-of select="concat(., ' - sum - ', sum(key('kYearByVal',.)/../Qty))"/>
</li>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
When this transformation is applied on the following XML document:
<units>
<unit>
<unitnum>12</unitnum>
<Year>1922</Year>
<Qty>3</Qty>
</unit>
<unit>
<unitnum>13</unitnum>
<Year>1922</Year>
<Qty>8</Qty>
</unit>
<unit>
<unitnum>14</unitnum>
<Year>1999</Year>
<Qty>5</Qty>
</unit>
<unit>
<unitnum>15</unitnum>
<Year>1999</Year>
<Qty>7</Qty>
</unit>
<unit>
<unitnum>16</unitnum>
<Year>2003</Year>
<Qty>3</Qty>
</unit>
</units>
the wanted, correct result is produced:
<li>1922 - sum - 11</li>
<li>1999 - sum - 12</li>
<li>2003 - sum - 3</li>