Parsing multiple columns from XMLTYPE - oracle

I want to select data from a XML file using Oracle SQL.
Here is my XML file:
<worksheet>
<sheetData>
<row>
<column>
<value>0</value>
</column>
<column>
<value>1</value>
</column>
<column>
<value>2</value>
</column>
</row>
<row>
<column>
<value>3</value>
</column>
<column>
<value>4</value>
</column>
<column>
<value>5</value>
</column>
</row>
<row>
<column>
<value>6</value>
</column>
<column>
<value>7</value>
</column>
<column>
<value>8</value>
</column>
</row>
</sheetData>
</worksheet>
The following code is the SQL I'm using to extract the XML (minified in SQL query):
SELECT
*
FROM
XMLTABLE(
'for $i in worksheet/sheetData/row return $i'
PASSING XMLTYPE('<worksheet><sheetData><row><column><value>0</value></column><column><value>1</value></column><column><value>2</value></column></row><row><column><value>3</value></column><column><value>4</value></column><column><value>5</value></column></row><row><column><value>6</value></column><column><value>7</value></column><column><value>8</value></column></row></sheetData></worksheet>')
COLUMNS
column1 CLOB PATH 'column[1]/value',
column2 CLOB PATH 'column[2]/value'
) xml;
And this is the output:
---------------------
| COLUMN1 | COLUMN2 |
|---------|---------|
| 0 | 1 |
| 3 | 4 |
| 6 | 7 |
---------------------
You can see that the third <column> child of <row> is missing from the output but if I add column3 CLOB PATH 'column[3]/value' to the COLUMNS property, it will appear.
My problem is that the XML can have any number of <column> tags and since I don't know the count from the beginning, I can't define a fixed solution.
Can you help to tell me which modification should make my query to work for multiple <column> tags? Or which query should output a dynamic number of <column>?

Easy ways
Predefined redundant amount of columns.
SELECT
*
FROM
XMLTABLE(
'/worksheet/sheetData/row'
PASSING XMLTYPE('<worksheet><sheetData><row><column><value>0</value></column><column><value>1</value></column><column><value>2</value></column></row><row><column><value>3</value></column><column><value>4</value></column><column><value>5</value></column></row><row><column><value>6</value></column><column><value>7</value></column><column><value>8</value></column></row></sheetData></worksheet>')
COLUMNS
column_cnt number path 'count(column)',
column1 varchar2(4000) PATH 'column[1]/value',
column2 varchar2(4000) PATH 'column[2]/value',
--etc. ...
column10 varchar2(4000) path 'column[10]/value'
) xml;
Pivot the result
SELECT
row_nr,
col_nr,
col_value
FROM
XMLTABLE(
'/worksheet/sheetData/row'
PASSING XMLTYPE('<worksheet><sheetData><row><column><value>0</value></column><column><value>1</value></column><column><value>2</value></column></row><row><column><value>3</value></column><column><value>4</value></column><column><value>5</value></column></row><row><column><value>6</value></column><column><value>7</value></column><column><value>8</value></column></row></sheetData></worksheet>')
COLUMNS
columns_xml xmltype path '.',
row_nr FOR ORDINALITY
) xml
,xmltable('row/column' passing columns_xml
columns
col_nr for ordinality,
col_value varchar2(10) path './value/text()')
Hardcore approach.
Alway you can use pipelined table function.
1) Find max count of columns
2) Generate dynamic type
3) Populate dynamic type
4) Two days later enjoy the most sophisticated code written in pl sql :)
Good example of pipelline function below. This is not exactly what you need but you can build on it
Return dynamic result set from sys_refcursor

Related

Liquibase with column type numeric(x, y) is creating postgreSQL table with column type number

