pls-00306 error when trying to print output - oracle

This code block returns error pls-00306 wrong number or types of arguments in call to 'put_line':
SET SERVEROUTPUT ON
DECLARE
ch varchar2(16);
cursor ebs_user_cur
is
select xmlagg(xmlelement("r", ch)).extract('//text()').getstringval()
from
(
select distinct first_value(ch) over (partition by lower(ch))
from (
select substr('abcd#efghijklmn#pqrstuvwxyzABC$DEFGHIJK$LMNPQR!STUVWXYZ1!23456789',
level, 1) ch
from dual
connect by level <= 59
order by dbms_random.value
)
where rownum <= dbms_random.value(18,18)
);
l_passwd ebs_user_cur%ROWTYPE;
BEGIN
open ebs_user_cur;
loop
fetch ebs_user_cur into l_passwd;
DBMS_OUTPUT.PUT_LINE(l_passwd);
EXIT WHEN l_passwd%NOTFOUND;
END LOOP;
close ebs_user_cur;
END;
Where is mistake?
Thank you

You should give column alias in your cursors
You should specify exact field name in l_passwd.{field_name}:
SET SERVEROUTPUT ON
DECLARE
ch varchar2(16);
cursor ebs_user_cur
is
select xmlagg(xmlelement("r", ch)).extract('//text()').getstringval()
as res -- <<< see here
from
(
select distinct first_value(ch) over (partition by lower(ch))
from (
select substr('abcd#efghijklmn#pqrstuvwxyzABC$DEFGHIJK$LMNPQR!STUVWXYZ1!23456789',
level, 1) ch
from dual
connect by level <= 59
order by dbms_random.value
)
where rownum <= dbms_random.value(18,18)
);
l_passwd ebs_user_cur%ROWTYPE;
BEGIN
open ebs_user_cur;
loop
fetch ebs_user_cur into l_passwd;
DBMS_OUTPUT.PUT_LINE(l_passwd.res); -- <<< .res here
EXIT WHEN l_passwd%NOTFOUND;
END LOOP;
close ebs_user_cur;
END;

Related

Getting error 'ORA-00976: Specified pseudocolumn or operator not allowed here' in Oracle SQL

I am trying to run a code which generates sample data and am getting the following errors:
ORA-00976: Specified pseudocolumn or operator not allowed here.
ORA-06512: at line 11
I believe rownum is causing this error however, without it I don't know how to create an unique ID. uploads_seq.next does not seem to work either
DECLARE
v_counter INTEGER := 0;
v_num_rows INTEGER;
BEGIN
FOR i IN (SELECT start_date, id FROM batch)
LOOP
v_num_rows := DBMS_RANDOM.VALUE(1, 10);
WHILE v_counter < v_num_rows
LOOP
v_counter := v_counter + 1;
INSERT INTO uploads (id, id_batch, file_name, upload_date, ingested)
VALUES (rownum + 4000, i.id, 'Batch', i.start_date, 'Y');
END LOOP;
v_counter := 0;
END LOOP;
END;
Create a sequence:
CREATE SEQUENCE uploads__id__seq START WITH 4001;
Then you can perform the insert in a single statement (without the need for PL/SQL or loops):
INSERT INTO uploads (id, id_batch, file_name, upload_date, ingested)
SELECT uploads__id__seq.NEXTVAL,
b.id,
'Batch',
b.start_date,
'Y'
FROM (
SELECT start_date,
id,
FLOOR(DBMS_RANDOM.VALUE(1, 11)) AS num_rows
FROM batch
WHERE ROWNUM > 0 -- Force the sub-query to be materialized.
) b
CROSS JOIN LATERAL (
SELECT LEVEL AS rn
FROM DUAL
CONNECT BY LEVEL <= b.num_rows
);
fiddle

SELECT records only once within a stored procedure and operate on selected records

