Problem in merge condition in oracle when merging two tables - oracle

I have a table which has data as:
id payor_name
---------------
1 AETNA
2 UMR
3 CIGNA
4 METLIFE
4 AETNAU
5 ktm
6 ktm
Id and payor_name are two columns.So,
My expected output is:
id payor_name
---------------
1 AETNA
2 UMR
3 CIGNA
4 METLIFE
4 AETNAU
6 ktm ...> I want to change the id of this row to be 6 from 5.
6 ktm
I want one to one mapping between id and payor_name.So,this is what I tried:
MERGE INTO offc.payor_collec A
USING (select id from offc.payor_collec where payor_name in(
select payor_name from offc.payor_collec group by payor_name having count(distinct id)>=2)) B
ON (A.id=B.id)
WHEN MATCHED THEN
UPDATE SET A.id=B.id
But when I compiled I got error as:
Error at line 1
ORA-38104: Columns referenced in the ON Clause cannot be updated: "A"."ID"
Id is number where as payor_name is varchar2.
How can I achieve this result?

MERGE works, but slightly different than your code.
SQL> select * from test;
ID PAYOR
---------- -----
1 aetna
2 umr
5 ktm
6 ktm
SQL> merge into test t
2 using (select max(t1.id) id,
3 t1.payor_name
4 from test t1
5 group by t1.payor_name
6 ) x
7 on (x.payor_name = t.payor_name)
8 when matched then update set
9 t.id = x.id;
4 rows merged.
SQL> select * from test;
ID PAYOR
---------- -----
1 aetna
2 umr
6 ktm
6 ktm
SQL>

Use a correlated subquery:
UPDATE PAYOR_COLLEC pc
SET pc.ID = (SELECT MAX(pc2.ID)
FROM PAYOR_COLLEC pc2
WHERE pc2.PAYOR_NAME = pc.PAYOR_NAME)
dbfiddle here

You can use a MERGE statement, as you tried and as Littlefoot has shown.
You can also use a correlated subquery as Bob Jarvis has shown, but that will be quite inefficient.
Many Oracle developers are unaware that you can also update through a join. Worse, there are many who say "there is no such thing in Oracle."
In your problem, you need to join your table to an aggregate query (picking just the max id for each payor_name) and the join is on the group by column in the aggregate. This already guarantees that the join column will be unique in the right-hand table; that is all Oracle needs to allow the update through join.
Here is a complete example, starting with the create table statement, then the update and then showing the table after the update. Note that I don't need any constraints (like primary key, not null, unique, etc.) or indexes on the base table. If they do exist, so much the better, but the solution works in the most general case.
create table t (id, payor_name) as
select 1, 'AETNA' from dual union all
select 2, 'UMR' from dual union all
select 3, 'CIGNA' from dual union all
select 4, 'METLIFE' from dual union all
select 4, 'AETNAU' from dual union all
select 5, 'ktm' from dual union all
select 6, 'ktm' from dual;
Table T created.
update
(
select id, payor_name, max_id
from t inner join
(select max(id) as max_id, payor_name from t group by payor_name)
using (payor_name)
)
set id = max_id where id != max_id
;
1 row updated.
select * from t;
ID PAYOR_NAME
----- ----------
1 AETNA
2 UMR
3 CIGNA
4 METLIFE
4 AETNAU
6 ktm
6 ktm
Notice the where clause at the end of the update statement, too. You don't want to update rows to their pre-existing value; that will still generate undo and redo data (although I understand that Oracle has changed that in more recent versions - it now doesn't generate undo and redo unless a row did indeed change). I assume ID is NOT NULL - otherwise you should rewrite the where clause as
where decode(id, max_id, 0) is null
or equivalent

Related

How to execute stored procedure cursor records parallelly

