Can I alias columns from a cursor in SELECT statement in Oracle? - sql

I am writing a client application that calls a stored procedure from an Oracle database via a select statement. The stored procedure returns a cursor. I need to define aliases for the columns returned by this cursor, and I need to do it within my select statement.
I cannot make any changes to the Oracle database. I cannot write any PLSQL. The only thing I can do with this database is query it.
Please advise.
Background: This stored procedure is one of many called inside an application framework. Currently, all calls return their results in XML format, using this syntax to do the conversion:
select XMLType.createXML(package_name.storedProcName('1', '2', '3')).getClobVal() as sresult from dual;
However, this cursor contains two columns with the same name (specifically "NAME"). When this query is run in TOAD, the column automatically gets appended a "_1", however the XMLType results in illogical XML, like this:
<?xml version="1.0"?>
<ROWSET>
<ROW>
<ID>1</ID>
<NAME>BRUCE WAYNE</NAME>
<NAME>BATMAN</NAME>
</ROW>
</ROWSET>
This is why I must alias the columns before they are converted to XMLType. I want the query output to contain no duplicate column names so that the XML can be like this instead (with no duplicate tags):
<?xml version="1.0"?>
<ROWSET>
<ROW>
<ID>1</ID>
<NAME>BRUCE WAYNE</NAME>
<OTHER_NAME>BATMAN</OTHER_NAME>
</ROW>
</ROWSET>