I have a spring application connected to a postgreSQL DB. My issue is that this bit of script:
<column name="utmx" type="numeric(20, 5)">
<constraints nullable="true"/>
</column>
<column name="utmy" type="numeric(20, 5)">
<constraints nullable="true"/>
</column>
Is being executed like this:
CREATE TABLE PUBLIC.table (... utmx NUMBER(20, 5), utmy NUMBER(20, 5)...
As you can see, the column type NUMERIC is being changed to NUMBER on table creation.
Why is that? How can I solve it?
Thanks in advance

Liquibase Different Datatypes H2/Oracle

So I do have the following changeset in my dbchangelog:
<changeSet id="04_04_2018" author="codeit">
<createTable tableName="BSP_TABELLE">
<column name="name" type="varchar(255)"/>
<column name="datum" type="datetime"/>
<column name="richtigfalsch" type="boolean"/>
<column name="blub" type="blob"/>
<column name="club" type="clob"/>
<column name="ganzzahl" type="int"/>
<column name="grosseganzzahl" type="bigint"/>
<column name="gleitkommazahl" type="decimal(17,5)"/>
</createTable>
</changeSet>
In H2 I do get what I expected like:
CREATE TABLE BSP_TABELLE
(
NAME VARCHAR(255),
DATUM TIMESTAMP,
RICHTIGFALSCH BOOLEAN,
BLUB BLOB,
CLUB CLOB,
GANZZAHL INTEGER,
GROSSEGANZZAHL BIGINT,
GLEITKOMMAZAHL DECIMAL(17, 5)
)
But in Oracle I do get:
CREATE TABLE BSP_TABELLE
(
NAME VARCHAR2(255),
DATUM TIMESTAMP(6),
RICHTIGFALSCH NUMBER(1),
BLUB BLOB,
CLUB CLOB,
GANZZAHL NUMBER,
GROSSEGANZZAHL NUMBER,
GLEITKOMMAZAHL NUMBER(17, 5)
)
And I expected:
CREATE TABLE BSP_TABELLE
(
NAME VARCHAR2(255),
DATUM TIMESTAMP(6),
RICHTIGFALSCH NUMBER(1),
BLUB BLOB,
CLUB CLOB,
GANZZAHL NUMBER(10),
GROSSEGANZZAHL NUMBER(19,0),
GLEITKOMMAZAHL NUMBER(17, 5)
)
How can I achieve it, that the DataType for Oracle will fit, without modifying the datatypes in H2?
I am currently loading the changelog from the java implementation at runtime like:
Connection connection = openDatabaseConnection();
Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(new JdbcConnection(connection));
String[] validatedLiquibaseChangelogs = validateLiquibaseChangelogs();
for (String validatedChangelog : validatedLiquibaseChangelogs) {
Liquibase liquibase = new liquibase.Liquibase(validatedChangelog, new FileSystemResourceAccessor(), database);
//Updaten der Datenbank
liquibase.update(new Contexts(), new LabelExpression());
}

mybatis batch insert generating duplicate uuid primary key

when i insert single record:
<insert id="add" parameterType="SysUser">
<selectKey keyProperty="id" resultType="String" order="BEFORE">
select replace(uuid(),'-','') from dual
</selectKey>
insert into sys_user(id,user_name,user_email,user_info,user_password,create_time)
values
(#{id,jdbcType=VARCHAR},
#{userName,jdbcType=VARCHAR},
#{userEmail,jdbcType=VARCHAR},
#{userInfo,jdbcType=VARCHAR},
#{userPassword,jdbcType=VARCHAR},
#{createTime,jdbcType=TIMESTAMP})
</insert>
but i want to insert a list, how can i generate the uuid like the single insert above???
<insert id="addSysUsers" parameterType="List" useGeneratedKeys="true" keyProperty="id">
INSERT INTO sys_user(user_name,user_password,user_email)
VALUES
<foreach collection="sysUsers" item="sysUser" separator=",">
(#{sysUser.userName,jdbcType=VARCHAR},#{sysUser.userPassword,jdbcType=VARCHAR},#{sysUser.userEmail,jdbcType=VARCHAR})
</foreach>
</insert>
The simplest way to do it is put the UUID generator in the values block:
<insert id="add" parameterType="SysUser">
insert into sys_user(id,user_name,user_email,user_info,user_password,create_time)
values
(replace(uuid(),'-',''), <!-- create uuid directly here --->
#{userName,jdbcType=VARCHAR},
#{userEmail,jdbcType=VARCHAR},
#{userInfo,jdbcType=VARCHAR},
#{userPassword,jdbcType=VARCHAR},
#{createTime,jdbcType=TIMESTAMP})
</insert>

Loading XML data gets error saying my control file "references a non existent field"

I am getting an error loading an XML document from a text file into an Oracle table using SQL*Loader.
I have created an XML table:
CREATE TABLE TEST_XML OF XMLTYPE
XMLTYPE STORE AS SECUREFILE BINARY XML;
And I have a file test_file.xml:
<ROWSET>
<ROW>
<ID>1</ID>
<TEXT>This is some text</TEXT>
</ROW>
<ROW>
<ID>2</ID>
<TEXT>This is some more text</TEXT>
</ROW>
<ROW>
<ID>3</ID>
<TEXT>This is some other text</TEXT>
</ROW>
<ROW>
<ID>4</ID>
<TEXT>This is also some text</TEXT>
</ROW>
</ROWSET>
I have created a control file test_loading.ctl:
LOAD DATA
INFILE "test_file.xml"
append INTO TABLE TEST_XML
xmltype(XMLDATA)
(
XMLDATA LOBFILE("test_file.xml") TERMINATED BY EOF
)
When I run SQL*Loader using that control file:
sqlldr username/password control=/path/test_loading.ctl
it returns with the following error:
SQL*Loader-416: SDF clause for field XMLDATA in table TEST_XML_ARUN references a non existent field.
What am I doing wrong?
You seem to be mixing up a few ways of doing this. The error is because it's trying to interpret the "test_file.xml" inside LOBFILE() as a field reference.
If you know you will only load one XML document from a single text file you can make your control file:
LOAD DATA
INFILE *
append INTO TABLE TEST_XML
XMLType(XMLDATA)
FIELDS
(
FILL FILLER CHAR(1),
XMLDATA LOBFILE(CONSTANT test_file.xml) TERMINATED BY EOF
)
BEGINDATA
0
The BEGINDATA section has a row with a filler character for each XML doc in the file, and as there's only one, there's a single filler.
Note the CONSTANT which makes it look for a file called that, not a field. The log file shows that static name:
Table TEST_XML, loaded from every logical record.
Insert option in effect for this table: APPEND
Column Name Position Len Term Encl Datatype
------------------------------ ---------- ----- ---- ---- ---------------------
FILL FIRST 1 CHARACTER
(FILLER FIELD)
XMLDATA DERIVED * EOF CHARACTER
Static LOBFILE. Filename is test_file.xml
Table TEST_XML:
1 Row successfully loaded.
0 Rows not loaded due to data errors.
0 Rows not loaded because all WHEN clauses were failed.
0 Rows not loaded because all fields were null.
To use a field you would have a data file with the file name, lets call it test_loading.dat to match the control file name, which contains:
test_file.xml
And a control file which uses that as the INFILE, and the content of its first field as the file name:
LOAD DATA
INFILE test_loading.dat
append INTO TABLE TEST_XML
XMLType(XMLDATA)
FIELDS
(
filename FILLER CHAR(30),
XMLDATA LOBFILE(filename) TERMINATED BY EOF
)
This time the log file shows the name is being retrieved dynamically:
Table TEST_XML, loaded from every logical record.
Insert option in effect for this table: APPEND
Column Name Position Len Term Encl Datatype
------------------------------ ---------- ----- ---- ---- ---------------------
FILENAME FIRST 30 CHARACTER
(FILLER FIELD)
XMLDATA DERIVED * EOF CHARACTER
Dynamic LOBFILE. Filename in field FILENAME
Table TEST_XML:
1 Row successfully loaded.
0 Rows not loaded due to data errors.
0 Rows not loaded because all WHEN clauses were failed.
0 Rows not loaded because all fields were null.
Read more in the documentation.
Either will work for you. As you only have a single file in your example the first version might be slightly simpler, but if you will be loading multiple files (with a table row per file) the second version is more useful.

How to get Oracle next sequence value in liqiibase 1.9.5 for insert

How to get Oracle next sequence value in liqiibase 1.9.5 for insert
<changeSet author="MIGRATE" id="070214010049877">
<insert tableName="MYTABLE">
<column name="ID" valueNumeric=" nextval( 'MYTABLE_SEQ' ) "/>
<column name="NUMBER" value="5"/>
</insert>
</changeSet>
it is trying to put the value of ID " nextval( 'MYTABLE_SEQ' )" in table so it gives error
With Liquibase 1.9.5, there is no support for finding the next sequence value with the <insert> tag. You will have to either drop back to using <sql> or use <modifySql> assuming that existed in 1.9.5.

Resources