substr on pl/sql - oracle

I am trying to write a small pl/sql script and need some help.
first, I have 2 tables called project1 , project2. both tables have a column called cust_code.
cust_code values are varchar2 type. values always begin with 1. (number 1, decimal point) and 8 digits. for example 1.10002332
when I import data into project1 table, if the last digit is 0, for example 1.22321630, the last zero is dropped and then theres only seven digits beyond the decimal point. in that case it will be 1.2232163
the script I want to write will check whether there are only 7 digits beyond the decimal point and will insert that record into the project2 table.
this is what I came up with
DECLARE
CURSOR dif IS
SELECT CUST_CODE, CUST_ID, CONTRACT_NUM, MSISDN
FROM project1
WHERE CUST_CODE IN (SELECT CUST_CODE FROM CUST_ALL);
BEGIN
FOR a in dif LOOP
IF SUBSTR(a.CUST_CODE, 10)=null
THEN
INSERT INTO project2 (cust_code)
VALUES(a.CUST_CODE);
END IF;
END LOOP;
commit;
END;
the script runs with no errors but nothing happens. on the substr function, when I chose different value than NULL, then it works. I cant figure out how to check if the 8th digit is missing.
Assaf.

Your script doesn't work because of the line:
IF SUBSTR(a.CUST_CODE, 10)=null
In plsql <something> = null will always be FALSE.
You should write:
IF SUBSTR(a.CUST_CODE, 10) IS null
But actually you don't really nead plsql, you can do it with one sql command:
INSERT INTO project2 (cust_code)
SELECT CUST_CODE, CUST_ID, CONTRACT_NUM, MSISDN
FROM project1
WHERE CUST_CODE IN (SELECT CUST_CODE FROM CUST_ALL)
AND SUBSTR(a.CUST_CODE, 10) IS null;

Try this for your condition:
IF LENGTH(a.CUST_CODE) = 10 AND SUBSTR(a.CUST_CODE,-1,10) = '0'
(check if length is 10 and also last character is 0)

Related

Using EXECUTE IMMEDIATE based on entries of table

I (using Oracle 12c, PL/SQL) need to update an existing table TABLE1 based on information stored in a table MAP. In a simplified version, MAP looks like this:
COLUMN_NAME
MODIFY
COLUMN1
N
COLUMN2
Y
COLUMN3
N
...
...
COLUMNn
Y
COLUMN1 to COLUMNn are column names in TABLE1 (but there are more columns, not just these). Now I need to update a column in TABLE1 if MODIFY in table MAP contains a 'Y' for that columns' name. There are other row conditions, so what I would need would be UPDATE statements of the form
UPDATE TABLE1
SET COLUMNi = value_i
WHERE OTHER_COLUMN = 'xyz_i';
where COLUMNi runs through all the columns of TABLE1 which are marked with MODIFY = 'Y' in MAP. value_i and xyz_i also depend on information stored in MAP (not displayed in the example).
The table MAP is not static but changes, so I do not know in advance which columns to update. What I did so far is to generate the UPDATE-statements I need in a query from MAP, i.e.
SELECT <Text of UPDATE-STATEMENT using row information from MAP> AS SQL_STMT
FROM MAP
WHERE MODIFY = 'Y';
Now I would like to execute these statements (possibly hundreds of rows). Of course I could just copy the contents of the query into code and execute, but is there a way to do this automatically, e.g. using EXECUTE IMMEDIATE? It could be something like
BEGIN
EXECUTE IMMEDIATE SQL_STMT USING 'xyz_i';
END;
only that SQL_STMT should run through all the rows of the previous query (and 'xyz_i' varies with the row as well). Any hints how to achieve this or how one should approach the task in general?
EDIT: As response to the comments, a bit more background how this problem emerges. I receive an empty n x m Matrix (empty except row and column names, think of them as first row and first column) quarterly and need to populate the empty fields from another process.
The structure of the initial matrix changes, i.e. there may be new/deleted columns/rows and existing columns/rows may change their position in the matrix. What I need to do is to take the old version of the matrix, where I already have filled the empty spaces, and translate this into the new version. Then, the populating process merely looks if entries have changed and if so, alters them.
The situation from the question arises after I have translated the old version into the new one, before doing the delta. The new matrix, populated with the old information, is TABLE1. The delta process, over which I have no control, gives me column names and information to be entered into the cells of the matrix (this is table MAP). So I need to find the column in the matrix labeled by the delta process and then to change values in rows (which ones is specified via other information provided by the delta process)
Dynamic SQL it is; here's an example, see if it helps.
This is a table whose contents should be modified:
SQL> select * from test order by id;
ID NAME SALARY
---------- ---------- ----------
1 Little 100
2 200
3 Foot 0
4 0
This is the map table:
SQL> select * from map;
COLUMN CB_MODIFY VALUE WHERE_CLAUSE
------ ---------- ----- -------------
NAME Y Scott where id <= 3
SALARY N 1000 where 1 = 1
Procedure loops through all columns that are set to be modified, composes the dynamic update statement and executes it:
SQL> declare
2 l_str varchar2(1000);
3 begin
4 for cur_r in (select m.column_name, m.value, m.where_clause
5 from map m
6 where m.cb_modify = 'Y'
7 )
8 loop
9 l_str := 'update test set ' ||
10 cur_r.column_name || ' = ' || chr(39) || cur_r.value || chr(39) || ' ' ||
11 cur_r.where_clause;
12 execute immediate l_str;
13 end loop;
14 end;
15 /
PL/SQL procedure successfully completed.
Result:
SQL> select * from test order by id;
ID NAME SALARY
---------- ---------- ----------
1 Scott 100
2 Scott 200
3 Scott 0
4 0
SQL>

