Using Cases with update in oracle query - oracle

How to use CASE of WHEN and THEN in oracle UPDATE query, I have tried as following but no success,
when user status_id in 1,3,5 update ID to 3 (yes even if already 3 is there), and when status is 2 update to 4.
UPDATE USER
CASE WHEN USER_STATUS_ID IN ('1','3','5') THEN SET USER_STATUS_ID='3' WHEN USER_STATUS_ID IN ('2') THEN SET USER_STATUS_ID='4',
BLOCKING_REASON_CODE=(SELECT REASON_CODE_ID FROM REASON_CODES WHERE REASON_CODE='AGE_LIMIT')
WHERE CUSTOMER_ID IN
(SELECT CUSTOMER_ID FROM CUSTOMER_AGE_TABLE)
WHERE BATCH_ID=101

Assuming that your status is a varchar, even if it always seems to contain numbers, you may try:
update yourTable
set status_id = case
when status_id in ('1','3','5') then '3'
when status_id = '2' then '4'
else status_id -- no changes if status is not in 1, 2, 3, 4
end
where ...
If it is a number, you can remove the ''.
The else part is needed if you want to avoid null; for example:
select case when 1=2 then 'a' end from dual
gives null.
You may avoid it, by adding some more conditions in the WHERE clause, but this mainly depends on whether you always need to update that column, even if with no changes, or not.

Related

Oracle: How to check whether the given array exists in the table?

I want to check whether the array of values (4690, 4693) both is exists in the contextid column without using functions as the table contains more that a million records
Table structure:
ID
CONTEXTID
4
4690
5
4690
6
4693
7
4693
8
4690
What about this query?
select
case when count(distinct CONTEXTID) = 2 then 'Y' else 'N' end as contains_4690_4693
from tab
where CONTEXTID in (4690, 4693)
it retuns Y if both keys are in the table at least once, N otherwise.
If you just want to find out if they exist then
SELECT DISTINCT(CONTEXTID)
FROM SOME_TABLE
wHERE CONTEXTID IN (4690, 4693)
will do it. If CONTEXTID isn't indexed, though, the database will have to do a full table scan which will probably be slow.
Takeaway: add an index on CONTEXTID or live with the fact that it's going to be slow.
If the "test" values are known when you write the query (as they very rarely are - even though all the solutions presented so far make the implicit assumption that they are), you could do something like this - which is probably the most efficient way, regardless of whether there is an index on the relevant column or not:
select case when exists
( select *
from sys.odcinumberlist(4690, 4693)
where column_value not in ( select contextid
from the_table
where context_id is not null
)
) then 'Not all found' else 'All found' end as result
from dual
;
Note how I gave an array of input values to the query - I used the sys.odcinumberlist constructor. You will have to clarify how you plan to "input" an array of "test" values.

How to copy data along a dimension while avoiding unique name constraint issues in Oracle

Database is Oracle 18c
Data info:
We have the temporal notion of Seasons, and we have a set of Business Groups in each Season. My table, AGROUP, has columns(ID*[auto-generated], NAME, SEASON_ID). Group Names must be unique by Season. Said another way, I can have two groups with the same name ONLY if they are in different seasons.
Task At Hand:
I need to create a stored procedure that, given a source season and a target season, can copy all the groups from the source season to the target season. This part is easy enough. The naive solution is:
PROCEDURE COPY_GROUPS(IN_SOURCE_SEASON_ID IN SEASON.ID%TYPE,
IN_TARGET_SEASON_ID IN SEASON.ID%TYPE)
AS
WITH SOURCE AS
(SELECT AG.ID AS AG_ID,
AG.NAME,
AG.SEASON_ID
FROM AGROUP AG
WHERE AG.SEASON_ID = IN_SOURCE_SEASON_ID)
INSERT INTO AGROUP (NAME, SEASON_ID)
SELECT SRC.NAME,
IN_TARGET_SEASON_ID AS SEASON_ID,
FROM SOURCE SRC;
COMMIT;
END;
The tricky bit is that it is possible that there exists a group in the target season that already has the same name as a group in the source season. Without intervention, this would generate a unique constraint error. In this case, we want to copy the group but re-name it to something unique. Any method to rename is fine, but my first thought is simply to rename with a suffixed number. To do this, I suppose I would change things around to open a REFCURSOR and loop through, inserting one at a time and catching the unique constraint error and responding with a re-attempt to insert with a number suffixed. I'm really in-experienced with routing error handling like this in Oracle however. I could really use some pointing in the right direction.
Thanks for the help!
I played around with this a bit. I'd say to try to do the conditional logic in a case statement as per the following example. You'll need a better way to generate sequences though.
/* INSERT INTO AGROUP (id, NAME, SEASON_ID) */
WITH agroup as
(
select 1 id, 'A' name, 1 season_id from dual union
select 2 id, 'A' name, 2 season_id from dual union
select 3 id, 'B' name, 1 season_id from dual union
select 4 id, 'C' name, 2 season_ud from dual
)
, SOURCE AS
(SELECT rownum row_num, AG.ID AS AG_ID,
AG.NAME,
AG.SEASON_ID
FROM AGROUP AG
WHERE AG.SEASON_ID = 1 /* source season */
)
SELECT /* sequence generation */ (select max(id) from agroup) + row_num as id
, case when SRC.NAME in (select name
from agroup
where agroup.season_id = 2 /* target season */
and agroup.name = src.name
)
then src.name || ' ' || to_char(sysdate, 'DD-MON-YYYY hh24:MI:SS')
else src.name
end as name
, 2 /* target season */ AS SEASON_ID
FROM SOURCE SRC
;

