How to use SQL XML and XMLAGG to return multiple xmlelements - db2

In SQL, I need to create xml code that looks like this:
<Phone>
<PhoneTypeCode tc="12">Mobile</PhoneTypeCode>
<Area>801</Area>
<DialNumber>9996666</DialNumber>
</Phone>
<Phone>
<PhoneTypeCode tc="2">Business</PhoneTypeCode>
<Area>801</Area>
<DialNumber>1113333</DialNumber>
</Phone>
using xmlagg, but it is throwing an error on the ',' after p.desc
How does this IBM DB2 SQL function need to be fixed to achieve the above xml?
select
xmlelement(Name "Phone",
xmlagg(xmlelement(name "PhoneTypeCode",
xmlattributes(trim(p.phtype) as "tc"), trim(p.desc)),
xmlelement(name "AreaCode", p.area),
xmlelement(name "DialNumber", p.phone)
)
) as xml
from phone p
where p.entityid = #entity_id
I also wanted to add that it does compile and run with this:
select
xmlelement(Name "Phone",
xmlagg(xmlelement(name "PhoneTypeCode",
xmlattributes(trim(p.phtype) as "tc"), trim(p.desc))
)
) as xml
from phone p
where p.entityid = #entity_id
Here is what it returns:
<Phone>
<PhoneTypeCode tc="12">Mobile</PhoneTypeCode>
<PhoneTypeCode tc="2">Business</PhoneTypeCode>
</Phone>
But of course, I need the Area and DialNumber. It is as if you can't have more than one xmlelement within an xmlagg.

How does this IBM DB2 SQL function need to be fixed to achieve the above xml?
Firstly, you may want to count your parentheses. Normally one would want as many closing parentheses as there are opening parentheses.
Secondly, you don't need XMLAGG() at all. You'd use it when inserting multiple elements of the same type, based on multiple relational records, into a single outer element, like
<phones>
<phone no="1" .../>
<phone no="2" .../>
...
</phones>
For you something like this should work:
with phone (phtype, desc, area, phone) as
(values ('home','blah','555','555-5555'),('office','blah','555','555-1111'))
select
xmlelement(
Name "Phone",
xmlelement(
name "PhoneTypeCode",
xmlattributes(
trim(p.phtype) as "tc"
),
trim(p.desc)
),
xmlelement(name "AreaCode", p.area),
xmlelement(name "DialNumber", p.phone)
) as xml
from phone p

Related

Postgres use xpath_table parsing with xmlnamespaces

Can I use xpath_table parsing with xmlnamespaces
drop table if exists _xml;
create temporary table _xml (fbf_xml_id serial,str_Xml xml);
insert into _xml(str_Xml)
select '<DataSet1 xmlns="http://tempuri.org/DataSet_LocalMaMC.xsd">
<Stations>
<ID>5</ID>
</Stations>
<Stations>
<ID>1</ID>
</Stations>
<Stations>
<ID>2</ID>
</Stations>
<Stations>
<ID>10</ID>
</Stations>
<Stations>
<ID/>
</Stations>
</DataSet1>' ;
drop table if exists _y;
create temporary table _y as
SELECT *
FROM xpath_table('FBF_xml_id','str_Xml','_xml',
'/DataSet1/Stations/ID',
'true') AS t(FBF_xml_id int,ID text);
select * from _y
If I take of the xmlnamespaces it works fine.
I thought to work with Xpath, but when there is null, it gives me wrong results.
With Postgres 10 or later, xmltable() is the preferred way to do this.
You can easily specify a list of namespaces with that.
SELECT fbf_xml_id, xt.id
FROM _xml
cross join
xmltable(xmlnamespaces ('http://tempuri.org/DataSet_LocalMaMC.xsd' as x),
'/x:DataSet1/x:Stations'
passing str_xml
columns
id text path 'x:ID') as xt
Note that in the XPath expression used for the xmltable() function, the tags are prefixed with the namespace alias defined in the xmlnamespaces option even though they are not prefixed in the input XML.
Online example

understanding complex SP in DB2

