Oracle UPDATE Question - oracle

Can the update described below be completed in one UPDATE statement?
I want to update the Operators.name_id values with the Users.name_id values by joining on Users.name = Operators.op_name. Both Users.name and Operators.op_name are have a unique.
I know the situation described below doesn't follow "best practices", but it's a much simpler example of what I'm trying to do: namely updating a field with the value from another joined table.
Table: Users
user_id name
----------------
34 Billy
43 Jimmy
50 Joe
Table: Operators (before UPDATE)
op_id op_name user_id
-------------------------
12 Billy 35
35 Jimmy 46
33 Joe 99
Table: Operators (after UPDATE)
op_id op_name name_id
-------------------------
12 Billy 34
35 Jimmy 43
33 Joe 50

UPDATE operators o
SET user_id =
(
SELECT u.user_id
FROM users u
WHERE o.op_name = u.name
)
WHERE o.op_name IN
(
SELECT name
FROM users
)
In Oracle 10g, more efficient:
MERGE
INTO operators o
USING users u
ON (u.name = o.op_name)
WHEN MATCHED THEN
UPDATE
SET user_id = u.user_id

update (
select oo.user_id, uu.user_id uu_id
from operators oo
join
users uu on uu.name = oo.op_name
)
set user_id = uu_id
Here's the full test script (I'm using my_users and my_operators so as not to mess with your data).
drop table my_users;
create table my_users (
user_id number(2),
name varchar2(30) unique
)
;
insert into my_users (user_id, name)
select 34, 'Billy' from dual union all
select 43, 'Jimmy' from dual union all
select 50, 'Joe' from dual
;
drop table my_operators;
create table my_operators (
op_id number(2),
op_name varchar2(30) unique,
user_id number(2)
)
;
insert into my_operators (op_id, op_name, user_id)
select 12, 'Billy', 35 from dual union all
select 35, 'Jimmy', 46 from dual union all
select 33, 'Joe', 99 from dual
;
update (
select oo.user_id, uu.user_id uu_id
from my_operators oo
join
my_users uu on uu.name = oo.op_name
)
set user_id = uu_id
;
select * from my_operators;

Related

Insert record from one table to another table - Oracle