duplicates with connect by in oracle

select RP.COUNTRYID,RP.PRDCODE,
RP.REPID,
RP.CHANNELID,
RP.CUSTOMERID,
RP.DIVISION,
RP.WWCOGS_GAUSS,
RP.WWCOGS_SAP,
RP.WWCOGS_BusLine,
RP.CURRID ,
ADD_MONTHS(to_date('01-01-'||rp.year,'DD-MM-YYYY'),level-1) KFDATE
from
(SELECT CP.COUNTRYID,
CP.PRDCODE,
CP.REPID,
CP.CHANNELID,
CP.CUSTOMERID,
IP.DIVISION,
CP.WWCOGS_GAUSS,
CP.WWCOGS_SAP,
CP.WWCOGS_BusLine,
CP.year,
CP.CURRID from
(select distinct IBC.COUNTRYID ,
decode(ls.dmdunit,null,mwa.ProductName,ls.dmdunit) PRDCODE,
ReportingUnit REPID,
'99' CHANNELID,
IBC.COUNTRYID CUSTOMERID ,
(
CASE
WHEN MWA.COGSSourceCF LIKE '%Gauss%'
THEN MWA.COGSPriceCF * MWA.ExchRate
ELSE NULL
END) WWCOGS_GAUSS,
(
CASE
WHEN MWA.COGSSourceCF LIKE '%SAP%'
THEN MWA.COGSPriceCF * MWA.ExchRate
ELSE NULL
END) WWCOGS_SAP,
(
CASE
WHEN MWA.COGSSourceCF NOT LIKE '%Gauss%'
AND MWA.COGSSourceCF NOT LIKE '%SAP%'
THEN MWA.COGSPriceCF * MWA.ExchRate
ELSE NULL
END) WWCOGS_BusLine,
mwa.year,
IRC.CURRID
from BAM.M_WWCOGS_AREA MWA,
MICSTAG.M_IBP_REPUNIT_CURRENCY IRC,
micstag.M_IBP_BDREPORTINGCOUNTRY IBC
,MICSTAG.M_LOCALPRODUCT_STAG LS
WHERE MWA.ReportingUnit=IRC.REPID
--and mwa.productname='FR21030390085'
--and MWA.GaussCountry='BE BELGIUM'
AND IBC.COUNTRYPLANNINGGROUP =MWA.GaussCountry
and IBC.businessdivision=31
and MWA.COGSPriceCF <>0
and ls.REPORTINGUNITID(+)=mwa.ReportingUnit
and ls.IBPLOCALPRDID(+)= mwa.ProductName ) CP, micstag.M_IBP_PRODUCT IP
where CP.PRDCODE=IP.PRDID) RP
CONNECT BY level <= 12 ;
the above query is getting unwanted duplicate, if i use distinct the query is running forever.
req. duplicate the records based on year in result set of rp
consider value of year is 2019 than 12 records should came from 1-jan-2019 to 1-dec-2019.
more than one value of year are possible
The CONNECT BY LEVEL <= 12 trick works nicely with dual because that table returns one row. Things are messier when they base result set returns more than one row, because the CONNECT BY generates a product. This is why you're getting duplicates.
What you need to do is specify some additional criteria for the connection. Ideally you will have a primary key in the projection - I'm going to assume it's REPID, so if it's something different you'll need to tweak this. Anyway, you'll need something like this:
) RP
CONNECT BY level <= 12
and rp.repid = prior rp.repid
and prior sys_guid() is not null
The prior sys_guid() bit prevents ORA-01436: CONNECT BY loop in user data.