I am selecting same set of records twice, once to return IN REF_CURSOR and then selecting same records to build a CSV so that i can update all records in IN clause .... Can i some how change my procedure to SELECT only once instead of selecting same records twice
PROCEDURE LOADBATCH(
inBUCKET_SIZE IN NUMBER,
OUTCURSOR OUT REF_CURSOR )
AS
V_HANDLE VARCHAR2(2000);
V_LOCK_RESULT INTEGER;
IDs VARCHAR2(2000);
BEGIN
BEGIN
V_HANDLE := GET_LOCK_HANDLE('BATCH');
V_LOCK_RESULT := DBMS_LOCK.REQUEST(V_HANDLE, TIMEOUT => 1);
DBMS_OUTPUT.PUT_LINE(V_LOCK_RESULT);
IF V_LOCK_RESULT <> 1 THEN
OPEN OUTCURSOR FOR SELECT BATCH_ID,INSTRUCTION_ID,INSTRUCTION_DUMP,BATCH_MSG_TYPE,BATCH_AMOUNT,BATCH_CURRENCY,RECIEVED_DATETIME,MODIFIED_DATETIME,SETTLEMENT_DATE,BATCH_STATUS,FROM_MMBID,BATCH_DATE,MODIFICATION_DATETIME,PARENTBATCH_ID,INSTR_REASON FROM
( SELECT DISTINCT BI.*,
BM.*,
BM.AMOUNT AS BATCH_AMOUNT,
BM.CURRENCY AS BATCH_CURRENCY,
BI.PARENT_BATCH_ID AS PARENTBATCH_ID,
BI.REASON AS INSTR_REASON
FROM ACT.BATCH_INSTRUCTIONS BI
INNER JOIN ACT.BATCH_MESSAGES BM
ON BI.BATCH_ID =BM.ID
WHERE (BI.STAGE = 'NEW'
OR (BI.STAGE = 'PICKED'
AND (SYSDATE > (BI.LAST_PICKED_AT + interval '65' second))))
AND (BM.STAGE <> 'COMPLETED')
ORDER BY LAST_PICKED_AT ASC
) WHERE ROWNUM <=inBUCKET_SIZE ;
SELECT listagg(INSTRUCTION_ID, ',') WITHIN GROUP (
ORDER BY INSTRUCTION_ID) AS concatenation
INTO IDs
FROM
(SELECT DISTINCT *
FROM ACT.BATCH_INSTRUCTIONS BI
INNER JOIN ACT.BATCH_MESSAGES BM
ON BI.BATCH_ID =BM.ID
WHERE (BI.STAGE = 'NEW'
OR (BI.STAGE = 'PICKED'
AND (SYSDATE > (BI.LAST_PICKED_AT + interval '65' second)))
)
AND (BM.STAGE <> 'COMPLETED')
ORDER BY LAST_PICKED_AT ASC
)
WHERE ROWNUM <=inBUCKET_SIZE ;
DBMS_OUTPUT.PUT_LINE('IDs are:');
DBMS_OUTPUT.PUT_LINE(IDs);
IF( IDs IS NOT NULL) THEN
UPDATE ACT.BATCH_INSTRUCTIONS
SET LAST_PICKED_AT =sysdate,
STAGE = 'PICKED'
WHERE INSTRUCTION_ID IN
(SELECT INSTRUCTION_ID
FROM ACT.BATCH_INSTRUCTIONS
WHERE INSTRUCTION_ID IN
(SELECT regexp_substr(IDs,'[^,]+', 1, level)
FROM dual
CONNECT BY regexp_substr(IDs, '[^,]+', 1, LEVEL) IS NOT NULL
)
);
COMMIT;
END IF;
END IF;
EXCEPTION
WHEN NO_DATA_FOUND THEN
NULL;
END;
-- DBMS_OUTPUT.PUT_LINE('releasing lock:');
V_LOCK_RESULT := DBMS_LOCK.RELEASE(V_HANDLE);
END LOADBATCH;
Wont work with one select because "select into" does not accept dynamic SQL nor cursors.

FOR LOOP with WHERE clause