I have a table TABLE1 which has 5 columns (ROLL_NO, NAME, UNITS, CODE, AMOUNT);
CREATE TABLE TABLE1 (ROLL_NO VARCHAR2(3), NAME VARCHAR2(4), UNITS NUMBER, AMOUNT NUMBER, CODE VARCHAR2(3));
------------------------------------------------------------------------------------------
INSERT INTO TABLE1 VALUES ('101', 'JOHN', 1, 6, 'ABC');
INSERT INTO TABLE1 VALUES ('101', 'JOHN', 2, 6, 'ABC');
INSERT INTO TABLE1 VALUES ('102', 'TOMS', 1, 7, 'ABC');
INSERT INTO TABLE1 VALUES ('102', 'TOMS', 6, 7, 'ABC');
INSERT INTO TABLE1 VALUES ('103', 'FINN', 1, 1, 'BCD');
ROLL_NO NAME UNITS AMOUNT CODE
-------------------------------------------------------
101 JOHN 1 6 ABC
101 JOHN 2 6 ABC
-------------------------------------------
102 TOMS 1 7 ABC
102 TOMS 6 7 ABC
103 FINN 1 1 BCD
There is second table TABLE2 where we need to insert data from TABLE1
CREATE TABLE TABLE2 (ROLL_NO VARCHAR2(3), NAME VARCHAR2(4), RESULT VARCHAR2(3));
There are three conditions to insert data into TABLE2
1st case : If CODE is 'ABC' and SUM(UNITS) of particular ROLL_NO is equal to AMOUNT then don't insert data into TABLE2
2nd case : If CODE is 'ABC' and SUM(UNITS) of particular ROLL_NO is not equal to AMOUNT then insert data with RESULT column value as 'YES'
3rd case : If CODE is not 'ABC' then RESULT column will be 'YES'.
Note: NAME, CODE and AMOUNT will be same for particular ROLL_NO though ROLL_NO has multiple UNITS.
Example :
ROLL_NO 102 CODE 'ABC' and two lines with SUM(UNITS) as 7 and its equal to AMOUNT i.e. 7 and (1st case)
ROLL_NO 101 has CODE 'ABC' and two lines with SUM(UNITS) as 3 and its not equal to AMOUNT i.e. 6 (2nd case)
ROLL_NO 103 has CODE 'BCD' which is not equal to 'ABC'(3rd case)
At the end TABLE2 should have
ROLL_NO NAME RESULT
-----------------------------
101 JOHN YES
103 FINN YES
I have tried this oracle query but it is inserting data related to 102 ROLL_NO which I don't need
SELECT T1.ROLL_NO, T1.NAME,
CASE
WHEN T1.CODE <> 'ABC' THEN 'YES'
WHEN T1.CODE = 'ABC' AND T2.TOT_UNITS <> T1.AMOUNT THEN 'YES'
END RESULT
FROM (SELECT DISTINCT ROLL_NO, NAME, AMOUNT, CODE
FROM TABLE1 ) T1
JOIN (SELECT ROLL_NO, SUM(UNITS) AS TOT_UNITS
FROM TABLE1
GROUP BY ROLL_NO) T2 ON T1.ROLL_NO = T2.ROLL_NO
I am not able to figure out how to not insert ROLL_NO 102 record into TABLE2..Can anyone provide better query than this if possible? Thank you
A "better" option is to scan table1 only once.
SQL> insert into table2 (roll_no, name, result)
2 with temp as
3 (select roll_no, name, sum(units) sum_units, amount, code,
4 case when code = 'ABC' and sum(units) = amount then 'NO'
5 when code = 'ABC' and sum(units) <> amount then 'YES'
6 else 'YES'
7 end as result
8 from table1
9 group by roll_no, name, amount, code
10 )
11 select roll_no, name, result
12 from temp
13 where result = 'YES';
2 rows created.
SQL> select * from table2;
ROL NAME RES
--- ---- ---
101 JOHN YES
103 FINN YES
SQL>

Oracle join returning too many rows

I have the following select statments in Oracle 19:
select count(*) as facility_load_stg_cnt from facility_load_stg;
select count(*) as facility_cnt from facility;
select count(*) as loctn_typ_cnt from loctn_typ;
select count(*) as join_cnt from (
select f.facility_id, lt.loctn_typ_id
from facility_load_stg stg
inner join facility f on stg.facility_cd = f.facility_cd
inner join loctn_typ lt on stg.bldg = lt.loctn_typ_nm);
the object is simple, get the PK's for facility and loctn_typ (facility_id, loctn_typ_id) to insert into the table that will build the relationship.
The problem is when I run the above code, I get these results:
FACILITY_LOAD_STG_CNT
---------------------
987
FACILITY_CNT
------------
645
LOCTN_TYP_CNT
-------------
188
JOIN_CNT
----------
2905
why is the select with the join have 3x the rows of the facility_load_stg table? I am sure I am doing something silly, I just cannot see it.
Folks have asked to see the table definitions, here is the relavent parts:
create table FACILITY_MAINT_DATA.FACILITY_LOAD_STG
(
FACILITY_CD VARCHAR2(100) not null,
BLDG VARCHAR2(100)
)
create table FACILITY_MAINT_DATA.FACILITY
(
FACILITY_CD VARCHAR2(100),
FACILITY_ID NUMBER(14) not null
constraint PK_FACILITY
primary key
)
create table FACILITY_MAINT_DATA.LOCTN_TYP
(
LOCTN_TYP_ID NUMBER(14) default "FACILITY_MAINT_DATA"."LOCTN_TYP_ID_SEQ"."NEXTVAL" not null
constraint PK_LOCTN_TYP
primary key,
LOCTN_TYP_NM VARCHAR2(100),
)
The generall rule if you join and you encounter a higher count that you expect is that the join keys are not unique*
Here a simple example reproducting the same result as you have (limiting only to two tables).
create table tab1 as
select rownum id,
case when rownum <= 80 then 'CD_'||rownum else 'CD_X' end cd from dual connect by level <= 645;
create table tab2 as
select rownum id,
case when rownum <= 80 then 'CD_'||rownum
when rownum <= 85 then 'CD_X'
else 'CD_Y' end cd from dual connect by level <= 987;
select count(*) from tab1;
COUNT(*)
----------
645
select count(*) from tab2;
COUNT(*)
----------
987
select count(*)
from tab1
join tab2
on tab1.cd = tab2.cd;
COUNT(*)
----------
2905
Summary change the join to use the unique keys or limit the join only to such CDcolumns that are unique.
Thank you everyone for the comments, that got me looking at detail at the actual data and there are duplications. Now I just have to figure out why, just need some investigation:)