i would go for a stylesheet for this.
eg:
SQL> select XMLType.createXML(foo()).transform(xmltype('<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
2 <xsl:template match="/ROWSET/ROW/NAME[2]">
3 <NAME_1>
4 <xsl:apply-templates select="#*|node()"/>
5 </NAME_1>
6 </xsl:template>
7 <xsl:template match="#*|node()">
8 <xsl:copy>
9 <xsl:apply-templates select="#*|node()"/>
10 </xsl:copy>
11 </xsl:template>
12 </xsl:stylesheet>')) as sresult from dual
13 /
SRESULT
--------------------------------------------------------------------------------
<ROWSET>
<ROW>
<ID>1</ID>
<NAME>BRUCE WAYNE</NAME>
<NAME_1>BATMAN</NAME_1>
</ROW>
<ROW>
<ID>2</ID>
<NAME>CLARK KENT</NAME>
<NAME_1>SUPERMAN</NAME_1>
</ROW>
</ROWSET>
i.e. we replace the 2nd NAME occurrence (/ROWSET/ROW/NAME[2]) in the ROW element with NAME_1. everything else gets copied as-is.

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

How to validate XML file in Oracle PLSQL

My XML file looks below format
<?xml version = '1.0'?>
<ROWSET>
<ROW>
<EMPNO>7369</EMPNO>
<ENAME>SMITH</ENAME>
<JOB>CLERK</JOB>
<MGR>7902</MGR>
<HIREDATE>12/17/1980 0:0:0</HIREDATE>
<SAL>800</SAL>
<DEPTNO>20</DEPTNO>
</ROW>
<ROW>
<EMPNO>7499</EMPNO>
<ENAME>ALLEN</ENAME>
<JOB>SALESMAN</JOB>
<MGR>7698</MGR>
<HIREDATE>2/20/1981 0:0:0</HIREDATE>
<SAL>1600</SAL>
<COMM>300</COMM>
<DEPTNO>30</DEPTNO>
</ROW>
<ROW>
<EMPNO>7521</EMPNO>
<ENAME>WARD</ENAME>
<JOB>SALESMAN</JOB>
<MGR>7698</MGR>
<HIREDATE>2/22/1981 0:0:0</HIREDATE>
<SAL>1250</SAL>
<COMM>500</COMM>
<DEPTNO>30</DEPTNO>
</ROW -- Now corresponding closing tag is missed in this line
</ROWSET>
Now My corresponding closing tag was missed in any of the nodes. In PLSQL programming how can identify the missing tag? XMLDB option I can use,But I would like do it in PLSQL.
If you only need to check if it is a valid XML, you can simply try converting it, handlng the error; for example:
good xml:
SQL> select xmltype(q'[<?xml version = '1.0'?>
2 <ROWSET>
3 <ROW>
4 <EMPNO>7369</EMPNO>
5 </ROW>
6 </ROWSET>]')
7 from dual;
XMLTYPE(Q'[<?XMLVERSION='1.0'?><ROWSET><ROW><EMPNO>7369</EMPNO></ROW></ROWSET>]'
--------------------------------------------------------------------------------
<?xml version="1.0"?>
<ROWSET>
<ROW>
<EMPNO>7369</EMPNO>
</ROW>
</ROWSET
bad xml:
SQL> select xmltype(q'[<?xml version = '1.0'?>
2 <ROWSET>
3 <ROW>
4 <EMPNO>7369</EMPNO>
5 </ROW
6 </ROWSET>]')
7 from dual;
ERROR:
ORA-31011: XML parsing failed
ORA-19202: Error occurred in XML processing
LPX-00231: invalid character 60 ('<') found in a Name or Nmtoken
Error at line 6
ORA-06512: at "SYS.XMLTYPE", line 310
ORA-06512: at line 1

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?

Extract values from XML String in oracle

How do you extract the value of an xml string in oracle e.g.
XML Example
<?xml version="1.0" encoding="UTF-8"?>
<node>
<key label="name">Test</key>
<key label="lastname">Test</key>
</node>
So far this is what I have tried to extract the values
SELECT <COLUMN>, EXTRACTVALUE(xmltype(<COLUMN>),'/node/lastname') AS TEST FROM <TABLE>;
But it always returns a null value.

How to extract element-path from XMLType Node?

I would like to have a select statement on an XML document and one column should return me the path of each node.
For example, given the data
SELECT *
FROM TABLE(XMLSequence(
XMLTYPE('<?xml version="1.0"?>
<users><user><name>user1</name></user>
<user><name>user2</name></user>
<group>
<user><name>user3</name></user>
</group>
<user><name>user4</name></user>
</users>').extract('/*//*[text()]'))) t;
Which results in
column_value
--------
<user><name>user1</name></user>
<user><name>user2</name></user>
<user><name>user3</name></user>
<user><name>user4</name></user>
I'd like to have a result like this:
path value
------------------------ --------------
/users/user/name user1
/users/user/name user2
/users/group/user/name user3
/users/user/name user4
I can not see how to get to this. I figure there are two thing that have to work together properly:
Can I extract the path from an XMLType with a single operation or method, or do I have to do this with string-magic?
What is the correct XPath expression so that I do get the whole element path (if thats possible), eg. <users><group><user><name>user3</name></user></group></user> insead of <user><name>user3</name></user>?
Maybe I am not understanding XMLType fully, yet. It could be I need a different approach, but I can not see it.
Sidenotes:
In the final version the XML document will be coming from CLOBs of a table, not a static document.
The path column can of course also use dots or whatever and the initial slash is not the issue, any representation would do.
Also I would not mind if every inner node also gets a result row (possibly with null as value), not only the ones with text() in it (which is what I am really interested in).
In the end I will need the tail element of path separate (always "name" in the example here, but this will vary later), i.e. ('/users/groups/user', 'name', 'user3'), I can deal with that separately.
You can achieve that with help of XMLTable function from Oracle XML DB XQuery function set:
select * from
XMLTable(
'
declare function local:path-to-node( $nodes as node()* ) as xs:string* {
$nodes/string-join(ancestor-or-self::*/name(.), ''/'')
};
for $i in $rdoc//name
return <ret><name_path>{local:path-to-node($i)}</name_path>{$i}</ret>
'
passing
XMLParse(content '
<users><user><name>user1</name></user>
<user><name>user2</name></user>
<group>
<user><name>user3</name></user>
</group>
<user><name>user4</name></user>
</users>'
)
as "rdoc"
columns
name_path varchar2(4000) path '//ret/name_path',
name_value varchar2(4000) path '//ret/name'
)
For me XQuery looks at least more intuitive for XML data manipulation than XSLT.
You can find useful set of XQuery functions here.
Update 1
I suppose that you need totally plain dataset with full data at last stage.
This target can be reached by complicated way, constructed step-by-step below, but this variant is very resource-angry. I propose to review final target (selecting some specific records, count number of elements etc.) and after that simplify this solution or totally change it.
Update 2
All steps deleted from this Update except last because #A.B.Cade proposed more elegant solution in comments.
This solution provided in Update 3 section below.
Step 1 - Constructing dataset of id's with corresponding query results
Step 2 - Aggregating to single XML row
Step 3 - Finally get full plain dataset by querying constracted XML with XMLTable
with xmlsource as (
-- only for purpose to write long string only once
select '
<users><user><name>user1</name></user>
<user><name>user2</name></user>
<group>
<user><name>user3</name></user>
</group>
<user><name>user4</name></user>
</users>' xml_string
from dual
),
xml_table as (
-- model of xmltable
select 10 id, xml_string xml_data from xmlsource union all
select 20 id, xml_string xml_data from xmlsource union all
select 30 id, xml_string xml_data from xmlsource
)
select *
from
XMLTable(
'
for $entry_user in $full_doc/full_list/list_entry/name_info
return <tuple>
<id>{data($entry_user/../#id_value)}</id>
<path>{$entry_user/name_path/text()}</path>
<name>{$entry_user/name_value/text()}</name>
</tuple>
'
passing (
select
XMLElement("full_list",
XMLAgg(
XMLElement("list_entry",
XMLAttributes(id as "id_value"),
XMLQuery(
'
declare function local:path-to-node( $nodes as node()* ) as xs:string* {
$nodes/string-join(ancestor-or-self::*/name(.), ''/'')
};(: function to construct path :)
for $i in $rdoc//name return <name_info><name_path>{local:path-to-node($i)}</name_path><name_value>{$i/text()}</name_value></name_info>
'
passing by value XMLParse(content xml_data) as "rdoc"
returning content
)
)
)
)
from xml_table
)
as "full_doc"
columns
id_val varchar2(4000) path '//tuple/id',
path_val varchar2(4000) path '//tuple/path',
name_val varchar2(4000) path '//tuple/name'
)
Update 3
As mentioned by #A.B.Cade in his comment, there are really simple way to join ID's with XQuery results.
Because I don't like external links in answers, code below represents his SQL fiddle, a little bit adapted to the data source from this answer:
with xmlsource as (
-- only for purpose to write long string only once
select '
<users><user><name>user1</name></user>
<user><name>user2</name></user>
<group>
<user><name>user3</name></user>
</group>
<user><name>user4</name></user>
</users>' xml_string
from dual
),
xml_table as (
-- model of xmltable
select 10 id, xml_string xml_data from xmlsource union all
select 20 id, xml_string xml_data from xmlsource union all
select 30 id, xml_string xml_data from xmlsource
)
select xd.id, x.* from
xml_table xd,
XMLTable(
'declare function local:path-to-node( $nodes as node()* ) as xs:string* {$nodes/string-join(ancestor-or-self::*/name(.), ''/'') }; for $i in $rdoc//name return <ret><name_path>{local:path-to-node($i)}</name_path>{$i}</ret> '
passing
XMLParse(content xd.xml_data
)
as "rdoc"
columns
name_path varchar2(4000) path '//ret/name_path',
name_value varchar2(4000) path '//ret/name'
) x
This is not perfect but can be a start:
Here is a sqlfiddle
with xslt as (
select '<?xml version="1.0" ?><xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<records>
<xsl:apply-templates/>
</records>
</xsl:template>
<xsl:template match="//name">
<columns>
<path>
<xsl:for-each select="ancestor-or-self::*">
<xsl:call-template name="print-step"/>
</xsl:for-each>
</path>
<value>
<xsl:value-of select="."/>
</value>
<xsl:apply-templates select="*"/>
</columns>
</xsl:template>
<xsl:template name="print-step">
<xsl:text>/</xsl:text>
<xsl:value-of select="name()"/>
<xsl:text>[</xsl:text>
<xsl:value-of select="1+count(preceding-sibling::*)"/>
<xsl:text>]</xsl:text>
</xsl:template>
</xsl:stylesheet>'
xsl from dual)
, xmldata as
(select xmltransform(xmltype('<?xml version="1.0"?>
<users><user><name>user1</name></user>
<user><name>user2</name></user>
<group>
<user><name>user3</name></user>
</group>
<user><name>user4</name></user>
</users>'), xmltype(xsl)) xd from xslt)
select XT.*
from xmldata c,
xmltable('$x//columns' passing c.xd
as "x"
columns
path_c VARCHAR2(4000) PATH 'path',
value_c VARCHAR2(4000) PATH 'value'
) as XT
This is what I tried to do:
Since you want the "path" I had to use xslt (credits to this post)
Then I used xmltransform for transforming your original xml with the xsl to the
desired output (path, value)
Then I used xmltable to read it as a table
This improves on above answer by A.B.Cade:
<xsl:template name="print-step">
<xsl:variable name="name" select="name()" />
<xsl:text>/</xsl:text>
<xsl:value-of select="$name"/>
<xsl:text>[</xsl:text>
<xsl:value-of select="1+count(preceding-sibling::*[name()=$name])"/>
<xsl:text>]</xsl:text>
</xsl:template>
With result:
/users[1]/user[1]/name[1] user1
/users[1]/user[2]/name[1] user2
/users[1]/group[1]/user[1]/name[1] user3
/users[1]/user[3]/name[1] user4
Instead of:
/users[1]/user[1]/name[1] user1
/users[1]/user[2]/name[1] user2
/users[1]/group[3]/user[1]/name[1] user3
/users[1]/user[4]/name[1] user4