This question has been posed before but not specifically for Oracle database.
Can a FOR LOOP be filtered with WHERE clause? For example I would like to do something like:
--LOG ERRORS
FOR err in c_errors WHERE trx_type='CYCLE_COUNT'
LOOP
...do some stuff
END LOOP;
This code gives error:
PLS-00103: Encountered the symbol "WHERE" when expecting one of the following ...
Is there proper syntax for this?
Here is the cursor definition. It grabs cycle count and adjustment transaction types. But in the log errors section mentioned above, I only want to report on cycle count errors. Sure I could use separate cursors, but was trying to accomplish using one.
CURSOR c_errors IS
SELECT DISTINCT CC_ENTRY_INTERFACE_ID INTERFACE_ID
,ERROR_MESSAGE
,creation_date
,LAST_UPDATE_DATE
,'CYCLE_COUNT' TRX_TYPE
FROM mtl_cc_interface_errors
UNION
SELECT DISTINCT TRANSACTION_INTERFACE_ID
,ERROR_EXPLANATION
,CREATION_DATE
,LAST_UPDATE_DATE
,'ADJUSTMENT'
FROM mtl_transactions_interface
WHERE process_flag=3
AND error_code IS NOT NULL
ORDER BY last_update_date DESC;
FOR err in c_errors WHERE trx_type='CYCLE_COUNT'
This is semantically incorrect.
What you can do, as one of the options, is to create a parameterized cursor.
Here is an example:
Case #1: Parameter is null. All rows returned
set serveroutput on;
declare
cursor l_cursor ( param1 varchar2) is
select *
from (
select level as c1
, 'cycle_count' as trx_type
from dual
connect by level < 3
union all
select level as c1
, 'adjustemnt' as trc_type
from dual
connect by level < 3
) q
where param1 is null
or trx_type = param1;
begin
-- param1 = null. No filter applied
for r in l_cursor(null) loop
dbms_output.put_line('C1: ' || to_char(r.c1) || '; ' ||
'TRX_TYPE: ' || r.trx_type);
end loop;
end;
Result:
CNT: 1; TRX_TYPE: cycle_count
CNT: 2; TRX_TYPE: cycle_count
CNT: 1; TRX_TYPE: adjustemnt
CNT: 2; TRX_TYPE: adjustemnt
Case #1: Filtering by TRX_TYPE
set serveroutput on;
declare
cursor l_cursor ( param1 varchar2) is
select *
from (
select level as c1
, 'cycle_count' as trx_type
from dual
connect by level < 3
union all
select level as c1
, 'adjustemnt' as trc_type
from dual
connect by level < 3
) q
where param1 is null
or trx_type = param1;
begin
-- param1 = 'cycle_count'
for r in l_cursor('cycle_count') loop
dbms_output.put_line('C1: ' || to_char(r.c1) || '; ' ||
'TRX_TYPE: ' || r.trx_type);
end loop;
end;
Result:
C1: 1; TRX_TYPE: cycle_count
C1: 2; TRX_TYPE: cycle_count
Nick's answer it's a nice explanation.
Here is another example of how to use it, by declaring the cursor inline with the FOR syntax:
BEGIN
FOR r_product IN (
SELECT
product_name, list_price
FROM
products
WHERE list_price > 120
ORDER BY list_price DESC
)
LOOP
dbms_output.put_line( r_product.product_name ||
': $' ||
r_product.list_price );
END LOOP;
END;
Source:
https://www.oracletutorial.com/plsql-tutorial/plsql-cursor-for-loop/

How to store the number of affected records and return it as a parameter?