Display the Alternative columns from table row

enter image description here
Display the Alternative columns from table row
Convert the salary column to a string data type (so that salary and name have the same data type) and then UNPIVOT:
Oracle Setup:
CREATE TABLE test_data ( id, name, salary ) AS
SELECT 100, 'A', 1000 FROM DUAL UNION ALL
SELECT 101, 'B', 2000 FROM DUAL UNION ALL
SELECT 102, 'C', 3000 FROM DUAL
Query:
SELECT id, value
FROM (
SELECT id, name, TO_CHAR( salary ) AS salary
FROM test_data
)
UNPIVOT ( value FOR key IN ( name, salary ) )
Output:
ID | VALUE
--: | :----
100 | A
100 | 1000
101 | B
101 | 2000
102 | C
102 | 3000
db<>fiddle here
Alternatively, UNION ALL can also work for you, If you agreed to change the datatype of Salary column -
SELECT ID, Name "Name & Salary"
FROM TABLE1
UNION ALL
SELECT ID, Salary
FROM TABLE1
ORDER BY ID, Name

Oracle Shuffle columns in rows

Based in the solution if this issue - Shuffle a column between rows - I need to apply to this, a new condition: the names must guarantee the gender: male, female or unknown.
So, my table have a column named gender. I need to shuffle the columns whit the same gender.
I've tried this, but in some cases I cannot guarantee the gender
merge into original_table o
using (
with
helper ( id, gender, rn, rand_rn ) as (
select id,
gender,
row_number() over (order by id),
row_number() over (order by dbms_random.value())
from original_table
)
select ot.name, ot.gender, h2.id
from original_table ot inner join helper h1 on (ot.id = h1.id and ot.gender = h1.gender)
inner join helper h2 on h1.rand_rn = h2.rn
) p
on (o.id = p.id)
when matched then update set o.name = p.name
;
And in cases that gender is not available (is null value), for example company's name, this merge not update anything.
I can split this query in 3 different merge statement. One for each gender that I have. But I'm looking for a better and simply statement. Because I need to apply the same solution in a different context and with different conditions.
Thanks.
EDIT: Sample data
ID GENDER NAME
3721 M MARK
3722 M JUSTIN
3723 F RUTH
3724 F MARY
3725 F ANNE
4639 CAMPANY SA
4640 M JOHN
4641 M LUCAS
4642 COMPANY HOLDER SA
One possible solution:
ID GENDER NAME
3721 M LUCAS
3722 M JOHN
3723 F MARY
3724 F ANNE
3725 F RUTH
4639 CAMPANY HOLDER SA
4640 M MARK
4641 M JUSTIN
4642 COMPANY SA
Include gender in the PARTITION BY clause in the analytic functions and in the JOIN clause. If you don't have a primary key to match on then you could use the ROWID pseudo-column.
Oracle Setup:
CREATE TABLE original_table ( id, gender, name, company ) AS
SELECT 1, 'F', 'Alice', CAST( NULL AS VARCHAR2(20) ) FROM DUAL UNION ALL
SELECT 2, 'M', 'Bobby', 'ACME' FROM DUAL UNION ALL
SELECT 3, 'F', 'Carol', 'XYZ' FROM DUAL UNION ALL
SELECT 4, 'M', 'David', NULL FROM DUAL UNION ALL
SELECT 5, 'M', 'Errol', 'ACME' FROM DUAL UNION ALL
SELECT 6, 'F', 'Fiona', 'XYZ' FROM DUAL;
Update:
MERGE INTO original_table dst
USING (
WITH rnd ( rid, rn, rnd_rn, gender, name ) AS (
SELECT ROWID,
ROW_NUMBER() OVER ( PARTITION BY gender ORDER BY id ),
ROW_NUMBER() OVER ( PARTITION BY gender ORDER BY DBMS_RANDOM.VALUE ),
gender,
name
FROM original_table
)
SELECT o.rid, r.name
FROM rnd o INNER JOIN rnd r
ON ( ( o.gender = r.gender OR ( o.gender IS NULL AND r.gender IS NULL ) )
AND o.rn = r.rnd_rn )
)
ON ( dst.ROWID = src.ROWID )
WHEN MATCHED THEN
UPDATE SET name = src.name;
Output:
SELECT * FROM original_table;
ID G NAME COMPANY
-- - ----- -------
1 F Fiona
2 M David ACME
3 F Alice XYZ
4 M Bobby
5 M Errol ACME
6 F Carol XYZ

