XPATH ORACLE how to get values from parent node - oracle

My query ouhgt to transform XML into a table (data set):
with UFD_data as
(select xmltype(
'<Document>
<Lines>
<Line>
<LineNumber>1</LineNumber>
<Fields>
<Field>
<FieldName>A1</FieldName>
<FieldType>B1</FieldType>
<FieldValue>C1</FieldValue>
</Field>
</Fields>
</Line>
<Line>
<LineNumber>2</LineNumber>
<Fields>
<Field>
<FieldName>A2</FieldName>
<FieldType>B2</FieldType>
<FieldValue>C2</FieldValue>
</Field>
</Fields>
</Line>
<Line>
<LineNumber>3</LineNumber>
</Line>
</Lines>
</Document>
') xml_data from dual)
select xml_data,
x.*
from UFD_data sd
cross join xmltable('/Document/Lines/Line'
passing sd.xml_data
columns "LineNumber" varchar2(20) path 'LineNumber',
"FieldName" varchar2(255) path 'Fields/Field/FieldName',
"FieldType" varchar2(255) path 'Fields/Field/FieldType',
"FieldValue" varchar2(4000) path 'Fields/Field/FieldValue'
) x
but there is a problem when added more than one group with fields:
with UFD_data as
(select xmltype(
'<Document>
<Lines>
<Line>
<LineNumber>1</LineNumber>
<Fields>
<Field>
<FieldName>A1</FieldName>
<FieldType>B1</FieldType>
<FieldValue>C1</FieldValue>
</Field>
<Field>
<FieldName>D1</FieldName>
<FieldType>E1</FieldType>
<FieldValue>F1</FieldValue>
</Field>
</Fields>
</Line>
<Line>
<LineNumber>2</LineNumber>
<Fields>
<Field>
<FieldName>A2</FieldName>
<FieldType>B2</FieldType>
<FieldValue>C2</FieldValue>
</Field>
</Fields>
</Line>
<Line>
<LineNumber>3</LineNumber>
</Line>
</Lines>
</Document>
') xml_data from dual)
select xml_data,
x.*
from UFD_data sd
cross join xmltable('/Document/Lines/Line'
passing sd.xml_data
columns "LineNumber" varchar2(20) path 'LineNumber',
"FieldName" varchar2(255) path 'Fields/Field/FieldName',
"FieldType" varchar2(255) path 'Fields/Field/FieldType',
"FieldValue" varchar2(4000) path 'Fields/Field/FieldValue'
) x
The error returned :
ORA-19279: XPTY0004 - XQuery dynamic TYPE mismatch: expected singleton SEQUENCE
How to change XPATH expession to get results like below:
LineNumber
FieldName
FieldType
FieldValue
1
A1
B2
C1
1
D1
E2
F1
2
A2
B2
C2
3
How to change XPATH expession to get results like below:
LineNumber
FieldName
FieldType
FieldValue
1
A1
B2
C1
1
D1
E2
F1
2
A2
B2
C2
3

Use two XMLTABLEs, passing Fields from the first to the second:
SELECT l."LineNumber",
f.*
FROM UFD_data sd
CROSS JOIN XMLTABLE(
'/Document/Lines/Line'
PASSING sd.xml_data
COLUMNS
"LineNumber" varchar2(20) PATH 'LineNumber',
Fields XMLTYPE PATH 'Fields'
) l
LEFT OUTER JOIN XMLTABLE(
'/Fields/Field'
PASSING l.fields
COLUMNS
"FieldName" varchar2(255) PATH 'FieldName',
"FieldType" varchar2(255) PATH 'FieldType',
"FieldValue" varchar2(4000) PATH 'FieldValue'
) f
ON (1 = 1)
Which outputs:
LineNumber
FieldName
FieldType
FieldValue
1
A1
B1
C1
1
D1
E1
F1
2
A2
B2
C2
3
null
null
null
fiddle

Related

Select the C1 values having 0th line(C2) has value in C3 and all other lines(C2) having value as NULL - Oracle

CREATE TABLE TABLE1
(
C1 VARCHAR2(3), C2 VARCHAR2(3), C3 VARCHAR2(4)
);
INSERT INTO TABLE1 VALUES('A', '0', '1234');
INSERT INTO TABLE1 VALUES('A', '1', '4568');
INSERT INTO TABLE1 VALUES('A', '2', '5432');
INSERT INTO TABLE1 VALUES('B', '0', '3562');
INSERT INTO TABLE1 VALUES('B', '1', Null);
INSERT INTO TABLE1 VALUES('B', '2', Null);
INSERT INTO TABLE1 VALUES('C', '0', '2132');
INSERT INTO TABLE1 VALUES('C', '1', Null);
INSERT INTO TABLE1 VALUES('C', '2', '5431');
When you execute above query, we get the data into TABLE1 and each unique value of C1 column corresponds to 3 lines i.e. 0,1,2 (in C2 column). What is the query to select the data of column1 having its first line i.e. 0 in column2 has a value(which is not null) in C3 and all other lines of C2 have a value as Null in C3.
The answer for above example is
C1 C2 C3
------------------
B 0 3562
B 1 Null
B 2 Null
There are various rows. For each unique value of C1 can have multiple lines i.e. 0 to 100 etc. in C2 but I have taken above one as an example. In above you can see that A has values in all the 3 lines. B has value in 0th line but as Null in all other lines. C has values in 0th line and 2nd line but Null in 1st line. We need to select the unique value of C1 having value in 0th line and Null in other lines
Perhaps something like this:
select *
from table1
where c1 in (
select c1
from table1
group by c1
having count(case when c2 = 0 then c3 end) = 1
and count(c3) = 1
)
order by c1, c2
;
C1 C2 C3
--- --- ----
B 0 3562
B 1
B 2
This reads the base data twice. If you use analytic functions instead, you can have the base data read just once, but analytic functions themselves are slower than traditional aggregation. If this query works for you, but it is slow, you can try the analytic functions approach just to make sure, but I expect it will be slower, not faster.
Is column c2 supposed to be numeric? I treated it as such, but in your sample data you gave it as strings.

How to generate XML using all the columns as attributes and rows as XML tag named <ROW>

Requirement : Hello, I need to generate a XML out of the results produced by a CTE. Each row should be represented by a new XML tag and all column values are its XML attributes.
Conditions : The Query inside the CTE is dynamic hence the name and number and columns produced by it cannot be known in advance.
For generating the XML I am currently running the query inside CTE and typing each of column names inside XMLATTRIBUTES() function as shown in below example:-
with cte as(
select 'john' as name_,
'2021' as dept,
'26' as age
FROM dual
)
SELECT
XMLELEMENT("ROW", XMLAGG( XMLELEMENT("ROW", XMLATTRIBUTES( name_,dept,age ) ) ) )
FROM
cte;
Please suggest a way to include all the columns produced by CTE query without typing all the column names
You can use dbms_xmlgen to generate an XML version of your CTE; the slight trick is create a context on the fly form a ref cursor, using the cursor() function:
with cte as(
select 'john' as name_,
'2021' as dept,
'26' as age
FROM dual
)
SELECT
dbms_xmlgen.getxmltype(dbms_xmlgen.newcontext(cursor(select * from cte)))
from dual;
which generates:
<ROWSET>
<ROW>
<NAME_>john</NAME_>
<DEPT>2021</DEPT>
<AGE>26</AGE>
</ROW>
</ROWSET>
Then riffing off this answer, you can convert the column nodes to attributes:
with cte as(
select 'john' as name_,
'2021' as dept,
'26' as age
FROM dual
)
SELECT
dbms_xmlgen
.getxmltype(dbms_xmlgen.newcontext(cursor(select * from cte)))
.transform(xmltype('<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="ROWSET">
<ROWSET>
<xsl:apply-templates/>
</ROWSET>
</xsl:template>
<xsl:template match="ROW">
<ROW>
<xsl:for-each select="*">
<xsl:attribute name="{name()}">
<xsl:value-of select="text()"/>
</xsl:attribute>
</xsl:for-each>
</ROW>
</xsl:template>
</xsl:stylesheet>'))
from dual;
which gives you:
<ROWSET>
<ROW NAME_="john" DEPT="2021" AGE="26"></ROW>
</ROWSET>
I've left the root node as rowset but if you do really want both the root and inner nodes to be called row you can just change that in the XLST document. And you can add a tostringval() or toclobval() call if you don't want the result as an XMLType.
db<>fiddle

How to display xmltype columns as table

I have the XML type values as below structure
<row id=“124”>
<c1>Name</c1>
<c2>Name2</c2>
</row>
The columns is not fixed and it will be different for each record.
Expected result is
Result
Name
Name2
You could use wild cards and positions to get whatever value is in each node, providing as many columns clauses as you might possible have nodes under a row, and each of those defined as string with the maximum length you expect from any column (maybe 4000, but 30 here for brevity):
select *
from xmltable (
'/row'
passing xmltype('<row id="124">
<c1>Name</c1>
<c2>Name2</c2>
</row>')
columns
col1 varchar2(30) path '*[1]',
col2 varchar2(30) path '*[2]',
col3 varchar2(30) path '*[3]'
);
COL1 COL2 COL3
------------------------------ ------------------------------ ------------------------------
Name Name2
And you could then unpivot that to get the output you stated, again with as many unpivot clauses as you have XMLTable columns clauses:
select result
from xmltable (
'/row'
passing xmltype('<row id="124">
<c1>Name</c1>
<c2>Name2</c2>
</row>')
columns
col1 varchar2(30) path '*[1]',
col2 varchar2(30) path '*[2]',
col3 varchar2(30) path '*[3]'
)
unpivot (result for x in (col1 as 1, col2 as 2, col3 as 3));
RESULT
------------------------------
Name
Name2
You could also do a separate XMLQuery call for each row and union them together, but that seems more complicated and less efficient.
If your data is in a table then you can extend that - here with a CTE to represent the table:
with your_table(xml_col) as (
select xmltype('<row id="124">
<c1>Name</c1>
<c2>Name2</c2>
</row>')
from dual
union all
select xmltype('<row id="125">
<x>Value X</x>
<y>Value Y</y>
<z>999</z>
</row>')
from dual
)
select id, result
from your_table t
cross join xmltable (
'/row'
passing t.xml_col
columns
id number path '#id',
col1 varchar2(30) path '*[1]',
col2 varchar2(30) path '*[2]',
col3 varchar2(30) path '*[3]'
) x
unpivot (result for x in (col1 as 1, col2 as 2, col3 as 3));
ID RESULT
---------- ------------------------------
124 Name
124 Name2
125 Value X
125 Value Y
125 999
You could also extend this to include the original node name, which might be helpful for understanding the values:
with your_table(xml_col) as (
...
)
select id, col, result
from your_table t
cross join xmltable (
'/row'
passing t.xml_col
columns
id number path '#id',
col1_name varchar2(30) path '*[1]/local-name()',
col2_name varchar2(30) path '*[2]/local-name()',
col3_name varchar2(30) path '*[3]/local-name()',
col1 varchar2(30) path '*[1]',
col2 varchar2(30) path '*[2]',
col3 varchar2(30) path '*[3]'
) x
unpivot (
(col, result) for x in (
(col1_name, col1) as 1, (col2_name, col2) as 2, (col3_name, col3) as 3
)
);
ID COL RESULT
---------- ------------------------------ ------------------------------
124 c1 Name
124 c2 Name2
125 x Value X
125 y Value Y
125 z 999
db<>fiddle, though the version that is running seems to have a bug in the display of the intermediate (pre-unpivot) results from a table, so I've shown with both a table and CTE for those.

How to generate select query for a table based on the value of another table in Oracle

I have two tables A and B.
Table A has structure like below:
col1 | col2 | col3 | col4 | col5 | ....
Table B has only one entry with many columns, for example ( just taking 5) :
c1 c2 c3 c4 c5 ...
-- -- -- -- --
1 0 1 1 0 ...
Now I want to generate query dynamically in a stored procedure based the value of table B row. Only select columns which has corresponding value 1.
Example 1: For above entry query will look like :
select col1,
col3,
col4
from A;
Example 2 : If entry in B is like below
c1 c2 c3 c4 c5 ...
-- -- -- -- --
0 0 1 1 0 ...
For above entry query will look like :
select col3,
col4
from A;
It's not a good table design to store columns as lookup values. You may achieve it using UNPIVOT, but the mapping has to be explicitly listed.
SELECT
'SELECT ' ||LISTAGG(
CASE
WHEN col = 1 THEN val
END,',') WITHIN GROUP(
ORDER BY col
) ||' FROM A' as query
FROM b UNPIVOT ( col
FOR val
IN ( c1 as 'COL1',
c2 as 'COL2',
c3 as 'COL3',
c4 as 'COL4',
c5 as 'COL5') );
demo

How to get the failing column name for ora-01722: invalid number when inserting

How to get the failing column name for ora-01722: invalid number when inserting.
I have two tables. Table A - all hundred columns are varchar2 and table B one half varchar2 other half number.
table A ( c1 varchar2(100), c2 varchar2(100) ... c100 varchar2(100) )
table B ( c1 number, c2 number .. c49 number, c50 varchar2(100), c51 varchar2(100) ... c100 varchar2(100) )
Need to load table a into table b. the mapping is not 1 to 1. column names are not matching.
insert into b ( c1, c2, c3, c4 .. c50,c51 .. c100 ) select to_number(c20) + to_number(c30), to_number(c10), to_number(c80) .. c4, c5 .. c40 from a.
It takes a lot of time to find the failing column manually in case of ora-01722.
So i tried - log errors into err$_b ('INSERT') reject limit unlimited.
It gives you failing rows, but not the column.
Still need to find the failing column manually . Not much of help.
Tried error handling, but always get 0 offset position with
exception when others then
v_ret := DBMS_SQL.LAST_ERROR_POSITION;
dbms_output.put_line(dbms_utility.format_error_stack);
dbms_output.put_line('Error at offset position '||v_ret);
I could use custom is_number function to check if number before inserting.
But in this case I will get null target value not knowing if source is empty or it has wrong number format.
It is possible automate the process by running data check to find failing rows and columns.
If mapping is one to one - then simple data dictionairy query could generate the report to show what columns will fail.
select 'select col_name, count(*) rc, max(rowid_col) rowid_example from (' from dual union all
select txt from
(
with s as ( select t.owner, t.table_name, t.column_name, t.column_id
from all_tab_columns s join all_tab_columns t on s.owner =t.owner and s.column_name = t.column_name
where lower(s.table_name) = 'a' and s.data_type = 'VARCHAR2' and
lower(t.table_name) = 'b' and t.data_type = 'NUMBER' )
select 'select ''' || column_name ||''' col_name, rowid rowid_col, is_number('|| column_name || ') is_number_check from ' || owner ||'.'|| table_name ||
case when column_id = (select max(column_id) from s )
then ' ) where is_number_check = 0 group by col_name'
else ' union all' end txt
from s order by column_id
)
If there is no one to one mapping between table columns, then you could define and store the mapping into a separate table and then run the report.
Is there something more oracle could help in finding the failing column?
Are there any better manual ways to quicly find it?

Resources