Oracle hierarchical query - combine results - oracle

Given a table:
Old_ID New_ID
A B
B C
Multiple ID changes can form a chain A -> B -> C. I can do a hierarchical query.
SELECT Old_ID, New_ID from mytable
CONNECT BY PRIOR New_ID = Old_ID
Since C is the valid new ID for both A and B, the result I'd like to get is:
Old_ID New_ID
A C
B C
So map all old IDs to the latest new ID in the chain.

Use a combination of CONNECT_BY_ROOT( old_id ), to get the start of the hierarchy, and WHERE CONNECT_BY_ISLEAF = 1, to find the last item in the hierarchy:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE your_table( Old_ID, New_ID ) As
SELECT 'A', 'B' FROM DUAL UNION ALL
SELECT 'B', 'C' FROM DUAL;
Query 1:
SELECT CONNECT_BY_ROOT( old_id ) AS old_id,
new_id
FROM your_table
WHERE CONNECT_BY_ISLEAF = 1
CONNECT BY PRIOR New_ID = Old_id
Results:
| OLD_ID | NEW_ID |
|--------|--------|
| A | C |
| B | C |

Related

rewrite query without DENSE_RANK

I have one very slow query and try to optimize response time by using a materialized view. But one part is not compatible with General Restrictions on Fast Refresh.
How to rewrite it without DENSE_RANK?
create table t (id,object_id,log_cre_date) as
select 1,2,to_date('18/5/2010, 08:00','dd/mm/yyyy, hh:mi') from dual union all
select 2,2,to_date('18/5/2010, 10:00','dd/mm/yyyy, hh mi') from dual union all
select 3,3,to_date('18/5/2010, 11:00','dd/mm/yyyy, hh mi') from dual union all
select 4,3,to_date('18/5/2010, 12:10','dd/mm/yyyy, hh mi') from dual union all
select 5,4,to_date('18/5/2010, 12:20','dd/mm/yyyy, hh mi') from dual union all
select 6,4,to_date('18/5/2010, 11:30','dd/mm/yyyy, hh mi') from dual;
SELECT
MAX(t.id) KEEP(DENSE_RANK FIRST ORDER BY log_cre_date ASC) id,
t.object_id
FROM
t
GROUP BY
t.object_id
I am not sure the accepted answer is fast refreshable. Here is a query that definitely is:
SELECT max(cast(to_char(t.log_cre_date,'YYYYMMDDHH24MISS') || lpad(t.id,30,'0') as varchar2(80))) maxid,
t.object_id,
COUNT(*) cnt
FROM t
GROUP BY t.object_id;
The idea is to append the id to the log_cre_date and take the max of the concatenation. That way, you can extract the id you need later.
So, to get the id, you would do this:
SELECT to_char(substr(maxid,-30)) id, object_id
FROM your_materialized_view;
You could put that in a view to hide the complexity.
Here is a full example:
Create the base table
DROP TABLE t;
create table t (id,object_id,log_cre_date) as
select 1,2,to_date('18/5/2010, 08:00','dd/mm/yyyy, hh:mi') from dual union all
select 2,2,to_date('18/5/2010, 10:00','dd/mm/yyyy, hh mi') from dual union all
select 3,3,to_date('18/5/2010, 11:00','dd/mm/yyyy, hh mi') from dual union all
select 4,3,to_date('18/5/2010, 12:10','dd/mm/yyyy, hh mi') from dual union all
select 5,4,to_date('18/5/2010, 12:20','dd/mm/yyyy, hh mi') from dual union all
select 6,4,to_date('18/5/2010, 11:30','dd/mm/yyyy, hh mi') from dual;
Add some constraints to allow fast-refresh MV
ALTER TABLE t MODIFY id NOT NULL;
ALTER TABLE t ADD CONSTRAINT t_pk PRIMARY KEY ( id );
Create a snapshot log to enable fast refresh
--DROP MATERIALIZED VIEW LOG ON t;
CREATE MATERIALIZED VIEW LOG ON t WITH ROWID, PRIMARY KEY (OBJECT_ID, LOG_CRE_DATE) INCLUDING NEW VALUES;
Create the materialized view (note presence of COUNT(*) in select-list. Important!
--DROP MATERIALIZED VIEW t_mv;
CREATE MATERIALIZED VIEW t_mv
REFRESH FAST ON COMMIT AS
SELECT max(cast(to_char(t.log_cre_date,'YYYYMMDDHH24MISS') || lpad(t.id,30,'0') as varchar2(80))) maxid,
t.object_id,
COUNT(*) cnt
FROM t
GROUP BY t.object_id;
Test it out
select to_number(substr(maxid,-30)) id, object_id
from t_mv;
+----+-----------+
| ID | OBJECT_ID |
+----+-----------+
| 2 | 2 |
| 4 | 3 |
| 5 | 4 |
+----+-----------+
DELETE FROM t WHERE id = 5;
COMMIT;
select to_number(substr(maxid,-30)) id, object_id
from t_mv;
+----+-----------+
| ID | OBJECT_ID |
+----+-----------+
| 4 | 3 |
| 5 | 4 |
| 1 | 2 | -- Now ID #1 is the latest for object_id 2
+----+-----------+
Maybe this query will run faster:
select object_id, id
from (
select object_id, first_value(id) over(partition by object_id order by log_cre_date) as id
from t
)
group by object_id, id;
Hope it helps!
I went through the restriction but I am not sure if following query will work or not.
Try this and let us know if it works.
Select t.id, t.object_id from
T join
(SELECT
min(log_cre_date) mindt,
t.object_id
FROM
t
GROUP BY
t.object_id) t1
On t.object_id = t1.object_id
And t.log_cre_date = t1.mindt;
Cheers!!

Oracle update invalid identifier on Update

I get an 'invalid identifier' exception when I run the next script.
As I have seen on this link it should work.
It cannot reference to the 'a2' table under the secound selection, but it should update the row with the related value.
update auto a2 set uuid =
(select uuid from (
select c.uuid, c.pk from color c
join sit s on s.pk = c.sit_fk
--where s.auto_fk = auto.pk
join auto m on m.pk = s.auto_fk
where m.pk = a2.pk
group by c.pk, c.uuid
order by c.pk desc
)
where rownum = 1)
Reference the a2 alias in the outer level of the correlated query, rather than the nested level.
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE auto ( pk, uuid ) AS
SELECT 1, 1 FROM DUAL UNION ALL
SELECT 2, 2 FROM DUAL;
CREATE TABLE color ( pk, uuid, sit_fk ) AS
SELECT 1, 2, 1 FROM DUAL UNION ALL
SELECT 2, 1, 2 FROM DUAL;
CREATE TABLE sit ( pk, auto_fk ) AS
SELECT 1, 1 FROM DUAL UNION ALL
SELECT 2, 2 FROM DUAL;
Query 1:
update auto a2 set uuid =
(select uuid from (
select c.uuid, c.pk, m.pk AS apk from color c
join sit s on s.pk = c.sit_fk
--where s.auto_fk = auto.pk
join auto m on m.pk = s.auto_fk
group by m.pk, c.pk, c.uuid
order by m.pk, c.pk desc
)
where rownum = 1
and a2.pk = apk
)
Results:
Query 2:
SELECT *
FROM auto
Results:
| PK | UUID |
|----|------|
| 1 | 2 |
| 2 | 1 |

REGEXP to capture values delimited by a set of delimiters

My column value looks something like below: [Just an example i created]
{BASICINFOxxxFyyy100x} {CONTACTxxx12345yyy20202x}
It can contain 0 or more blocks of data... I have created the below query to split the blocks
with x as
(select
'{BASICINFOxxxFyyy100x}{CONTACTxxx12345yyy20202x}' a from dual)
select REGEXP_SUBSTR(a,'({.*?x})',1,rownum,null,1)
from x
connect by rownum <= REGEXP_COUNT(a,'x}')
However I would like to further split the output into 3 columns like below:
ColumnA | ColumnB | ColumnC
------------------------------
BASICINFO | F |100
CONTACT | 12345 |20202
The delimiters are always standard. I failed to create a pretty query which gives me the desired output.
Thanks in advance.
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE your_table ( str ) AS
SELECT '{BASICINFOxxxFyyy100x}{CONTACTxxx12345yyy20202x}' from dual
/
Query 1:
select REGEXP_SUBSTR(
t.str,
'\{([^}]*?)xxx([^}]*?)yyy([^}]*?)x\}',
1,
l.COLUMN_VALUE,
NULL,
1
) AS col1,
REGEXP_SUBSTR(
str,
'\{([^}]*?)xxx([^}]*?)yyy([^}]*?)x\}',
1,
l.COLUMN_VALUE,
NULL,
2
) AS col2,
REGEXP_SUBSTR(
str,
'\{([^}]*?)xxx([^}]*?)yyy([^}]*?)x\}',
1,
l.COLUMN_VALUE,
NULL,
3
) AS col3
FROM your_table t
CROSS JOIN
TABLE(
CAST(
MULTISET(
SELECT LEVEL
FROM DUAL
CONNECT BY LEVEL <= REGEXP_COUNT( t.str,'\{([^}]*?)xxx([^}]*?)yyy([^}]*?)x\}')
) AS SYS.ODCINUMBERLIST
)
) l
Results:
| COL1 | COL2 | COL3 |
|-----------|-------|-------|
| BASICINFO | F | 100 |
| CONTACT | 12345 | 20202 |
Note:
Your query:
select REGEXP_SUBSTR(a,'({.*?x})',1,rownum,null,1)
from x
connect by rownum <= REGEXP_COUNT(a,'x}')
Will not work when you have multiple rows of input - In the CONNECT BY clause, the hierarchical query has nothing to restrict it connecting Row1-Level2 to Row1-Level1 or to Row2-Level1 so it will connect it to both and as the depth of the hierarchies gets greater it will create exponentially more duplicate copies of the output rows. There are hacks you can use to stop this but it is much more efficient to put the row generator into a correlated sub-query which can then be CROSS JOINed back to the original table (it is correlated so it won't join to the wrong rows) if you are going to use hierarchical queries.
Better yet would be to fix your data structure so you are not storing multiple values in delimited strings.
SQL> with x as
2 (select '{BASICINFOxxxFyyy100x}{CONTACTxxx12345yyy20202x}' a from dual
3 ),
4 y as (
5 select REGEXP_SUBSTR(a,'({.*?x})',1,rownum,null,1) c1
6 from x
7 connect by rownum <= REGEXP_COUNT(a,'x}')
8 )
9 select
10 substr(c1,2,instr(c1,'xxx')-2) z1,
11 substr(c1,instr(c1,'xxx')+3,instr(c1,'yyy')-instr(c1,'xxx')-3) z2,
12 rtrim(substr(c1,instr(c1,'yyy')+3),'x}') z3
13 from y;
Z1 Z2 Z3
--------------- --------------- ---------------
BASICINFO F 100
CONTACT 12345 20202
Here is another solution, which is derived from the place you left. Your query had already resulted into splitting of a row to 2 row. Below will make it in 3 columns:
WITH x
AS (SELECT '{BASICINFOxxxFyyy100x}{CONTACTxxx12345yyy20202x}' a
FROM DUAL),
-- Your query result here
tbl
AS ( SELECT REGEXP_SUBSTR (a,
'({.*?x})',
1,
ROWNUM,
NULL,
1)
Col
FROM x
CONNECT BY ROWNUM <= REGEXP_COUNT (a, 'x}'))
--- Actual Query
SELECT col,
REGEXP_SUBSTR (col,
'(.*?{)([^x]+)',
1,
1,
'',
2)
AS COL1,
REGEXP_SUBSTR (REGEXP_SUBSTR (col,
'(.*?)([^x]+)',
1,
2,
'',
2),
'[^y]+',
1,
1)
AS COL2,
REGEXP_SUBSTR (REGEXP_SUBSTR (col,
'[^y]+x',
1,
2),
'[^x]+',
1,
1)
AS COL3
FROM tbl;
Output:
SQL> /
COL COL1 COL2 COL3
------------------------------------------------ ------------------------------------------------ ------------------------------------------------ ------------------------------------------------
{BASICINFOxxxFyyy100x} BASICINFO F 100
{CONTACTxxx12345yyy20202x} CONTACT 12345 20202

Delete Oracle with join tables

I have a table that has three columns ( primary key) and I need a delete sentece that allows me to remove the elements I don't need , using a join with the this table and other table, I've tried two delete sentences but they are not working as expected:
First One: This one gets the values I dont need and they are removed from table A, but the issue here is it deletes the values from Table B and C too and those rows can't be removed
DELETE
FROM
(SELECT A.*
FROM TABLE_A A
JOIN TABLE_B B
ON A.CODE =B.CODE
JOIN TABLE_C C
ON B.PRODUCT =C.PRODUCT
WHERE B.VALUE >10000
AND C.RANGE NOT IN (4006, 4005, 4004, 4003, 4002, 4001)
);
**Second One:**The problem with this one is that it removes all the rows from table A, but if I test the query ( select) it returns 5 rows, the ones that should be removed.
DELETE
FROM A WHERE EXIST
(SELECT A.*
FROM TABLE_A A
JOIN TABLE_B B
ON A.CODE =B.CODE
JOIN TABLE_C C
ON B.PRODUCT =C.PRODUCT
WHERE B.VALUE >10000
AND C.RANGE NOT IN (4006, 4005, 4004, 4003, 4002, 4001)
);
So has anyone any idea of what I could be doing wrong?
The first one will delete matched rows across the joins, the second one will delete all rows when there EXISTS any one matched row as you are not correlating the deleted rows to the sub-query.
You can perform this correlation using the ROWID pseudo-column:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE table_a ( id, code ) AS
SELECT 1, 1 FROM DUAL UNION ALL
SELECT 2, 2 FROM DUAL UNION ALL
SELECT 3, 3 FROM DUAL;
CREATE TABLE table_b ( id, code, product, value ) AS
SELECT 1, 1, 1, 10001 FROM DUAL UNION ALL
SELECT 2, 2, 2, 10001 FROM DUAL UNION ALL
SELECT 3, 3, 1, 9999 FROM DUAL;
CREATE TABLE table_c ( id, product, range ) AS
SELECT 1, 1, 1001 FROM DUAL UNION ALL
SELECT 2, 2, 4001 FROM DUAL;
DELETE
FROM table_A
WHERE ROWID IN (
SELECT A.ROWID
FROM TABLE_A A
JOIN TABLE_B B
ON A.CODE = B.CODE
JOIN TABLE_C C
ON B.PRODUCT = C.PRODUCT
WHERE B.VALUE >10000
AND C.RANGE NOT IN (4006, 4005, 4004, 4003, 4002, 4001)
);
Query 1:
SELECT * FROM table_a
Results:
| ID | CODE |
|----|------|
| 2 | 2 |
| 3 | 3 |
Query 2:
SELECT * FROM table_b
Results:
| ID | CODE | PRODUCT | VALUE |
|----|------|---------|-------|
| 1 | 1 | 1 | 10001 |
| 2 | 2 | 2 | 10001 |
| 3 | 3 | 1 | 9999 |
Query 3:
SELECT * FROM table_c
Results:
| ID | PRODUCT | RANGE |
|----|---------|-------|
| 1 | 1 | 1001 |
| 2 | 2 | 4001 |

Merging Query Result into single row - Oracle

Can I do like this in oracle,.? I have some data like this:
No | Data |
===========
1 | A |
1 | B |
1 | C |
1 | D |
Is there any query that can produce a result like this,.?
No | Data |
=================
1 | A, B, C, D |
Many thanks :D
Maybe this page shows what you are looking for.
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE TEST ( ID, DATA ) AS
SELECT 1, 'A' FROM DUAL
UNION ALL SELECT 1, 'B' FROM DUAL
UNION ALL SELECT 1, 'C' FROM DUAL
UNION ALL SELECT 1, 'D' FROM DUAL
UNION ALL SELECT 2, 'E' FROM DUAL
UNION ALL SELECT 2, 'F' FROM DUAL;
Query 1:
SELECT ID,
LISTAGG( DATA, ',' ) WITHIN GROUP ( ORDER BY DATA ) AS AGGREGATED_DATA
FROM TEST
GROUP BY ID
Results:
| ID | AGGREGATED_DATA |
|----|-----------------|
| 1 | A,B,C,D |
| 2 | E,F |
In Oracle we can use wm_concat function. Here is the query for example above:
SELECT no, wm_concat(data) from table group by no
reference: wm_concat
select
no,
rtrim (xmlagg (xmlelement (d, data|| ',')).extract ('//text()'), ',') data
from
table_name
group by
no
;

Resources