Oracle Function .How to compare previous value with Current value - oracle

My Source Data
ename Age
BAL N
BAL Y
BAL Y
YUV N
YUV Y
NAR N
Logic
if ( (ename <> Previous_Ename or Previous_AGE='N' ) then AGE = as is
Else AGE='Y'
Could you please let me know how to code this using Oracle funcaiton? i tried but in all case it not showing the desired result set.
i used
create or replace function () RETURN
VARCHAR2
IS
previous_name VARCHAR2 (9) := 'DUMMY';
previous_age VARCHAR2 (9) := 'Z';
BEGIN
For cur_rec in (select ename, age from tablename order by ename) LOOP
if ( cur_rec.ename <> previous_ename or previous_age ='N')
then return cur_rec.age; /** it is populating the result set with only "N"***/
else return 'Y';
end if;
previous_ename :=ename; /*** not sure whether this assignment is correct- im trying to assignt current value as previous value for next row reference.****/
previous_age :=age; /*** not sure whether this assignment is correct****/
END LOOP;
END
REsult im getting:- actually the result should be same as Source for this data scenerio
ename Age
BAL N
BAL N
BAL N
YUV N
YUV N
NAR N

I still don't understand what you're trying to achieve here (at all)... or why
The problem is that you're trying to to this in a function but you're returning the something almost immediately. By your order logic (in the comments) the first value will always be N because that's the first value in the ORDER BY. For every record in your table this will be true.
Use a MERGE statement instead:
merge into tmp n
using ( select rowid as rid
, ename
, age
, lag(age) over ( partition by ename order by age ) as lag_age
from tmp
) o
on ( n.rowid = o.rid )
when matched then
update
set n.age = case when lag_age is null then age
when lag_age = 'N' then age
else 'Y'
end
;
SQL Fiddle

Related

Stored procedure is taking too much time to update the table columns

I have created a stored procedure which is taking too much of time to update the columns of the table. Say 3 hrs to update 2.5k records out of 43k records.
So can I reduce the time of updating the records. Below is my logic for the same.
procedure UPDATE_MST_INFO_BKC
(
P_SAPID IN NVARCHAR2
)
as
v_cityname varchar2(500):='';
v_neid varchar2(500):='';
v_latitude varchar2(500):='';
v_longitude varchar2(500):='';
v_structuretype varchar2(500):='';
v_jc_name varchar2(500):='';
v_jc_code varchar2(500):='';
v_company_code varchar2(500):='';
v_cnt number :=0;
begin
select count(*) into v_cnt from structure_enodeb_mapping where RJ_SAPID=P_SAPID and rownum=1;
if v_cnt > 0 then
begin
select RJ_CITY_NAME, RJ_NETWORK_ENTITY_ID,LATITUDE,LONGITUDE,RJ_STRUCTURE_TYPE,RJ_JC_NAME,RJ_JC_CODE,'6000'
into v_cityname,v_neid,v_latitude, v_longitude, v_structuretype,v_jc_name,v_jc_code,v_company_code from structure_enodeb_mapping where RJ_SAPID=P_SAPID and rownum=1;
update tbl_ipcolo_mast_info set
CITY_NAME = v_cityname,
NEID = v_neid,
FACILITY_LATITUDE = v_latitude,
FACILITY_LONGITUDE = v_longitude,
RJ_STRUCTURE_TYPE = v_structuretype,
RJ_JC_NAME = v_jc_name,
RJ_JC_CODE = v_jc_code,
COMPANY_CODE = v_company_code
where SAP_ID=P_SAPID;
end;
end if;
end UPDATE_MST_INFO_BKC;
What adjustments can I make to this?
As far as I understand your code, It is updating TBL_IPCOLO_MAST_INFO having SAP_ID = P_SAPID Means It is updating one record and you must be calling the procedure for each record.
It is a good practice of calling the procedure once and update all the record in one go. (In your case 2.5k records must be updated in one call of this procedure only)
For your requirement, Currently, I have updated the procedure code to only execute MERGE statement, which will be same as multiple SQLs in your question for single P_SAPID.
PROCEDURE UPDATE_MST_INFO_BKC (
P_SAPID IN NVARCHAR2
) AS
BEGIN
MERGE INTO TBL_IPCOLO_MAST_INFO I
USING (
SELECT
RJ_CITY_NAME,
RJ_NETWORK_ENTITY_ID,
LATITUDE,
LONGITUDE,
RJ_STRUCTURE_TYPE,
RJ_JC_NAME,
RJ_JC_CODE,
'6000' AS COMPANY_CODE,
RJ_SAPID
FROM
STRUCTURE_ENODEB_MAPPING
WHERE
RJ_SAPID = P_SAPID
AND ROWNUM = 1
)
O ON ( I.SAP_ID = O.RJ_SAPID )
WHEN MATCHED THEN
UPDATE SET I.CITY_NAME = O.RJ_CITY_NAME,
I.NEID = O.RJ_NETWORK_ENTITY_ID,
I.FACILITY_LATITUDE = O.LATITUDE,
I.FACILITY_LONGITUDE = O.LONGITUDE,
I.RJ_STRUCTURE_TYPE = O.RJ_STRUCTURE_TYPE,
I.RJ_JC_NAME = O.RJ_JC_NAME,
I.RJ_JC_CODE = O.RJ_JC_CODE,
I.COMPANY_CODE = O.COMPANY_CODE;
END UPDATE_MST_INFO_BKC;
Cheers!!
3 hours? That's way too much. Are sap_id columns indexed? Even if they aren't, data set of 43K rows is just too small.
How do you call that procedure? Is it part of another code, perhaps some unfortunate loop which does something row-by-row (which is, in turn, slow-by-slow)?
A few objections:
are all those variables' datatypes really varchar2(500)? Consider declaring them so that they'd take table column's datatype, e.g. v_cityname structure_enodeb_mapping.rj_city_name%type;. Also, there's no need to explicitly say that their value is null (:= ''), it is so by default
select statement which checks whether there's something in the table for that parameter's value should be rewritten to use EXISTS as it should perform better than rownum = 1 condition you used.
also, consider using exception handlers (no-data-found if there's no row for a certain ID; too-many-rows if there are two or more rows)
select statement that collects data into variables has the same condition; do you really expect more than a single row for each ID (passed as a parameter)?
Anyway, the whole procedure's code can be shortened to a single update statement:
update tbl_ipcolo_mst_info t set
(t.city_name, t.neid, ...) = (select s.rj_city_name,
s.rj_network_entity_id, ...
from structure_enodeb_mapping s
where s.rj_sapid = t.sap_id
)
where t.sap_id = p_sapid;
If there is something to be updated, it will be. If there's no matching t.sap_id, nothing will happen.

using regex_substr to get corresponding value from another column in oracle

I have the below query which works, which gives the nth string corresponding to the key I give in the where clause as names (separator being ##)
select names from (
select
regexp_substr('a##b##c##d##e##f','[^##]+', 1, level) as names,
rownum as nth
from dual
connect by regexp_substr('a##b##c##d##e##f', '[^##]+', 1, level) is not null
)
where nth in (
select nth from (
select
regexp_substr('150##200##13##8##51##61','[^##]+', 1, level) as names,
rownum as nth
from dual
connect by regexp_substr('150##200##13##8##51##61', '[^##]+', 1, level) is not null
)
where names = '200'
)
Now, I have a table temp with say 3 columns x,y and z where x has strings like a##b##c##d##e##f, y has 1##2##3##4##5##6 and z will have number like 1.
If I have a rows like
a##b##c##d##e##f 150##200##13##8##51##61 200
a##b##c##d##e##f 1##2##3##4##5##6 2
g##h##i##j##k##l 1##2##3##4##5##99 99
I want outputs like
a##b##c##d##e##f 150##200##13##8##51##61 200 b
a##b##c##d##e##f 1##2##3##4##5##6 2 b
g##h##i##j##k##l 1##2##3##4##5##99 99 l
simply plugging "temp" in place of dual in the above query takes long time as the db has over 50k rows. Any better solution or how do I fix this?
You may find a plain SQL solution, but I'd prefer to capsule the critical substring functionality in a function. After that the query is trivial
update
with tcols as (
select rownum colnum from dual connect by level <= 6 /* (max) number of columns */),
t2 as (
select x,y,z, colnum,
nth_substring(y,'#',colnum) subs
from regexp_tst, tcols
)
select
x,y,z, colnum,
nth_substring(x,'#',colnum) a
from t2
where subs = z
;
.
X Y Z A
---------------- ---------------- ---------- ----
a##b##c##d##e##f 1##2##3##4##5##6 1 a
a##b##c##d##e##f 1##2##3##4##5##6 2 b
g##h##i##j##k##l 1##2##3##4##5##6 3 i
The required function is as follows (You may want to adjust the trimming and the repeated delimieter logic)
create or replace function nth_substring( i_str VARCHAR2, i_del VARCHAR2, i_pos NUMBER)
/*
find n-th substring in delimited string
i_str input string e.g. a##b##c##d##e##f
i_del delimiter character
i_pos position of the strin starting from 1
*/
return VARCHAR2 is
BEGIN
return rtrim(ltrim(regexp_substr(i_str,'[^'||i_del||']+', 1, i_pos)));
END;
/

Merge statement without affecting records where there is no change in data

I have a stored procedure that takes data from several tables and creates a new table with just the columns I want. I now want to increase performance by only attempting to insert/update rows that have at least one column of new data. For existing rows that would only receive the exact data it already has, I want to skip the update altogether for that row.
For example if a row contains the data:
ID | date | population | gdp
15 | 01-JUN-10 | 1,530,000 | $67,000,000,000
and the merge statement comes for ID 15 and date 01-JUN-10 with population 1,530,000 and gdp $67,000,000,000 then I don't want to update that row.
Here are some snippets of my code:
create or replace PROCEDURE COUNTRY (
fromDate IN DATE,
toDate IN DATE,
filterDown IN INT,
chunkSize IN INT
) AS
--cursor
cursor cc is
select c.id, cd.population_total_count, cd.evaluation_date, cf.gdp_total_dollars
from countries c
join country_demographics cd on c.id = cd.country_id
join country_financials cf on cd.country_id = cf.country_id and cf.evaluation_date = cd.evaluation_date
where cd.evaluation_date > fromDate and cd.evaluation_date < toDate
order by c.id,cd.evaluation_date;
--table
type cc_table is table of cc%rowtype;
c_table cc_table;
BEGIN
open cc;
loop -- cc loop
fetch cc bulk collect into c_table limit chunkSize; --limit by chunkSize parameter
forall j in 1..c_table.count
merge
into F_AMB_COUNTRY_INFO_16830 tgt
using (
select c_table(j).id cid,
c_table(j).evaluation_date eval_date,
c_table(j).population_total_count pop,
c_table(j).gdp_total_dollars gdp
from dual
) src
on ( cid = tgt.country_id AND eval_date = tgt.evaluation_date )
when matched then
update
set tgt.population_total_count = pop,
tgt.gdp_total_dollars = gdp
when not matched then
insert (
tgt.country_id,
tgt.evaluation_date,
tgt.population_total_count,
tgt.gdp_total_dollars )
values (
cid,
eval_date,
pop,
gdp );
exit when c_table.count = 0; --quit condition for cc loop
end loop; --end cc loop
close cc;
EXCEPTION
when ACCESS_INTO_NULL then -- catch error when table does not exist
dbms_output.put_line('Error ' || SQLCODE || ': ' || SQLERRM);
END ;
I was thinking that in the on statement, I could just say something along the lines of:
on ( cid = tgt.country_id AND eval_date = tgt.evaluation_date
AND pop != tgt.population_total_count AND gdp != tgt.gdp_total_dollars )
but surely there's a cleaner / more efficient way to do it?
The otherway you could do it is use ora_hash to get a hash of the row. So your where clause could be something like.
where ora_hash(src.col1 || src.col2 || src.col3 || src.col4) = ora_hash(src.col1 || src.col2 || src.col3 || src.col4)

Sorting by value returned by a function in oracle

I have a function that returns a value and displays a similarity between tracks, i want the returned result to be ordered by this returned value, but i cannot figure out a way on how to do it, here is what i have already tried:
CREATE OR REPLACE PROCEDURE proc_list_similar_tracks(frstTrack IN tracks.track_id%TYPE)
AS
sim number;
res tracks%rowtype;
chosenTrack tracks%rowtype;
BEGIN
select * into chosenTrack from tracks where track_id = frstTrack;
dbms_output.put_line('similarity between');
FOR res IN (select * from tracks WHERE ROWNUM <= 10)LOOP
SELECT * INTO sim FROM ( SELECT func_similarity(frstTrack, res.track_id)from dual order by sim) order by sim; //that's where i am getting the value and where i am trying to order
dbms_output.put_line( chosenTrack.track_name || '(' ||frstTrack|| ') and ' || res.track_name || '(' ||res.track_id|| ') ---->' || sim);
END LOOP;
END proc_list_similar_tracks;
/
declare
begin
proc_list_similar_tracks(437830);
end;
/
no errors are given, the list is just presented unsorted, is it not possible to order by a value that was returned by a function? if so, how do i accomplish something like this? or am i just doing something horribly wrong?
Any help will be appreciated
In the interests of (over-)optimisation I would avoid ordering by a function if I could possibly avoid it; especially one that queries other tables. If you're querying a table you should be able to add that part to your current query, which enables you to use it normally.
However, let's look at your function:
There's no point using DBMS_OUTPUT for anything but debugging unless you're going to be there looking at exactly what is output every time the function is run; you could remove these lines.
The following is used only for a DBMS_OUTPUT and is therefore an unnecessary SELECT and can be removed:
select * into chosenTrack from tracks where track_id = frstTrack;
You're selecting a random 10 rows from the table TRACKS; why?
FOR res IN (select * from tracks WHERE ROWNUM <= 10)LOOP
Your ORDER BY, order by sim, is ordering by a non-existent column as the column SIM hasn't been declared within the scope of the SELECT
Your ORDER BY is asking for the least similar as the default sort order is ascending (this may be correct but it seems wrong?)
Your function is not a function, it's a procedure (one without an OUT parameter).
Your SELECT INTO is attempting to place multiple rows into a single-row variable.
Assuming your "function" is altered to provide the maximum similarity between the parameter and a random 10 TRACK_IDs it might look as follows:
create or replace function list_similar_tracks (
frstTrack in tracks.track_id%type
) return number is
sim number;
begin
select max(func_similarity(frstTrack, track_id)) into sim
from tracks
where rownum <= 10
;
return sim;
end list_similar_tracks;
/
However, the name of the function seems to preclude that this is what you're actually attempting to do.
From your comments, your question is actually:
I have the following code; how do I print the top 10 function results? The current results are returned unsorted.
declare
sim number;
begin
for res in ( select * from tracks ) loop
select * into sim
from ( select func_similarity(var1, var2)
from dual
order by sim
)
order by sim;
end loop;
end;
/
The problem with the above is firstly that you're ordering by the variable sim, which is NULL in the first instance but changes thereafter. However, the select from DUAL is only a single row, which means you're randomly ordering by a single row. This brings us back to my point at the top - use SQL where possible.
In this case you can simply SELECT from the table TRACKS and order by the function result. To do this you need to give the column created by your function result an alias (or order by the positional argument as already described in Emmanuel's answer).
For instance:
select func_similarity(var1, var2) as function_result
from dual
Putting this together the code becomes:
begin
for res in ( select *
from ( select func_similarity(variable, track_id) as f
from tracks
order by f desc
)
where rownum <= 10 ) loop
-- do something
end loop;
end;
/
You have a query using a function, let's say something like:
select t.field1, t.field2, ..., function1(t.field1), ...
from table1 t
where ...
Oracle supports order by clause with column indexes, i.e. if the field returned by the function is the nth one in the select (here, field1 is in position 1, field2 in position 2), you just have to add:
order by n
For instance:
select t.field1, function1(t.field1) c2
from table1 t
where ...
order by 2 /* 2 being the index of the column computed by the function */

Oracle and possible constant predicates in "WHERE" clause

I have a common problem with ORACLE in following example code:
create or replace procedure usp_test
(
p_customerId number,
p_eventTypeId number,
p_out OUT SYS_REFCURSOR
)
as
begin
open p_out for
select e.Id from eventstable e
where
(p_customerId is null or e.CustomerId = p_customerId)
and
(p_eventTypeId is null or e.EventTypeId = p_eventTypeId)
order by Id asc;
end usp_test;
The "OR" in "(p_customerId is null or e.CustomerId = p_customerId)" kills procedure performance, because optimizer will not use index (i hope for index seek) on "CustomerId" column optimally, resulting in scan instead of seek. Index on "CustomerId" has plenty of distinct values.
When working with MSSQL 2008 R2 (latest SP) or MSSQL 2012 i can hint the query with "option(recompile)" which will:
Recompile just this query
Resolve values for all variables (they are known after sproc is called)
Replace all resolved variables with constants and eliminate constant
predicate parts
For example: if i pass p_customerId = 1000, then "1000 is null" expression will always be false, so optimizer will ignore it.
This will add some CPU overhead, but it is used mostly for rarely called massive reports procedures, so no problems here.
Is there any way to do that in Oracle? Dynamic-SQL is not an option.
Adds
Same procedure just without "p_customerId is null" and "p_eventTypeId is null" runs for ~0.041 seconds, while the upper one runs for ~0.448 seconds (i have ~5.000.000 rows).
CREATE INDEX IX_YOURNAME1 ON eventstable (NVL(p_customerId, 'x'));
CREATE INDEX IX_YOURNAME2 ON eventstable (NVL(p_eventTypeId, 'x'));
create or replace procedure usp_test
(
p_customerId number,
p_eventTypeId number,
p_out OUT SYS_REFCURSOR
)
as
begin
open p_out for
select e.Id from eventstable e
where
(NVL(p_customerId, 'x') = e.CustomerId OR NVL(p_customerId, 'x') = 'x')
AND (NVL(p_eventTypeId, 'x') = e.EventTypeId OR NVL(p_eventTypeId, 'x') = 'x')
order by Id asc;
end usp_test;
One column index can't help as it's not stored in index definition.
Is creating index on (customer id, event id, id ) allowed? This way all needed columns are in index...

Resources