I need to make changes to an SP which has a bunch of complex XML functions and what not
Declare ResultCsr2 Cursor For
WITH
MDI_BOM_COMP(PROD_ID,SITE_ID, xml ) AS (
SELECT TC401F.T41PID,TC401F.T41SID,
XMLSERIALIZE(
XMLAGG(
XMLELEMENT( NAME "MDI_BOM_COMP",
XMLFOREST(
trim(TC401F.T41CTY) AS COMPONENT_TYPE,
TC401F.T41LNO AS COMP_NUM,
trim(TC401F.T41CTO) AS CTRY_OF_ORIGIN,
trim(TC401F.T41DSC) AS DESCRIPTION,
TC401F.T41EFR AS EFFECTIVE_FROM,
TC401F.T41EFT AS EFFECTIVE_TO,
trim(TC401F.T41MID) AS MANUFACTURER_ID,
trim(TC401F.T41MOC) AS MANUFACTURER_ORG_CODE,
trim(TC401F.T41CNO) AS PROD_ID,
trim(TC401F.T41POC) AS PROD_ORG_CODE,
TC401F.T41QPR AS QTY_PER,
trim(TC401F.T41SBI) AS SUB_BOM_ID,
trim(TC401F.T41SBO) AS SUB_BOM_ORG_CODE, --ADB01
trim(TC401F.T41VID) AS SUPPLIER_ID,
trim(TC401F.T41SOC) AS SUPPLIER_ORG_CODE,
TC401F.T41UCT AS UNIT_COST
)
)
) AS CLOB(1M)
)
FROM TC401F TC401F
GROUP BY T41PID,T41SID
)
SELECT
RowNum, '<BOM_INBOUND>' ||
XMLSERIALIZE (
XMLELEMENT(NAME "INTEGRATION_MESSAGE_CONTROL",
XMLFOREST(
'FULL_UPDATE' as ACTION,
'POLARIS' as COMPANY_CODE,
TRIM(TC400F.T40OCD) as ORG_CODE,
'5' as PRIORITY,
'INBOUND_ENTITY_INTEGRATION' as MESSAGE_TYPE,
'POLARIS_INTEGRATION' as USERID,
'TA' as RECEIVER,
HEX(Generate_Unique()) as SOURCE_SYSTEM_TOKEN
),
XMLELEMENT(NAME "BUS_KEY",
XMLFOREST(
TRIM(TC400F.T40BID) as BOM_ID,
TRIM(TC400F.T40OCD) as ORG_CODE
)
)
) AS VARCHAR(1000)
)
|| '<MDI_BOM>' ||
XMLSERIALIZE (
XMLFOREST(
TRIM(TC400F.T40ATP) AS ASSEMBLY_TYPE,
TRIM(TC400F.T40BID) AS BOM_ID,
TRIM(TC400F.T40CCD) AS CURRENCY_CODE,
TC400F.T40DPC AS DIRECT_PROCESSING_COST,
TC400F.T40EFD AS EFFECTIVE_FROM,
TC400F.T40EFT AS EFFECTIVE_TO,
TRIM(TC400F.T40MID) AS MANUFACTURER_ID,
TRIM(TC400F.T40MOC) AS MANUFACTURER_ORG_CODE,
TRIM(TC400F.T40OCD) AS ORG_CODE,
TRIM(TC400F.T40PRF) AS PROD_FAMILY,
TRIM(TC400F.T40PID) AS PROD_ID,
TRIM(TC400F.T40POC) AS PROD_ORG_CODE,
TRIM(TC400F.T40ISA) AS IS_ACTIVE,
TRIM(TC400F.T40VID) AS SUPPLIER_ID,
TRIM(TC400F.T40SOC) AS SUPPLIER_ORG_CODE,
TRIM(TC400F.T40PSF) AS PROD_SUB_FAMILY,
CASE TRIM(TC400F.T40PML)
WHEN '' THEN TRIM(TC400F.T40PML)
ELSE TRIM(TC400F.T40PML) || '~' || TRIM(TC403F.T43MDD)
END AS PROD_MODEL
) AS VARCHAR(3000)
)
|| IFNULL(MBC.xml, '') ||
XMLSERIALIZE (
XMLFOREST(
XMLFOREST(
TRIM(TC400F.T40CCD) AS CURRENCY_CODE,
TC400F.T40PRI AS PRICE,
TRIM(TC400F.T40PTY) AS PRICE_TYPE
) AS MDI_BOM_PRICE,
XMLFOREST(
TRIM(TC400F.T40CCD) AS CURRENCY_CODE,
TRIM(TC400F.T40PRI) AS PRICE,
'TRANSACTION_VALUE' AS PRICE_TYPE
) AS MDI_BOM_PRICE,
XMLFOREST(
TRIM(TC400F.T40INA) AS INCLUDE_IN_AVERAGING
) AS MDI_BOM_IMPL_BOM_PROD_FAMILY_AUTOMOBILES
) AS VARCHAR(3000)
)
|| '</MDI_BOM>' ||
'</BOM_INBOUND>' XML
FROM (
SELECT
ROW_NUMBER() OVER (
ORDER BY T40STS
,T40SID
,T40BID
) AS RowNum
,t.*
FROM TC400F t
) TC400F
LEFT OUTER JOIN MDI_BOM_COMP MBC
ON TC400F.T40SID = MBC.SITE_ID
AND TC400F.T40PID = MBC.PROD_ID
LEFT OUTER JOIN TC403F TC403F
ON TC400F.T40PML <> ''
AND TC400F.T40PML = TC403F.T43MDL
WHERE TC400F.T40STS = '10'
AND TC400F.RowNUM BETWEEN
(P_STARTROW + (P_PAGENOS - 1) * P_NBROFRCDS)
AND (P_STARTROW + (P_PAGENOS - 1) * P_NBROFRCDS +
P_NBROFRCDS - 1);
Given above is a cursor declaration in the SP code which I am struggling to understand. The very first WITH itself seems to be mysterious. I have used it along with temporary table names but this is the first time, Im seeing something of this sort which seems to be an SP or UDF? Can someone please guide me on how to understand and make sense out of all this?
Adding further to the question, the actual requirement here is to arrange the data in the XML such a way that that those records which have TC401F.T41SBI field populated should appear in the beginning of the XML output..
This field is being selected as below in the code:
trim(TC401F.T41SBI) AS SUB_BOM_ID. If this field is non-blank, this should appear first in the XML and any records with this field value Blank should appear only after. What would be the best approach to do this? Using ORDER BY in any way does not really seem to help as the XML is actually created through some functions and ordering by does not affect how the items are arranged within the XML. One approach I could think of was using a where clause where TC401F.T41SBI <> '' first then append those records where TC401F.T41SBI = ''
Best I can do is help with the CTE.
WITH
MDI_BOM_COMP(PROD_ID,SITE_ID, xml ) AS (
SELECT TC401F.T41PID,TC401F.T41SID,
This just generates a table named MDI_BOM_COMP with three columns named PROD_ID, SITE_ID, and XML. The table will have one record for each PROD_ID, SITE_ID, and the contents of XML will be an XML snippet with all the components for that product and site.
Now the XML part can be a bit confusing, but if we break it down into it's scalar and aggregate components, we can make it a bit more understandable.
First ignore the grouping. so the CTE retrieves each row in TC401F. XMLELEMENT and XMLFORREST are scalar functions. XMLELEMENT creates a single XML element The tag is the first parameter, and the content of the element is the second in the above example. XMLFORREST is like a bunch of XMLELEMENTs concatenated together.
XMLSERIALIZE(
XMLAGG(
XMLELEMENT( NAME "MDI_BOM_COMP",
XMLFOREST(
trim(TC401F.T41CTY) AS COMPONENT_TYPE,
TC401F.T41LNO AS COMP_NUM,
trim(TC401F.T41CTO) AS CTRY_OF_ORIGIN,
trim(TC401F.T41DSC) AS DESCRIPTION,
TC401F.T41EFR AS EFFECTIVE_FROM,
TC401F.T41EFT AS EFFECTIVE_TO,
trim(TC401F.T41MID) AS MANUFACTURER_ID,
trim(TC401F.T41MOC) AS MANUFACTURER_ORG_CODE,
trim(TC401F.T41CNO) AS PROD_ID,
trim(TC401F.T41POC) AS PROD_ORG_CODE,
TC401F.T41QPR AS QTY_PER,
trim(TC401F.T41SBI) AS SUB_BOM_ID,
trim(TC401F.T41SBO) AS SUB_BOM_ORG_CODE, --ADB01
trim(TC401F.T41VID) AS SUPPLIER_ID,
trim(TC401F.T41SOC) AS SUPPLIER_ORG_CODE,
TC401F.T41UCT AS UNIT_COST
)
)
) AS CLOB(1M)
So in the example, for each row in the table, XMLFORREST creates a list of XML elements, one for each of COMPONENT_TYPE, COMP_NUM, CTRY_OF_ORIGIN, etc. These elements form the content of another XML element MDI_BOM_COMP which is created by XMLELEMENT.
Now for each row in the table we have selected PROD_ID, SITE_ID, and created some XML. Next we group by PROD_ID, and SITE_ID. The aggregation function XMLAGG will collect all the XML for each PROD_ID and SITE_ID, and concatenate it together.
Finally XMLSERIALIZE will convert the internal XML representation to the string format we all know and love ;)
I think I found the answer for my requirement. I had to add an order by field name after XMLELEMENT function

Send query to CFWheels selectTag form helper, or list with commas in string elements

I have a query that returns names in <lastnamd>, <firstname> format such as
<cfquery name="instructorSelectList" dataSource="GIRSReport">
SELECT instructor_DBID,
last_name + ', ' + first_name as instructor_name,
hid
FROM instructors
WHERE working_status = 'active'
ORDER BY last_name, first_name
</cfquery>
I want to use this query for a selectTag form helper. If I do:
#selectTag
(
name="inst",
id="program",
options="#ValueList(instructorSelectList.instructor_name)#",
valueField="#ValueLIst(instructorSelectList.instructor_DBID)#",
display="#ValueList(instructorSelectList.instructor_name)#",
selected="",
label="HID",
multiple="no",
includeBlank="true",
size=1,
class="form-control",
prepend="<br/>"
)#
Then I get a list like <lastname1>, <firstname1>, <lastname2>, <firstname2>, ...
which is obviously not what I want.
If I just try to pass the options parameter a query, such as options="#instructorSelectList.instructor_name#", the options don't fill properly.
The idea is to use a form helper equivalent to
<cfselect
name="inst"
query="instructorSelectList"
queryPosition="below"
value="instructor_DBID"
display="instructor_name"
label="HID" size=1
class="form-control">
<option value=""></option>
</cfselect>
I think what you're needing is to pass a query name to Options, without quotes (or in quotes surrounded by hashes) while the column names are quoted.
#selectTag
(
name="inst",
id="program",
options=instructorSelectList,
valueField="instructor_DBID",
textField="instructor_name",
selected="",
label="HID",
multiple="no",
includeBlank="true",
size=1,
class="form-control",
prepend="<br/>"
)#
More Info: CFWheels selectTag() documentation

TRIM not works with lines and tabs of a xpath in PostgreSQL?

With this query
SELECT trim(title) FROM (
SELECT
unnest( xpath('//p[#class="secTitle1"]', xmlText )::varchar[] ) AS title
FROM t1
) as t2
and XML input text with lines and spaces,
<root>
...
<p class="x">
text text
text text
</p><p> ...</p>
...
</root>
The trim() have no effect (!). It is a PostgreSQL bug? How to apply fn:normalize-space() with the XPath? I need something like "WHERE title is not null"? (Oracle is simpler...) How to do this simple query with PostreSQL?
Workaround
I need a well-configured build-in function, not a workaround... But I need to work and to show results, so I am using regular expression...
SELECT id, TRIM(regexp_replace(tit, E'[\\n\\r\\t ]+', ' ', 'g')) AS tit
FROM (
SELECT
id, -- xpath returns array of 1, 2, or more strings
unnest( xpath('//p[#class="secTitle1"]', texto )::VARCHAR[] ) AS tit
FROM t
) AS tmp
So, a "only simple space trim" is not friendly, not util (!).
EDIT after #mu comment
I try
SELECT id, TRIM(tit, E'\\n\\r\\t') AS tit
and
SELECT id, TRIM(tit, '\n\r\t') AS tit
both NOT WORKs.
QUESTION REMAINS:
there are no TRIM-option or postgresql configuration to say to TRIM work as it is required?
can I use normalize-space() at xpath? How?
I am using PostgreSQL 9.1, need to upgrade?
It works in 9.2, and it works on 8.4 too.
postgres=# select trim(unnest(string_to_array(e'\t\tHello\n\t\tHello\n\t\tHello', e'\n')), e'\t');
btrim
-------
Hello
Hello
Hello
(3 rows)
your regexp replace any char \n or \r or \t, but trim working with string "\n\r\t". It has different meaning than you expect.

XML DML query for attribute

declare #myDoc xml
set #myDoc = '<Form xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.mydomain.org/MySchema.xsd" SectionId="ABCD" Description="Some stuff">
<ProductDescription ProductID="1" ProductName="Road Bike">
<Features>
<Warranty>1 year parts and labor</Warranty>
<Maintenance>3 year parts and labor extended maintenance is available</Maintenance>
</Features>
</ProductDescription>
</Form>'
;WITH XMLNAMESPACES( 'http://www.w3.org/2001/XMLSchema-instance' as xsi, 'http://www.w3.org/2001/XMLSchema' as xsd, DEFAULT 'http://www.mydomain.org/MySchema.xsd' )
SELECT #myDoc.value('/Form[#SectionId][0]', 'varchar')
I need to obtain the attribute value of SectionId as a nvarchar ? how do I do it ?...
T and R
Mark
You could write it even simpler:
;WITH XMLNAMESPACES(DEFAULT 'http://www.mydomain.org/MySchema.xsd')
SELECT #myDoc.value('(/Form/#SectionId)[1]', 'VARCHAR(100)') AS SectionId
Since you're never using/referring to any of the xsi or xsd namespaces, there's no need to declare those.
And since you're only fetching one attribute from one element, there's really no point in using the .nodes() function to create an internal "dummy table", either.
;WITH XMLNAMESPACES( 'http://www.w3.org/2001/XMLSchema-instance' as xsi, 'http://www.w3.org/2001/XMLSchema' as xsd, DEFAULT 'http://www.mydomain.org/MySchema.xsd' )
SELECT Node.value('#SectionId', 'VARCHAR(100)') AS SectionId
FROM #myDoc.nodes('/Form') TempXML (Node);