I have a stored procedure which does following tasks on 2 tables related to each other as below
CREATE TABLE address (adr_id, ver_id, address) AS
SELECT 1, 1, 'newYork' FROM DUAL UNION ALL
SELECT 1, 2, 'newYork' FROM DUAL UNION ALL
SELECT 1, 3, 'newYork' FROM DUAL UNION ALL
SELECT 4, 1, 'Washington' FROM DUAL UNION ALL
SELECT 4, 2, 'Washington' FROM DUAL;
CREATE TABLE employee (emp_id,adr_id,ver_id,) AS
SELECT 100,1, 1 FROM DUAL UNION ALL
SELECT 200,1, 2 FROM DUAL UNION ALL
SELECT 300,1, 3 FROM DUAL UNION ALL
SELECT 400,4, 1 FROM DUAL UNION ALL
SELECT 500,4, 2 FROM DUAL;
Here Following tasks are done in stored procedure and required to be executed in parallel as to increase throughput on billions of records in both table
Note: Foreign key Constraints are relaxed
Find records with distinct address "newYork" and have them in cursor
Loop on Cursor record as follows
and process each address record select * from address where address ="newYork"
Insert a new record in Address table with same address as first record (with ver_id=0) in #3 and take the newly inserted id
Find parent table records based on foreign key in Employee table and update them with newly id mentioned in #4.
delete all records of #3
Since Step 2 to 6 can be executed in parallel just wanted to know how this can be achieved ?
Expected After
Address
adr_id
ver_id
address
11
0
newYork
12
0
Washington
Employee
id
adr_id
ver_id
100
11
1
200
11
2
300
11
3
400
12
1
500
12
2
As a frame challenge, do not update the adr_id and just change the version; that way you do not need to change the referential constraints.
MERGE INTO address dst
USING (
SELECT ROWID AS rid,
COUNT(*) OVER (PARTITION BY adr_id) AS cnt,
ROW_NUMBER() OVER (PARTITION BY adr_id ORDER BY ver_id) AS rn
FROM address
) src
ON (dst.ROWID = src.rid AND src.cnt > 1)
WHEN MATCHED THEN
UPDATE
SET ver_id = 0
DELETE WHERE src.rn > 1
fiddle

How to insert values to newly added column

I am new to oracle thus this question.
There is a table already existed and I have added a new column to it.
There are 5 rows and I do not want to use update table with where clause to insert the values one by one in the new column. Is there a statement like INSERT ALL to insert the values into the new column in one shot ?
Thanks
You can also use something like below which in-effect I would say is multiple update only, wrapped in single statement.
SQL> select * from test_upd;
ID1 ID2
---------- ----------
1
2
3
4
SQL> update test_upd a set a.id2 =
2 (select
3 case
4 when id1=1 then 100
5 when id1=2 then 200
6 when id1=3 then 300
7 else 5000 end
8 from test_upd b
9 where a.id1=b.id1);
4 rows updated.
SQL> select * from test_upd;
ID1 ID2
---------- ----------
1 100
2 200
3 300
4 5000
Use a MERGE statement:
MERGE INTO your_table dst
USING (
SELECT 1 AS id, 'aaa' AS newvalue FROM DUAL UNION ALL
SELECT 2, 'bbb' FROM DUAL UNION ALL
SELECT 3, 'ccc' FROM DUAL UNION ALL
SELECT 4, 'ddd' FROM DUAL UNION ALL
SELECT 5, 'eee' FROM DUAL
) src
ON (dst.id = src.id)
WHEN MATCHED THEN
UPDATE SET value = src.newvalue;
Which, if you have the table:
CREATE TABLE your_table (id, value) AS
SELECT 1, CAST(NULL AS VARCHAR2(3)) FROM DUAL UNION ALL
SELECT 2, NULL FROM DUAL UNION ALL
SELECT 3, NULL FROM DUAL UNION ALL
SELECT 4, NULL FROM DUAL UNION ALL
SELECT 5, NULL FROM DUAL;
Then, after the MERGE, the table contains:
ID
VALUE
1
aaa
2
bbb
3
ccc
4
ddd
5
eee
db<>fiddle here

List all tables,numrows,numcol and primary key for table if any in oracle