Table Variable Style Entities in Oracle

I've been looking around for awhile for something in Oracle that acts like a table variable in SQL Server. I've found people asking questions like this here on SO and people always say "Yes, Oracle has that" but the examples show that the entities are not like SQL Server at all. Can someone show me how to perform the below simple TSQL solution in Oracle?
declare #users table (
ID int,
Name varchar(50),
Age int,
Gender char(1)
)
;with users as (
select 1001 as ID, 'Bob' as Name, 25 as Age, 'M' as Gender
union
select 1021 as ID, 'Sam' as Name, 29 as Age, 'F'
)
insert into #users (ID, Name, Age, Gender)
select * from users
declare #grades table (
UserID int,
ClassID int,
Grade int
)
;with grades as (
select 1001 as UserID , 120 as ClassID, 4 as Grade
Union
select 1001 as UserID , 220 as ClassID, 2 as Grade
Union
select 1021 as UserID , 130 as ClassID, 4 as Grade
Union
select 1021 as UserID , 230 as ClassID, 4 as Grade
Union
select 1021 as UserID , 340 as ClassID, 2 as Grade
)
insert into #grades
select * from grades
select u.ID, u.Name, GPA = AVG(cast(g.grade as decimal))
from #users u
inner join #grades g on u.ID=g.UserID
group by u.ID, u.Name
Some answers may tell you that Oracle has table variables, and it does to a certain extent. However, most answers will tell you that you should not be doing this in Oracle at all; there's simply no need.
In your case I would simply use a CTE:
with users as (
select 1001 as ID, 'Bob' as Name, 25 as Age, 'M' as Gender from dual
union
select 1021 as ID, 'Sam' as Name, 29 as Age, 'F' from dual
)
, grades as (
select 1001 as UserID , 120 as ClassID, 4 as Grade from dual
Union
select 1001 as UserID , 220 as ClassID, 2 as Grade from dual
Union
select 1021 as UserID , 130 as ClassID, 4 as Grade from dual
Union
select 1021 as UserID , 230 as ClassID, 4 as Grade from dual
Union
select 1021 as UserID , 340 as ClassID, 2 as Grade from dual
)
select u.ID, u.Name, AVG(g.grade) as gpa
from users u
join grades g on u.ID = g.UserID
group by u.ID, u.Name
UPDATE: The answer I've been trying to get for a long time is in Ben's comment below which I include here:
"There is no variable, which you can create on the fly and join to other tables in standard SQL #wcm, yes. There is a number of different type of objects that can be created that will allow you to do this, but not exactly as you would in T-SQL".
If I understand correctly, then if I needed temporary storage of data that is confined to being visible to my session, then I'd be using a global temporary table. Probably more overhead than storing in memory, but plenty of advantages too -- gathering statistics on them, indexing them, and the ability to store data without regard to memory consumption.

Resources