I have an in parameter (set = 0), to keep track of the count of entries I will be modifying. I am trying to merge data into a table called Table1, the records that have been updated in a different table (Table2) since the last time Table1 has been updated. The conditional statement will compare the Table1.LastUpdate column to the max(Modified_date) column of Table2 and only insert entries where the table1.last_update column is greater than the table2.max(modified_date) column. Then I will need to store this number and return it as an out parameter. What I have is follows:
create or replace procedure test_proc (rUpdated_Row_Count IN NUMBER, rUpdated_Row_Count_2 OUT NUMBER) is
CURSOR c1 is
select max(modified_date) as max_modified_date
from table1;
l_var c1%ROWTYPE;
-----------
CURSOR c2 is
select table2_id
, last_update
from table2;
k_var c2%ROWTYPE;
BEGIN
LOOP
Open c1;
Fetch c1 into l_var;
Open c2;
Fetch c2 into k_var;
EXIT WHEN c1%NOTFOUND;
IF k_var.last_update > l_var.max_modified_date THEN
Insert into table2(table2_id, last_update)
values(null, k_var.last_update);
commit;
rUpdated_Row_Count_2 := rUpdated_Row_Count + 1;
END IF;
END LOOP;
Close c1;
Close c2;
END test_proc;
Thanks in advance!
Modified my code (after doing further research):
create or replace procedure test_proc (rUpdated_Row_Count IN NUMBER, rUpdated_Row_Count_2 OUT NUMBER) is
CURSOR c1 is
select max(modified_date) as max_modified_date
from table1;
l_var c1%ROWTYPE;
-----------
CURSOR c2 is
select table2_id
, last_update
from table2;
k_var c2%ROWTYPE;
BEGIN
Open c1;
Open c2;
LOOP
Fetch c1 into l_var;
Fetch c2 into k_var;
EXIT WHEN c2%NOTFOUND;
IF k_var.last_update > l_var.max_modified_date THEN
Insert into table2(table2_id, last_update)
values(null, k_var.last_update);
commit;
rUpdated_Row_Count_2 := rUpdated_Row_Count + 1;
END IF;
END LOOP;
Close c1;
Close c2;
END test_proc;
Reproducable data / Code is below:
Create table1
(
table1_id number,
modified_date date
);
Create table2
(
table2_id number,
last_update date
);
insert into table1(table1_id, modified_date) values(1, sysdate);
insert into table1(table1_id, modified_date) values(2, sysdate);
insert into table1(table1_id, modified_date) values(3, sysdate -1);
insert into table2(table2_id, last_update) values(1, sysdate + 1);
insert into table2(table2_id, last_update) values(2, sysdate + 2);
Not quite sure what the "IN" parameter is for. Also not quite sure about the overall rationale. However, here's how I'd write a first version of your procedure:
create or replace procedure test_proc2 (
rUpdated_Row_Count IN NUMBER
, rUpdated_Row_Count_2 IN OUT NUMBER )
is
max_modified_date date ;
begin
select max( modified_date ) into max_modified_date from table1 ;
for rec_ in (
select table2_id, last_update
from table2
) loop
if rec_.last_update > max_modified_date then
insert into table2( table2_id, last_update )
values( null, rec_.last_update ) ;
rUpdated_Row_Count_2 := rUpdated_Row_Count_2 + 1 ;
end if ;
end loop;
end ;
/
Using your test tables (your DDL code should be: CREATE TABLE table1 ... by the way), we can use the following anonymous block for executing the procedure.
-- not sure what the "IN" parameter is used for
declare
rowcount_in number := 0 ; -- not needed
rowcount_out number := 0 ;
begin
test_proc2( rowcount_in, rowcount_out ) ;
dbms_output.put_line( 'updated rows: ' || rowcount_out ) ;
end;
/
updated rows: 2
After executing the anonymous block the tables contain ...
SQL> select * from table1 ;
TABLE1_ID MODIFIED_DATE
1 15-MAY-18
2 15-MAY-18
3 14-MAY-18
SQL> select * from table2 ;
TABLE2_ID LAST_UPDATE
1 16-MAY-18
2 17-MAY-18
NULL 16-MAY-18
NULL 17-MAY-18
Many people will tell you that you should use BULK operations (BULK COLLECT, FORALL etc) whenever possible. Does all that help you?

TRUNC(date) with subquery in oracle

I have a IF condition which checks the dates in the subquery as shown below
IF (TRUNC(lv_d_itr) IN
(SELECT next_day(TRUNC(to_date('01-01-17','dd-mm-yy'),'YYYY') + 14*(level-1), 'WED' )+7
FROM dual
CONNECT BY level <= 26)) THEN
Why am i getting the below error? usage is wrong?
PLS-00405: subquery not allowed in this context
What could be the alternate solution?
You cannot use a sub-query in a pl/sql IF statement:
BEGIN
IF 'X' IN ( SELECT DUMMY FROM DUAL ) THEN
DBMS_OUTPUT.PUT_LINE( 'X' );
END IF;
END;
/
Will error with PLS-00405: subquery not allowed in this context
You can refactor it to get the same effect:
DECLARE
p_exists NUMBER;
BEGIN
SELECT CASE WHEN 'X' IN ( SELECT DUMMY FROM DUAL )
THEN 1
ELSE 0
END
INTO p_exists
FROM DUAL;
IF p_exists = 1 THEN
DBMS_OUTPUT.PUT_LINE( 'X' );
END IF;
END;
/
You can edit this in different ways, a simple one could be something like:
declare
n number;
begin
select count(1)
into n
from
(
SELECT next_day(TRUNC(to_date('01-01-17','dd-mm-yy'),'YYYY') + 14*(level-1), 'MER' )+7 as x
FROM dual
CONNECT BY level <= 26
)
where x = TRUNC(lv_d_itr);
if n > 0
then ...
end if;
...
end;

Resources