I want to QUERY to generate a table like below:
Tablename||noofrows||noofcolumns||PRIMARKEYCOL(IF ANY for the table)
xyz. 590. 11. xyz_id
bcd. 934 15 null
...
...
So far I was able to do this until now in 2 query:
Query 1:
select a.table_name,count_rows(a.table_name) total_rows,count(b.column_name) total_cols from user_tables a,
,user_tab_columns b
where a.table_name =b.table_name
and a.table_name not like('amp%')
group by a.table_name;
note:Count_rows() is function to calculate rows as stats are not up to date
query 2:
select b.table_name b.column_name PRIMKEY_COL FROM user_constraints a,user_cons_columns b
where
a.constraint_type = 'P'
and a.constraint_name=b.constraint_name
and a.table_name=b.table_name
and b.table_name not like ('amp%');
Problem
I need to merge this table to one query (as shown in example above) so that I can represent the data in one table. My issue in clubbing the table is, with joins and how to make sure table without any primary keys are represent because if I just directly give constraint type ='p' in the where clause of the join I see that it only shows table with Primarykeys I am not able to figure this out.
Primary key can have more than just a single column, so - if you'd want to return them all, you'd either have to "aggregate" them, somehow (listagg looks like a natural choice, but - in 12c - you can't fetch distinct list of columns which you might need because of joining those tables duplicates appear).
As you already use a function to return number of rows (you'd probably rather regularly collect statistics, though, but yes - if tables are frequently modified (inserts and deletes), counting on-the-fly is the way to go.
So, alternative approach with 2 functions and 1 simple query. Yes, I know - context switching and stuff, but - see if it does any good.
This is what you already have (see if your and my code look similar):
SQL> create or replace function count_rows (par_table_name in varchar2)
2 return number
3 is
4 -- return number of rows in PAR_TABLE_NAME
5 retval number;
6 begin
7 execute immediate 'select count(*) from ' ||
8 dbms_assert.sql_object_name(par_table_name) into retval;
9 return retval;
10 end;
11 /
Function created.
This is new (to simplify fetching distinct list of primary key columns):
SQL> create or replace function pk_cols (par_table_name in varchar2)
2 return varchar2
3 is
4 -- return list of primary key columns for PAR_TABLE_NAME, sorted by column's
5 -- position within the primary key constraint
6 retval varchar2(100);
7 begin
8 select listagg(b.column_name, ', ') within group (order by b.position)
9 into retval
10 from user_constraints a join user_cons_columns b on b.constraint_name = a.constraint_name
11 where upper(a.table_name) = dbms_assert.sql_object_name(upper(par_table_name))
12 and a.constraint_type = 'P';
13 return retval;
14 end;
15 /
Function created.
Finally, that simple query I mentioned:
SQL> select a.table_name,
2 count_rows(a.table_name) total_rows,
3 max(a.column_id) total_cols,
4 pk_cols(a.table_name) primkey_cols
5 from user_tab_columns a
6 group by a.table_name;
TABLE_NAME TOTAL_ROWS TOTAL_COLS PRIMKEY_COLS
--------------- ---------- ---------- -------------------------
DEPT 4 3 DEPTNO
SO_TEST 0 1
TEST 7 3 ORD, TERMREGIONAL
EMP 14 8 EMPNO
TRAINING 0 5 TRAINING_PLACE_ID
TEMP_SE 10 3
<snip>
Just join the two queries:
select tab_cols.*, tab_keys.* from
(select a.table_name,count_rows(a.table_name) total_rows,count(b.column_name) total_cols from user_tables a,
,user_tab_columns b
where a.table_name =b.table_name
and a.table_name not like('amp%')
group by a.table_name) tab_cols,
(select b.table_name b.column_name PRIMKEY_COL FROM user_constraints a,user_cons_columns b
where
a.constraint_type = 'P'
and a.constraint_name=b.constraint_name
and a.table_name=b.table_name
and b.table_name not like ('amp%')) tab_keys
where tab_cols.table_name = tab_keys.table_name(+);

What is the best join condition for this two scenarios

I have two tables that i am trying to join to come up with the data that i need to report on.
First table contains all the interns that company hired.
Second table contain all the full the people that were interns but have been hired into full time position.
Requirement is to pull data that shows all the hired interns and out of those interns, get a count of all the ones that we hired into full time position.
Problem is when I do inner join I am only getting interns that were hired into full time position and not all the interns that were hired irrespective of whether they transitioned to full time or not.
Any suggestions?
A suggestion is to use outer join (instead of inner join).
Here's an example: sample tables:
SQL> create table t_first
2 (id number,
3 name varchar2(20));
Table created.
SQL> create table t_second
2 (id number,
3 name varchar2(20));
Table created.
SQL> insert into t_first (id, name)
2 select 1, 'Little' from dual union all
3 select 2, 'Foot' from dual union all
4 select 3, 'Scott' from dual union all
5 select 4, 'Tiger' from dual;
4 rows created.
SQL> insert into t_second (id, name)
2 select 3, 'Scott' from dual union all
3 select 4, 'Tiger' from dual;
2 rows created.
Select all of them, distinguish those who were hired to full time position:
SQL> select h.id, h.name,
2 decode(f.id, null, 'No', 'Yes') full_time_position
3 from t_first h left join t_second f on h.id = f.id
4 order by h.id;
ID NAME FUL
---------- -------------------- ---
1 Little No
2 Foot No
3 Scott Yes
4 Tiger Yes
SQL>
I'm not sure what you meant by "counting" those who were hired to full time position. Where would you want to display that information? How? If you could provide an example, it would be easier to suggest a solution.

Can I update a particular attribute of a tuple with the same attribute of another tuple of same table? If possible what should be the algorithm?

Suppose I have a table with 10 records/tuples. Now I want to update an attribute of 6th record with the same attribute of 1st record, 2nd-7th, 3rd-8th, 4th-9th, 5th-10th in a go i.e. without using cursor/loop. Use of any number of temporary table is allowed. What is the strategy to do so?
PostgreSQL (and probably other RDBMSes) let you use self-joins in UPDATE statements just as you can in SELECT statements:
UPDATE tbl
SET attr = t2.attr
FROM tbl t2
WHERE tbl.id = t2.id + 5
AND tbl.id >= 6
This would be easy with an update-with-join but Oracle doesn't do that and the closest substitute can be very tricky to get to work. Here is the easiest way. It involves a subquery to get the new value and a correlated subquery in the where clause. It looks complicated but the set subquery should be self-explanatory.
The where subquery really only has one purpose: it connects the two tables, much as the on clause would do if we could do a join. Except that the field used from the main table (the one being updated) must be a key field. As it turns out, with the self "join" being performed below, they are both the same field, but it is required.
Add to the where clause other restraining criteria, as shown.
update Tuples t1
set t1.Attr =(
select t2.Attr
from Tuples t2
where t2.Attr = t1.Attr - 5 )
where exists(
select t2.KeyVal
from Tuples t2
where t1.KeyVal = t2.KeyVal)
and t1.Attr > 5;
SqlFiddle is pulling a hissy fit right now so here the data used:
create table Tuples(
KeyVal int not null primary key,
Attr int
);
insert into Tuples
select 1, 1 from dual union all
select 2, 2 from dual union all
select 3, 3 from dual union all
select 4, 4 from dual union all
select 5, 5 from dual union all
select 6, 6 from dual union all
select 7, 7 from dual union all
select 8, 8 from dual union all
select 9, 9 from dual union all
select 10, 10 from dual;
The table starts out looking like this:
KEYVAL ATTR
------ ----
1 1
2 2
3 3
4 4
5 5
6 6
7 7
8 8
9 9
10 10
with this result:
KEYVAL ATTR
------ ----
1 1
2 2
3 3
4 4
5 5
6 1
7 2
8 3
9 4
10 5

Resources