Function returning Last record

I don't often use ORACLE PL/SQL by the way but i need to understand what if anything in this function created by someone else
in the company before me is wrong as for it is not returning the latest record i've been told. I found out in some other forum issues that they
suggested to use the max(dateColumn) instead of "row_numer = 1" for example but not quite sure how to and where to incorporate that.
-- Knowing that --
We use Oracle version 12,
CustomObjectTypeA is an custom Oracle OBJECT TYPE defined by some old employee not longer in here,
V_OtherView is of Table_Mnd type beeing defined by some old employee not longer in here,
V_ABC_123 is a view created by some old employee not longer in here as well.
CREATE OR REPLACE FUNCTION F_TABLE_APPROVED (NUMBER_F_UPD number, NUMBER_F_GET VARcHAR2)
RETURN Table_Mnd
IS
V_OtherView Table_Mnd
BEGIN
SELECT CustomObjectTypeA (FromT.NUMBER_F,
FromT.OP_CODE,
FromT.CATG_CODE,
FromT.CATG_NAME,
FromT.CATG_SORT,
FromT.ORG_CODE,
FromT.ORG_NAME
FromT.DATA_ENTRY_VALID,
FromT.NUMBER_RECEIVED,
FromT.YEAR_1,
FromT.YEAR_2)
BULK COLLECT INTO V_OtherView
FROM (SELECT NUMBER_F,
OP_CODE,
CATG_CODE,
CATG_NAME,
CATG_SORT,
ORG_CODE,
ORG_NAME
DATA_ENTRY_VALID,
NUMBER_RECEIVED,
YEAR_1,
YEAR_2,
ROW_NUMBER() OVER (PARTITION BY BY ORG_CODE ORDER BY NUMBER_RECEIVED DESC, LOAD_DATE DESC) AS ROW_NUMBER
FROM V_ABC_123
WHERE NUMBER_F = NUMBER_F_UPD AND DATA_ENTRY_VALID <> 'OnGoing'
AND LOAD_DATE >= (SELECT sysdate-10 FROM dual)
AND LOAD_DATE <= (SELECT DISTINCT LOAD_DATE
FROM V_ABC_123
WHERE NUMBER_RECEIVED = NUMBER_F_GET)) FromT
WHERE FromT.ROW_NUMBER=1;
RETURN V_OtherView;
END F_TABLE_APPROVED;
The important bits of the query are:
SELECT ...
FROM (select ...,
ROW_NUMBER()
OVER (PARTITION BY ORG_CODE
ORDER BY NUMBER_RECEIVED DESC,
LOAD_DATE DESC) AS ROW_NUMBER
...) FromT
WHERE FromT.ROW_NUMBER = 1;
The "ROW_NUMBER" column is computed according to the following window clause:
PARTITION BY ORG_CODE
ORDER BY NUMBER_RECEIVED DESC, LOAD_DATE DESC
Which means that for each ORG_CODE, it will sort all the records by NUMBER_RECEVED,LOAD_DATE in descending order. Note that if the columns are Oracle DATEs, they will only be accurate to the nearest second; so if there are multiple records with date/times in the exact same 1-second interval, this sort order will not be guaranteed unique. The logic of ROW_NUMBER will therefore pick one of them arbitrarily (i.e. whichever record happens to be emitted first) and assign it the value "1", and this will be deemed the "latest". Subsequent executions of the same SQL could (in theory) return a different record.
The suspicious part is NUMBER_RECEIVED which sounds like it's a number, not a date? Sorting by this means that the records with the highest NUMBER_RECEIVED will be preferred. Was this intentional?
I'm not sure why the PARTITION is there, this would cause the query to return one "latest" record for each value of ORG_CODE that it finds. I can only assume this was intentional.
The problem is that the query can only determine the "latest record" as well as it can based on the data provided to it. In this case, it's possible the data is simply not granular enough to be able to decide which record is the actual "latest" record.

ORA-00001: unique constraint (MYUSER.ADI_PK) violated