how can i limit count of duplicate rows in oracle?

create table file( member_no number, filepath varchar2(100) );
I want to limit the number of duplicate rows of member_no in this table.
for example:
In this way, the number of member_no can be up to 5, but it shouldn't be more than six.
how can I do this?
So you have two ways I can think of:
when you are inserting (i assume you are using a store procedure) run an if to check the current rows
Declare
count number;
too_many_num_exception EXCEPTION;
BEGIN
select count(file_path) into count from file where member_no = <num_you_are_inserting>;
if(count = 5)
Then
raise too_many_num_exception;
end if;
insert(...);
EXCEPTION
WHEN too_many_num_exception then
--do something
end;
or you could try play around with creating indexes on you tables (however this may not work - it's just a thought)
CREATE UNIQUE INDEX file_ix1 on file (
CASE WHEN (select count() from file ... ) < 6 THEN member_id ELSE NULL END)
online
Although im not 100% if that would work

OUT parameter with multiples values

create or replace PROCEDURE Show_R(A IN VARCHAR2, B OUT VARCHAR2)
IS
BEGIN
select func_w(day),TO_CHAR(hour, 'HH24:MI')INTO B
from task t
inner join mat m
on t.id_p = m.id_a
where m.cod_mod = A;
END;
I have a issue with this code, this select gets two types of columns data that are not the same type of data, i don't know how to add into B two types of data in only one "out parameter"
You can't put 2 values into 1 OUT parameter. So, use 2 OUT parameters.
Firstly don't store day and hour in separate columns. Just use a single DATE column as, in Oracle, the DATE data type has year, month, day, hour, minute and second components and so can store both the date and time.
Secondly, don't use A, B, show_R or func_w identifiers; use meaningful names as it will be far easier to debug your code in 6-months if you can tell what it is intended to do.
Third, your SELECT ... INTO statement will fail as you have two columns but only one variable to select into; you need 2 variables in INTO clause and this means (unless you are going to concatenate the two values) that you need 2 OUT parameters.
CREATE PROCEDURE Show_w_day_and_hour(
i_cod_mod IN mat.cod_mod%TYPE,
o_w_day OUT VARCHAR2,
o_hour OUT VARCHAR2
)
IS
BEGIN
SELECT func_w(day),
TO_CHAR(hour, 'HH24:MI')
INTO o_w_day,
o_hour
FROM task t
INNER JOIN mat m
ON ( t.id_p = m.id_a )
WHERE m.cod_mod = i_cod_mod;
END;
/
db<>fiddle

Oracle password generator for EBS database users

I have task where I need to change passwords for current EBS 189 schemas (Oracle 12.1). I have created query how to manage this in one go:
select 'FNDCPASS APPS/atesta 0 Y SYSTEM/test ORACLE '||oracle_username||' '||password||'' oracle_username from FND_ORACLE_USERID
where READ_ONLY_FLAG='A'
So my question is how to replace password word in my query with random generated password?
Password needs to have at least 15 characters, 1 special character, one number and one Upper letter.
Any ideas?
Thanks
You can create a function to return such random passwords and use it in your queries:
-- function:
with
function get_rand_pass(n in number) return varchar2 as
res varchar2(15);
begin
loop
res:=DBMS_RANDOM.STRING('p',15);
exit when
regexp_like(res,'[^a-zA-Z0-9]') -- special char
and regexp_like(res,'\d') -- at least one number
and regexp_like(res,'[A-Z]') -- at least one UPPER letter
;
end loop;
return res;
end;
-- end of function
-- test query:
select
get_rand_pass(dbms_random.value()) pass
from dual;
Full example:
with
function get_rand_pass(n in number) return varchar2 as
res varchar2(15);
begin
loop
res:=DBMS_RANDOM.STRING('p',15);
exit when
regexp_like(res,'[^a-zA-Z0-9]') -- special char
and regexp_like(res,'\d') -- at least one number
and regexp_like(res,'[A-Z]') -- at least one UPPER letter
;
end loop;
return res;
end;
select
get_rand_pass(dbms_random.value()) pass
from dual
connect by level<=10
/
PASS
---------------
C7Bncs'bH9+Hx&p
kHG<&4Aw36VS3W
np xvyvM]3In #{
%A1&+}XVNyBvL'%
+,Bp hII\q,&7>V
h#l` kE(b4=-C</
(c{7"`~UvN44#e/
<_s+4G!nlujcytJ
{[a2{*sXW;;$#<.
7)^>Qj4!1MTm?};
10 rows selected.
or use the following subquery in your queries:
(
select pass
from
(select DBMS_RANDOM.STRING('p',15) pass from dual connect by level<=1e5)
where
rownum=1 -- first pass
and regexp_like(pass,'[^a-zA-Z0-9]') -- special char
and regexp_like(pass,'\d') -- at least one number
and regexp_like(pass,'[A-Z]') -- at least one UPPER letter
) pass
But it may be cached (scalar subquery caching) and if you use cross join with such generator, it may be executed just once in hash join, so it needs a workaround, for example:
select--+ use_nl(pass_gen)
*
from FND_ORACLE_USERID,
lateral(
select--+ no_merge no_unnest
pass
from
(select DBMS_RANDOM.STRING('p',15) pass from dual connect by level<=1e5)
where
rownum=1 -- first pass
and regexp_like(pass,'[^a-zA-Z0-9]') -- special char
and regexp_like(pass,'\d') -- at least one number
and regexp_like(pass,'[A-Z]') -- at least one UPPER letter
) pass_gen;
As you can see here I forced nested loops and disable merge and unnest transformations.

alias required in select list of cursor

I m getting an error as when I compiled the below code as alias required in select list of the cursor.
Create Or Replace PROCEDURE pr_no_debit is
Cursor c_Today(From_date date, To_Date date) is
Select Today from sttm_dates where today between From_Date and To_Date;
cursor c_no_debit is
Select a.* , b.* from STTM_NO_DEBIT_customer a , STTM_FIN_CYCLE b where a.Fin_Cycle = b.Fin_Cycle ;
l_No_Debit_List STTM_NO_DEBIT_CUSTOMER%ROWTYPE;
begin
For i_indx in c_Today(l_No_Debit_List.From_Date,l_No_Debit_List.To_Date)
Loop
for j_indx in c_no_debit
loop
update sttm_cust_account set ac_stat_no_Dr='Y' where account_class=j_index.account_class;
end loop;
End Loop;
-- At the end of the period Change No_Debit to 'N'
End pr_no_debit;
Another solution could be to split the cursor into two parts, though giving alias to respective columns shall be sufficient under the case:
Cursor c_no_debit :
c_no_debit_1: Based on table STTM_NO_DEBIT_customer a
c_no_debit_2: Based on table STTM_FIN_CYCLE b
Through parameterized cursor pass value of of cursor_1 into cursor_2.
Tables STTM_NO_DEBIT_CUSTOMER and STTM_FIN_CYCLE both have a column named FIN_CYCLE, so when the PL/SQL compiler tries to construct the record j_indx from c_no_debit, it gets something like this:
( fin_cycle number
, from_date date
, to_date date
, account_class varchar2(20)
, fin_cycle number
, ...
which is invalid because a record can't have two fields with the same name.
Change c_no_debit to specify only the columns you need, for example:
cursor c_no_debit is
select a.account_class
from sttm_no_debit_customer a
join sttm_fin_cycle b on b.fin_cycle = a.fin_cycle;
(and maybe other columns - I don't have your schema and I don't know what it needs to do)

Resources