Oracle XPath: Parent Node Attribute? - sql

I'm an XPath newbie; I've found a way to do what I want, but wonder if there's another way that can save me some repetition in my code.
I have this table:
CREATE TABLE t (
quark_p1 VARCHAR2(4)
, quark_p2 VARCHAR2(7)
, quark_p3 VARCHAR2(6)
, one VARCHAR2(1)
, two VARCHAR2(1)
, three VARCHAR2(1)
, four VARCHAR2(1)
, five VARCHAR2(1)
, six VARCHAR2(1)
, seven VARCHAR2(1)
, eight VARCHAR2(1)
, nine VARCHAR2(1)
)
;
The following PL/SQL anonymous block does what I want, extracting into this table the structure from my given XML:
BEGIN
INSERT INTO t (
quark_p1
, quark_p2
, quark_p3
, one
, two
, three
, four
, five
, six
, seven
, eight
, nine
)
SELECT x.quark_p1
, x.quark_p2
, x.quark_p3
, x.one
, x.two
, x.three
, x.four
, x.five
, x.six
, x.seven
, x.eight
, x.nine
FROM XMLTABLE('/BASIS/QUARK'
PASSING XMLTYPE (
'<BASIS>
<QUARK P1="up" P2="charm" P3="bottom">
<NEST>
<ONE>A</ONE>
<TWO>B</TWO>
<THREE>C</THREE>
<FOUR>D</FOUR>
<FIVE>E</FIVE>
<SIX>F</SIX>
<SEVEN>G</SEVEN>
<EIGHT>H</EIGHT>
<NINE>I</NINE>
</NEST>
</QUARK>
<QUARK P1="up" P2="strange" P3="top">
<NEST>
<ONE>J</ONE>
<TWO>K</TWO>
<THREE>L</THREE>
<FOUR>M</FOUR>
<FIVE>N</FIVE>
<SIX>O</SIX>
<SEVEN>P</SEVEN>
<EIGHT>Q</EIGHT>
<NINE>R</NINE>
</NEST>
</QUARK>
</BASIS>')
COLUMNS quark_p1 VARCHAR2(4) PATH '#P1'
, quark_p2 VARCHAR2(7) PATH '#P2'
, quark_p3 VARCHAR2(6) PATH '#P3'
, one VARCHAR2(1) PATH 'NEST/ONE'
, two VARCHAR2(1) PATH 'NEST/TWO'
, three VARCHAR2(1) PATH 'NEST/THREE'
, four VARCHAR2(1) PATH 'NEST/FOUR'
, five VARCHAR2(1) PATH 'NEST/FIVE'
, six VARCHAR2(1) PATH 'NEST/SIX'
, seven VARCHAR2(1) PATH 'NEST/SEVEN'
, eight VARCHAR2(1) PATH 'NEST/EIGHT'
, nine VARCHAR2(1) PATH 'NEST/NINE'
) x;
END;
/
This results in the following, which is what I'm after:
SQL> SELECT * FROM t
2 ;
QUARK_P1 QUARK_P2 QUARK_P3 O T T F F S S E N
-------- -------- -------- - - - - - - - - -
up charm bottom A B C D E F G H I
up strange top J K L M N O P Q R
SQL>
Since the "NEST" level is repeated so often, I'd like to pull it up into the starting node, and still get the same results. I'm looking to do something like the following:
BEGIN
INSERT INTO t (
quark_p1
, quark_p2
, quark_p3
, one
, two
, three
, four
, five
, six
, seven
, eight
, nine
)
SELECT x.quark_p1
, x.quark_p2
, x.quark_p3
, x.one
, x.two
, x.three
, x.four
, x.five
, x.six
, x.seven
, x.eight
, x.nine
-- Notice, I changed the starting node from /BASIS/QUARK to /BASIS/QUARK/NEST....
FROM XMLTABLE('/BASIS/QUARK/NEST'
PASSING XMLTYPE (
'<BASIS>
<QUARK P1="up" P2="charm" P3="bottom">
<NEST>
<ONE>A</ONE>
<TWO>B</TWO>
<THREE>C</THREE>
<FOUR>D</FOUR>
<FIVE>E</FIVE>
<SIX>F</SIX>
<SEVEN>G</SEVEN>
<EIGHT>H</EIGHT>
<NINE>I</NINE>
</NEST>
</QUARK>
<QUARK P1="up" P2="strange" P3="top">
<NEST>
<ONE>J</ONE>
<TWO>K</TWO>
<THREE>L</THREE>
<FOUR>M</FOUR>
<FIVE>N</FIVE>
<SIX>O</SIX>
<SEVEN>P</SEVEN>
<EIGHT>Q</EIGHT>
<NINE>R</NINE>
</NEST>
</QUARK>
</BASIS>')
-- ...and I changed all the paths here
COLUMNS quark_p1 VARCHAR2(4) PATH '../#P1'
, quark_p2 VARCHAR2(7) PATH '../#P2'
, quark_p3 VARCHAR2(6) PATH '../#P3'
, one VARCHAR2(1) PATH 'ONE'
, two VARCHAR2(1) PATH 'TWO'
, three VARCHAR2(1) PATH 'THREE'
, four VARCHAR2(1) PATH 'FOUR'
, five VARCHAR2(1) PATH 'FIVE'
, six VARCHAR2(1) PATH 'SIX'
, seven VARCHAR2(1) PATH 'SEVEN'
, eight VARCHAR2(1) PATH 'EIGHT'
, nine VARCHAR2(1) PATH 'NINE'
) x;
END;
/
This seems to me like it should work, but I get this error:
FROM XMLTABLE('/BASIS/QUARK/NEST'
*
ERROR at line 28:
ORA-06550: line 28, column 10:
PL/SQL: ORA-19110: unsupported XQuery expression
ORA-06550: line 2, column 5:
PL/SQL: SQL Statement ignored
SQL>
Am I missing something simple, or can I not get there from here?
Thanks.

I was surprised to find from
How to get the name of the parent element in an Oracle XPath expression?
that it works if you just put "./" in front of the "../"

Related

Select and insert on the same table

Can you tell me what is wrong with this query:
INSERT INTO properties 
(
f_gen_id(NULL)
, 'ASHAgroup18E'
, entity
, effective_dt
, property_key
, property_value
, created_by
, create_ts
, updated_by
, update_ts
) 
SELECT f_gen_id(NULL)
, 'ASHAgroup18E'
, entity
, effective_dt
, property_key
, property_value
, description
, created_by
, create_ts
, updated_by
, update_ts
-- 'UIL-Migration', CURRENT_TIMESTAMP, 'UIL-Migration', CURRENT_TIMESTAMP 
FROM properties
WHERE group_key = 'ASHAgroup18B';
In the INSERT part you should have column names, not a function
INSERT INTO properties (f_gen_id(NULL),...
replace this with the name of the primary key/id column
INSERT INTO properties (id,...
Update
I guess also the second column in the INSERT is incorrect since it contains a string. Again, replace it with a column name

I've been trying to insert a specific data to my database

I have been trying to insert a data into my database, but it seems am missing a comma , and i have tried to figure out where , but i cant seems to find the issue, any help would be appreciated... newbie here..
thanks
INSERT INTO EMPLOYEES
( FIRST_NAME ,
LAST_NAME ,
EMAIL ,
PHONE_NUMBER ,
HIRE_DATE ,
JOB_ID ,
Salary ,
COMMISSION_PCT ,
MANAGER_ID ,
DEPARTMENT_ID )
VALUES (
'Jackson' ,
'Kayode' ,
'amima#gmail#gmail.com',
216.313.9890 ,
12-12-18 ,
'AD_PRES' ,
10500 ,
0.10 ,
100 ,
90 );
You need single quotes around date constants and strings. I would recommend using the date keyword:
INSERT INTO EMPLOYEES (FIRST_NAME , LAST_NAME , EMAIL , PHONE_NUMBER , HIRE_DATE , JOB_ID , Salary , COMMISSION_PCT , MANAGER_ID , DEPARTMENT_ID)
VALUES ('Jackson', 'Kayode', 'amima#gmail#gmail.com', '216.313.9890',
date '2018-12-12', 'AD_PRES', 10500, 0.10, 100, 90
);

Unpivot Multiple Columns in Oracle SQL

I have a requirement to unpivot a table similar to the table below:
create TABLE dummy_x
(
EMP_NAME VARCHAR2(100)
, EMP_NUMBER VARCHAR2(100)
, PAYROLL_NAME VARCHAR2(100)
, PAYROLL_ID NUMBER
, JOB_TITLE VARCHAR2(100)
, JOB_TITLE_ID NUMBER
, LOCATION VARCHAR2(100)
, LOCATION_ID NUMBER
, NEW_PAYROLL_NAME VARCHAR2(100)
, NEW_PAYROLL_ID NUMBER
, NEW_JOB_TITLE VARCHAR2(100)
, NEW_JOB_TITLE_ID NUMBER
, NEW_LOCATION VARCHAR2(100)
, NEW_LOCATION_ID NUMBER
);
INSERT INTO dummy_x (EMP_NAME, EMP_NUMBER, PAYROLL_NAME, PAYROLL_ID, JOB_TITLE, JOB_TITLE_ID, LOCATION, LOCATION_ID, NEW_PAYROLL_NAME, NEW_PAYROLL_ID, NEW_JOB_TITLE, NEW_JOB_TITLE_ID, NEW_LOCATION, NEW_LOCATION_ID)
VALUES ('MISIP', '111X', 'PAY1', 1, 'DEVELOPER', 2, 'PHIL', 3, 'PAYPHIL', 11, 'PHIL DEV', 22, 'MANILA PH', 33);
INSERT INTO dummy_x (EMP_NAME, EMP_NUMBER, PAYROLL_NAME, PAYROLL_ID, JOB_TITLE, JOB_TITLE_ID, LOCATION, LOCATION_ID, NEW_PAYROLL_NAME, NEW_PAYROLL_ID, NEW_JOB_TITLE, NEW_JOB_TITLE_ID, NEW_LOCATION, NEW_LOCATION_ID)
VALUES ('FHONS', '111Y', 'PAY2', 2, 'SUPPORT', 3, 'HONDURAS', 4, 'PAYHON', 55, 'HON SUP', 66, 'SP SULA HON', 77);
I need the format to be something like below:
EMP_NAME EMP_NUMBER DETAILS CURRENT_VALUE NEW_VALUE
--------- ------------ -------------- -------------- ----------
MISIP 111X PAYROLL_NAME PAY1 PAYPHIL
PAYROLL_ID 1 11
JOB_TITLE DEVELOPER PHIL DEV
JOB_TITLE_ID 2 22
LOCATION PHIL MANILA PH
LOCATION_ID 3 33
FHONS 111Y PAYROLL_NAME PAY2 PAYHON
PAYROLL_ID 2 55
JOB_TITLE SUPPORT HON SUP
JOB_TITLE_ID 3 66
LOCATION HONDURAS SP SULA HON
LOCATION_ID 4 77
This is what i've done so far:
SELECT EMP_NAME
, EMP_NUMBER
, Details
, current_value
FROM (SELECT EMP_NAME
, EMP_NUMBER
, PAYROLL_NAME
, cast(PAYROLL_ID as varchar2(100)) PAYROLL_ID
, JOB_TITLE
, cast(JOB_TITLE_ID as varchar2(100)) JOB_TITLE_ID
, LOCATION
, cast(LOCATION_ID as varchar2(100)) LOCATION_ID
, NEW_PAYROLL_NAME
, cast(NEW_PAYROLL_ID as varchar2(100)) NEW_PAYROLL_ID
, NEW_JOB_TITLE
, cast(NEW_JOB_TITLE_ID as varchar2(100)) NEW_JOB_TITLE_ID
, NEW_LOCATION
, cast(NEW_LOCATION_ID as varchar2(100)) NEW_LOCATION_ID
FROM dummy_x)
unpivot (current_value for Details in (PAYROLL_NAME
, PAYROLL_ID
, JOB_TITLE
, JOB_TITLE_ID
, LOCATION
, LOCATION_ID));
QUERY OUTPUT
EMP_NAME EMP_NUMBER DETAILS CURRENT_VALUE NEW_VALUE
--------- ------------ -------------- -------------- ----------
MISIP 111X PAYROLL_NAME PAY1
MISIP 111X PAYROLL_ID 1
MISIP 111X JOB_TITLE DEVELOPER
MISIP 111X JOB_TITLE_ID 2
MISIP 111X LOCATION PHIL
MISIP 111X LOCATION_ID 3
FHONS 111Y PAYROLL_NAME PAY2
FHONS 111Y PAYROLL_ID 2
FHONS 111Y JOB_TITLE SUPPORT
FHONS 111Y JOB_TITLE_ID 3
FHONS 111Y LOCATION HONDURAS
FHONS 111Y LOCATION_ID 4
How can i add the "New Value" Column data to this script and would it be possible to remove the duplicate data from the EMP_NAME and EMP_NUMBER columns?
To get both columns is much easier than you may think: unpivot ( (current_value, new_value) for details in...) Of course, the "details" should also be given in pairs, each enclosed in ( ... , ... ). For example: for ((payroll_name, new_payroll_name) as 'PAYROLL NAME', .... )
The second requirement doesn't make sense. Which row should keep the EMP_NAME and the EMP_NUMBER, and which should show NULL? What if the row you "think" should get the values doesn't actually exist, or must be deleted in further processing? THAT is something you should do in your front-end application (for example in SQL*Plus, where what you want is easy to do).
Do the cell merging in your application code.
For new_value, try this:
SELECT EMP_NAME
, EMP_NUMBER
, Details
, current_value
, new_value
FROM (SELECT EMP_NAME
, EMP_NUMBER
, PAYROLL_NAME
, cast(PAYROLL_ID as varchar2(100)) PAYROLL_ID
, JOB_TITLE
, cast(JOB_TITLE_ID as varchar2(100)) JOB_TITLE_ID
, LOCATION
, cast(LOCATION_ID as varchar2(100)) LOCATION_ID
, NEW_PAYROLL_NAME
, cast(NEW_PAYROLL_ID as varchar2(100)) NEW_PAYROLL_ID
, NEW_JOB_TITLE
, cast(NEW_JOB_TITLE_ID as varchar2(100)) NEW_JOB_TITLE_ID
, NEW_LOCATION
, cast(NEW_LOCATION_ID as varchar2(100)) NEW_LOCATION_ID
FROM dummy_x)
unpivot ((current_value, new_value) for Details in (
(PAYROLL_NAME, NEW_PAYROLL_NAME) as 'PAYROLL_NAME'
, (PAYROLL_ID , NEW_PAYROLL_ID ) as 'PAYROLL_ID'
, (JOB_TITLE , NEW_JOB_TITLE ) as 'JOB_TITLE'
, (JOB_TITLE_ID, NEW_JOB_TITLE_ID) as 'JOB_TITLE_ID'
, (LOCATION , NEW_LOCATION ) as 'LOCATION'
, (LOCATION_ID , NEW_LOCATION_ID) as 'LOCATION_ID'
)
);

Workaround for ORA-00997: illegal use of LONG datatype

I want to save some data from a system table user_tab_cols, to a temp table so I can take a dump from it.
There are 100,000 rows in it , I have select from user_tab_cols about 1,000 records and d save them into a temp table with this query:
create table temp table as
select * from user_tab_cols where condition...
I had error 'illegal use of longtype' , because of the column DATA_DEFAULT that contain a type of long.
Is there an alterantive way where I can store a long type into another table?
ORA-00997: illegal use of LONG datatype
It is a restriction on usage of LONG data type. You cannot create an object type with a LONG attribute.
SQL> CREATE TABLE t AS SELECT data_default FROM user_tab_cols;
CREATE TABLE t AS SELECT data_default FROM user_tab_cols
*
ERROR at line 1:
ORA-00997: illegal use of LONG datatype
SQL>
Alternatively, you could use TO_LOB as a workaround. Which would convert it into CLOB data type.
For example,
SQL> CREATE TABLE t AS SELECT TO_LOB(data_default) data_default FROM user_tab_cols;
Table created.
SQL> desc t;
Name Null? Type
----------------------------------------- -------- ----------------------------
DATA_DEFAULT CLOB
SQL>
See more examples of workarounds here.
You'll need to create your target table explicitly, not from select *:
create table demo_copy
( table_name varchar2(30)
, column_name varchar2(30)
, data_type varchar2(106)
, data_type_mod varchar2(3)
, data_type_owner varchar2(30)
, data_length number
, data_precision number
, data_scale number
, nullable varchar2(1)
, column_id number
, default_length number
, data_default clob
, num_distinct number
, low_value raw(32)
, high_value raw(32)
, density number
, num_nulls number
, num_buckets number
, last_analyzed date
, sample_size number
, character_set_name varchar2(44)
, char_col_decl_length number
, global_stats varchar2(3)
, user_stats varchar2(3)
, avg_col_len number
, char_length number
, char_used varchar2(1)
, v80_fmt_image varchar2(3)
, data_upgraded varchar2(3)
, hidden_column varchar2(3)
, virtual_column varchar2(3)
, segment_column_id number
, internal_column_id number
, histogram varchar2(15)
, qualified_col_name varchar2(4000) );
(I've made data_default a clob for more convenient querying.)
Then you can insert rows in a PL/SQL loop:
begin
for r in (
select * from user_tab_cols c
where rownum <= 2 -- your filter condition here
)
loop
insert into demo_copy values r;
end loop;
end;
There are some limitations in principle with this approach, as a long column can hold more than the varchar2(32760) that PL/SQL will use in the loop. However, I expect 32K will be enough for most column default expressions.

ORA 00937 while using INSERT INTO SELECT

When I am running the below insert into select statement, I get ORA 00937 because the below query cannot deal with one of the sub selects on APPLICATIONS table. I don't want to hardcode that value. Any suggestions?
Thanks in advance.
insert into CONFIGURATION_PARAMETER_VALUES
( ID
, NAME
, DESCRIPTION
, DATA_TYPE
, VALUE_STRING
, VALUE_INTEGER
, VALUE_DATE
, VALUE_FLOAT
, VALUE_TIMESTAMP
, APPLICATION_ID
, DELETED
)
select NVL(MAX(ID),0)+1
, 'Alert_Statuses_AllExceptNoStatus'
, 'Suspicious'
, 'String'
, 'RBS_EIM_AL_008'
, null
, null
, null
, null
, (select ID from APPLICATIONS where name = 'Rabobank v 1.0.0.0')
, 'N'
from CONFIGURATION_PARAMETER_VALUES
If it's not too late, I'd suggest implementing a SEQUENCE instead of counting. You may not get strict numeric order (there can be gaps), but you'll get a unique value every time:
CREATE SEQUENCE Config_Parm_Values_Seq START WITH <1 + your current max ID>;
Also note that your INSERT as it stands right now will behave as follows:
If there are no records in the table, it will insert nothing.
If there are records in the table, it will double the number of rows in your table every time you execute it.
So, even if you don't use the sequence, I'd consider a "plain old" INSERT instead of an INSERT ... SELECT. This example uses the sequence:
insert into CONFIGURATION_PARAMETER_VALUES
( ID
, NAME
, DESCRIPTION
, DATA_TYPE
, VALUE_STRING
, VALUE_INTEGER
, VALUE_DATE
, VALUE_FLOAT
, VALUE_TIMESTAMP
, APPLICATION_ID
, DELETED
) VALUES (
Config_Parm_Values_Seq.NEXTVAL -- Use seqname.nextval to get
-- the next value from the sequence
, 'Alert_Statuses_AllExceptNoStatus'
, 'Suspicious'
, 'String'
, 'RBS_EIM_AL_008'
, null
, null
, null
, null
, (select MAX(ID) from APPLICATIONS where name = 'Rabobank v 1.0.0.0')
, 'N')
The problem is in your SELECT statement where you are using SELECT NVL(MAX(ID), 0) + 1.
As you are using MAX function in the SELECT list, you must use a GROUP BY, which is not the solution here.
Use something like the following:
insert into CONFIGURATION_PARAMETER_VALUES
( ID
, NAME
, DESCRIPTION
, DATA_TYPE
, VALUE_STRING
, VALUE_INTEGER
, VALUE_DATE
, VALUE_FLOAT
, VALUE_TIMESTAMP
, APPLICATION_ID
, DELETED
)
select (SELECT MAX(ID) FROM configuration_parameter_values) + 1
-- above line instead of NVL(MAX(ID),0)+1
-- You can also put NVL function around the subquery.
, 'Alert_Statuses_AllExceptNoStatus'
, 'Suspicious'
, 'String'
, 'RBS_EIM_AL_008'
, null
, null
, null
, null
, (select ID from APPLICATIONS where name = 'Rabobank v 1.0.0.0')
-- Warning: The above subquery can generate a TOO_MANY_ROWS exception.
-- Use the solution in the other answer to avoid this.
, 'N'
from CONFIGURATION_PARAMETER_VALUES