I have two tables, adv_institution and institution. institution has 5000+ rows, while adv_institution has 1400+
I want to use Oracle MERGE to back-fill records to adv_institution from institution. These two tables have about four fields tin common which I can use to back-fill.
Here is my entire MERGE statement
merge into
adv_institution to_t
using (
select
uni.*,
adv_c.country_cd as con_code_text
from
(
select
institution_cd,
name,
institution_status,
country_cd
from
institution uni
where
uni.institution_status = 'ACTIVE' and
uni.country_cd is not null
group by
institution_cd,
name,
institution_status,
country_cd
order by
name
) uni,
country_cd c_cd,
adv_country adv_c
where
uni.country_cd = c_cd.country_cd and
c_cd.description = adv_c.country_cd
) from_t
on
(
to_t.VENDOR_INSTITUTION_CD = from_t.INSTITUTION_CD or
to_t.INSTITUTION_CD = from_t.NAME
)
WHEN NOT MATCHED THEN INSERT (
to_t.INSTITUTION_CD,
to_t.INSTITUTION_NAME,
to_t.SHORT_NAME,
to_t.COUNTRY_CD,
to_t.NOTE,
to_t.UNIT_TERMINOLOGY,
to_t.COURSE_TERMINOLOGY,
to_t.CLOSED_IND,
to_t.UPDATE_WHO,
to_t.UPDATE_ON,
to_t.CALLISTA_INSTITUTION_CD
)
VALUES (
from_t.NAME,
from_t.NAME,
'',
from_t.con_code_text,
'',
'UNIT',
'COURSE',
'N',
'MYUSER',
SYSDATE,
from_t.institution_cd
);
The error I got is
Error report -
ORA-00001: unique constraint (MYUSER.ADI_PK) violated
ADI_PK means adv_institution.institution_cd is a primary key and it must be unique.
That is because in WHEN NOT MATCHED THEN INSERT there is an insert statement. I insert from_t.NAME into to_t.INSTITUTION_CD.
It looks like from_t.NAME has the same value at least twice, when inserting into to_t.INSTITUTION_CD
But I did a group statement to make sure from_t.NAME is unique:
(
select
institution_cd,
name,
institution_status,
country_cd
from
institution uni
where
uni.institution_status = 'ACTIVE' and
uni.country_cd is not null
group by
institution_cd,
name,
institution_status,
country_cd
order by
name
) uni
I am not sure I understand the issue correctly. I tried all I can, but still no luck.
I think your main issue is with group by.
Please consider below example:
desc temp_inventory;
Name Type
--------------------- -----------
WAREHOUSE_NO NUMBER(2)
ITEM_NO NUMBER(10)
ITEM_QUANTITY NUMBER(10)
WAREHOUSE_NO ITEM_NO ITEM_QUANTITY
1 1000 100
1 2000 200
1 2000 300
If i write a query where I want warehouse_no to be unique:
select warehouse_no,item_quantity
from temp_inventory
group by warehouse_no,item_quantity
Its going to return the same 3 rows.. instead i want to group by..
select warehouse_no,sum(item_quantity)
from temp_inventory
group by warehouse_no
which will make the warehouse_no unique in this situation !
Also in cases where you have VARCHAR2 columns, you can use MAX, MIN on them as aggregate functions along with group by to make a unique key in the query.
Example:
Select object_type, min(object_name)
from user_objects group by object_type;
which will make the object_type unique & return only 1 corresponding object name for it.
So note that if there are duplicate's, in the end some records will be eliminated based on the aggregate function.
"But I did a group statement to make sure from_t.NAME is unique:"
But your query does not do that. It produces a set of distinct combinations of (institution_cd,name,institution_status,country_cd). Clearly such a set could contain multiple recurrences of name, one for each different value of country_cd. As you have four elements in your key you are virtually guaranteeing that your set will have multiple occurrences of name.
You compound this with the or in the ON conditions, which means you trigger the UNMATCHED logic if to_t.VENDOR_INSTITUTION_CD = from_t.INSTITUTION_CD even though there is already a record in the target table where to_t.INSTITUTION_CD = from_t.NAME.
The problem is that the MERGE statement is atomic. The set of records coming from the USING subquery must contain unique keys. When Oracle finds a second occurrence of the same name in the result set it doesn't say, I've already merged one of those, let's skip it. It has to hurl ORA-00001 because there is no way for Oracle to know which record is apply, which combination of (institution_cd,name,institution_status,country_cd) is the correct one.
To solve this you need to change the USING query to produce a result set with unique keys. It's your data model, you understand its business rules, so you're in the position to rewrite it properly. But maybe something like this:
select
name,
max(institution_cd) as institution_cd,
institution_status,
max(country_cd) as country_cd
from (
institution uni
where
uni.institution_status = 'ACTIVE' and
uni.country_cd is not null
group by
name,
institution_status
order by
name
) uni
Then you can simplify the MERGE ON clause to:
on
(
to_t.INSTITUTION_CD = from_t.NAME
)
The use of MAX() in the subquery is an inelegant kludge. I hope you can apply better business rules.

Resources