Sending result of Procedure as the body of an email - oracle

I have a procedure which returns a set of rows.
Similarly, I am using a procedure to send an email whose parameters is as follows :
SEND_MAIL ( mail_to, mail_from, mail_subject, mail_body, host)
I have been trying to pass a variable which consists of the result from the DBMS_OUTPUT.PUT_LINE as mail subject in the SEND_MAIL procedure but I am only obtaining the last record of the result which indicates the variables only has the latest inserted value.
I want to receive the entire result in the mail body.
create OR REPLACE PROCEDURE EVAL_LOG AS
mail_body_v varchar2(4000);
BEGIN
FOR I IN ( select LOG.LOG_DATE,LOG.ACTION,LOG.OLD,LOG.NEW from
dw.EVALUATIONS_LOG LOG
where to_char(log_date,'DD-MON-YYYY')
in (select max(to_char(log_date,'DD-MON-YYYY')) from dw.EVALUATIONS_LOG))
loop mail_body_v:=
DBMS_OUTPUT.PUT_LINE (
( 'Date : ' || i.log_date
|| ' ACTION : ' || i.ACTION
|| ' OLD : ' || i.OLD
|| ' NEW : ' || i.NEW));
end loop;
SEND_MAIL('****#mailid.com',
'Subject',mail_body_v,'****#mailid.com','host');
end;
Output:
Date : 16-AUG-22 ACTION : INSERT OLD : NEW : 2382382
Date : 16-AUG-22 ACTION : UPDATE OLD : 175 NEW : 195
Date : 16-AUG-22 ACTION : INSERT OLD : NEW : 232323
Date : 16-AUG-22 ACTION : UPDATE OLD : 195 NEW : 175
Date : 16-AUG-22 ACTION : UPDATE OLD : 195 NEW : 175
Date : 16-AUG-22 ACTION : UPDATE OLD : 175 NEW : 195
Date : 16-AUG-22 ACTION : DELETE OLD : 232323 NEW :

You are overwriting value of mail_body_v each time you enter loop, thats why only the last result is visible.
Instead of assigning new value to the mail_body_v each time it enters the loop, you should add text to the old value.
So instead of: mail_body_v:= DBMS_OUTPUT...
you should do it like that: mail_body_v:= mail_body_v || DBMS_OUTPUT...
Your code, after required changes could look like:
CREATE OR REPLACE PROCEDURE EVAL_LOG AS
mail_body_v varchar2(4000);
BEGIN
FOR I IN ( select LOG.LOG_DATE,LOG.ACTION,LOG.OLD,LOG.NEW from
dw.EVALUATIONS_LOG LOG
where to_char(log_date,'DD-MON-YYYY')
in (select max(to_char(log_date,'DD-MON-YYYY')) from dw.EVALUATIONS_LOG))
loop
mail_body_v:= mail_body_v ||
DBMS_OUTPUT.PUT_LINE (
( 'Date : ' || i.log_date
|| ' ACTION : ' || i.ACTION
|| ' OLD : ' || i.OLD
|| ' NEW : ' || i.NEW));
end loop;
SEND_MAIL('****#mailid.com',
'Subject',mail_body_v,'****#mailid.com','host');
end;

Related

Getting the number of rows with same date in a procedure (PL/SQL)

I have been working this for a while now, and I am confused as to why I am getting "exact fetch returns more than requested number of rows" error. I even followed the answer for this Udemy course but that was giving the same error as well.
This is the code:
CREATE OR REPLACE PROCEDURE GET_SALE_COUNT
(
c_order_id IN NUMBER,
c_count_sales OUT NUMBER
)
AS
c_sales_date sales.sales_date%TYPE;
c_product_id sales.product_id%TYPE;
c_customer_id sales.customer_id%TYPE;
c_salesperson_id sales.salesperson_id%TYPE;
c_quantity sales.quantity%TYPE;
c_unit_price sales.unit_price%TYPE;
c_sales_amount sales.sales_amount%TYPE;
c_tax_amount sales.tax_amount%TYPE;
c_total_amount sales.total_amount%TYPE;
BEGIN
SELECT sales_date,product_id,customer_id,salesperson_id,quantity,unit_price,sales_amount,tax_amount,total_amount
INTO c_sales_date,c_product_id,c_customer_id,c_salesperson_id,c_quantity,c_unit_price,c_sales_amount,c_tax_amount,c_total_amount
FROM sales WHERE c_order_id = sales.order_id;
DBMS_OUTPUT.put_line('Sales Date: ' || c_sales_date);
DBMS_OUTPUT.put_line('Product ID: ' || c_product_id);
DBMS_OUTPUT.put_line('Customer ID: ' || c_customer_id);
DBMS_OUTPUT.put_line('Salesperson ID: ' || c_salesperson_id);
DBMS_OUTPUT.put_line('Quantity: ' || c_quantity);
DBMS_OUTPUT.put_line('Unit Price: ' || c_unit_price);
DBMS_OUTPUT.put_line('Sales Amount: ' || c_sales_amount);
DBMS_OUTPUT.put_line('Tax Amount: ' || c_tax_amount);
DBMS_OUTPUT.put_line('Total Amount: ' || c_total_amount);
SELECT COUNT(1) INTO c_count_sales FROM sales WHERE sales_date = c_sales_date;
END GET_SALE_COUNT;
DECLARE
count_sales NUMBER;
BEGIN
GET_SALE_COUNT(1267, count_sales);
DBMS_OUTPUT.put_line('Sale Count: ' || count_sales);
END
;
If your task is to count number of rows, why are you doing other unnecessary stuff? Because, it is the first select you wrote that returns more than exactly one row (and returns TOO_MANY_ROWS error). Why did that happen? Because not only one row exists for c_order_id parameter you pass. Run that statement out of the procedure and you'll see.
Therefore, as your task isn't to do what you did, skip it and use something like this:
create or replace procedure get_sale_count
(c_order_id in number,
c_count_sales out number
)
is
begin
select count(*)
into c_count_sales
from sales a
where a.sales_date in (select b.sales_date
from sales b
where b.order_id = c_order_id);
end;
/
If you do need to display various data, then do it in a loop.

Appending values to a CLOB in an 'after update' trigger

CREATE TABLE COPPER_TAN_META
(
ID decimal(22,0) PRIMARY KEY,
NOTES clob,
ERROR varchar2(2000)
);
CREATE UNIQUE INDEX SYS_C0070016 ON COPPER_TAN_META(ID);
Before update
ID ERROR NOTES
20 <null> 27-APR-21 08.48.18 AM - XML is not a full-text article;
Update
update COPPER_TAN_META set error = 'trigger warning' where id = 20;
With trigger, I'd like to see this:
ID ERROR NOTES
20 trigger warning 27-APR-21 08.48.18 AM - XML is not a full-text article; <timestamp> - trigger warning;
My trigger doesn't work:
CREATE OR REPLACE TRIGGER copper_error_appends_to_note
AFTER UPDATE of error on COPPER_TAN_META
for each row
begin
IF :new.error is not null THEN
:new.notes := :old.notes || localtimestamp(0) || ' - ' || :new.error || '; ';
END IF;
end;
/
Error: ORA-04098: trigger 'B026.COPPER_ERROR_APPENDS_TO_NOTE' is invalid and failed re-validation
This works fine using a normal update statement, like
update copper_tan_meta
set notes = notes || localtimestamp(0) || ' - ' || :fileError || '; '
where id = :tanMetaId
I'm running this on SQuirreL SQL Client Version 3.5.3
new values cannot be changed for AFTER trigger type, but possible for BEFORE
localtimestamp without argument might be used within the direct concatenation, but localtimestamp with precision argument such as localtimestamp(0) could be used within a SQL statement.
So, rewrite the code block as
CREATE OR REPLACE TRIGGER copper_error_appends_to_note
BEFORE UPDATE OF ERROR ON copper_tan_meta
FOR EACH ROW
DECLARE
ts TIMESTAMP;
BEGIN
SELECT localtimestamp(0) INTO ts FROM dual;
IF :new.error IS NOT NULL THEN
:new.notes := :old.notes || ts || ' - ' || :new.error || '; ';
END IF;
END;
/

PLS-00306: wrong number or types of arguments in call to 'fun'

I'm trying to call an function with table type argument which is defined in package scope, but got error:
PLS-00306: wrong number or types of arguments in call to 'fun'.
CREATE OR REPLACE PACKAGE pkg AS
TYPE aging_sch_record_type IS
RECORD (days_delq NUMBER, eligible NUMBER, unit NUMBER, balance NUMBER);
TYPE aging_sch_table_type IS
TABLE OF aging_sch_record_type INDEX BY VARCHAR2(20);
PROCEDURE proc ();
FUNCTION fun (v_aging_sch_table aging_sch_table_type,
v_days_delq NUMBER, v_eligible NUMBER) RETURN NUMBER;
END pkg;
create or replace PACKAGE BODY pkg AS
PROCEDURE proc () AS
CURSOR aging_sch_cursor IS
SELECT ...
GROUP BY ...;
v_aging_sch_row aging_sch_cursor%rowtype;
v_aging_sch_table aging_sch_table_type;
BEGIN
FOR v_aging_sch_row IN aging_sch_cursor LOOP
v_aging_sch_table(v_aging_sch_row.days_delq || ' ' || v_aging_sch_row.eligible).days_delq := v_aging_sch_row.days_delq;
v_aging_sch_table(v_aging_sch_row.days_delq || ' ' || v_aging_sch_row.eligible).eligible := v_aging_sch_row.eligible;
v_aging_sch_table(v_aging_sch_row.days_delq || ' ' || v_aging_sch_row.eligible).unit := v_aging_sch_row.unit;
v_aging_sch_table(v_aging_sch_row.days_delq || ' ' || v_aging_sch_row.eligible).balance := v_aging_sch_row.balance;
END LOOP;
INSERT INTO t
VALUES (fun(v_aging_sch_table,0,2));
END proc;
FUNCTION fun (v_aging_sch_table aging_sch_table_type,
v_days_delq NUMBER, v_eligible NUMBER) RETURN NUMBER
AS
BEGIN
...
END fun;
END pkg;
Any idea on what the problem?
Thanks.
You cannot use associative array in SQL (and you cannot use any array declared in package specification in SQL in Oracle versions prior to 12c), but you can store result of the fun function into local variable, and then insert that variable into table t:
PROCEDURE proc () AS
CURSOR aging_sch_cursor IS
SELECT ...
GROUP BY ...;
v_aging_sch_row aging_sch_cursor%rowtype;
v_aging_sch_table aging_sch_table_type;
v_fun number;
BEGIN
FOR v_aging_sch_row IN aging_sch_cursor LOOP
v_aging_sch_table(v_aging_sch_row.days_delq || ' ' || v_aging_sch_row.eligible).days_delq := v_aging_sch_row.days_delq;
v_aging_sch_table(v_aging_sch_row.days_delq || ' ' || v_aging_sch_row.eligible).eligible := v_aging_sch_row.eligible;
v_aging_sch_table(v_aging_sch_row.days_delq || ' ' || v_aging_sch_row.eligible).unit := v_aging_sch_row.unit;
v_aging_sch_table(v_aging_sch_row.days_delq || ' ' || v_aging_sch_row.eligible).balance := v_aging_sch_row.balance;
END LOOP;
v_fun := fun(v_aging_sch_table,0,2)
INSERT INTO t
VALUES (v_fun);
END proc;
Furthermore, you are inserting result of the function into table t without naming column - does that table really have only one column? If not, you should name every column into which you are inserting data, as in:
INSERT INTO t (COLUMN1)
VALUES (v_fun);
If you weren't indexing by string you could have used standalone object type and nested table which would have worked in SQL, but in your case that is not an option.

Use of dbms_sql

This piece of code does not work but I dont know why :
set serveroutput on buffer 2560000
declare
requete varchar2(4000);
name_firm varchar2(35);
curseur_ref number;
response number;
begin
curseur_ref := dbms_sql.open_cursor;
requete := 'SELECT trim(myTable.myColumn) from myTable';
dbms_sql.parse(curseur_ref, requete, dbms_sql.native);
dbms_sql.define_column(curseur_ref, 1, name_firm, 35);
response := dbms_sql.execute(curseur_ref);
dbms_sql.column_value(curseur_ref, 1, name_firm);
dbms_output.put_line('NB enregs : ' || dbms_sql.fetch_rows(curseur_ref));
dbms_output.put_line('name_firm : ' || name_firm);
dbms_sql.close_cursor(curseur_ref);
end;
There is no plsql error
The result is :
NB enregs : 1 (it found one... which is correct)
name_firm : 'there is nothing but a name exists'
The request is good as I can send it alone and have the name of the firm
What is going on?
Thank you for answering
Note : the thing is that I saw this code work once....
You have your column_value call in the wrong place; it needs to be after the fetch:
...
response := dbms_sql.execute(curseur_ref);
-- dbms_sql.column_value(curseur_ref, 1, name_firm);
dbms_output.put_line('NB enregs : ' || dbms_sql.fetch_rows(curseur_ref));
dbms_sql.column_value(curseur_ref, 1, name_firm);
dbms_output.put_line('name_firm : ' || name_firm);
...
Which gives:
NB enregs : 1
name_firm : some val
From the documentation:
Call COLUMN_VALUE Procedure or COLUMN_VALUE_LONG Procedure to determine the value of a column retrieved by the FETCH_ROWS Function
for your query
It's also shown in their third example.
The only way i can see you'd have seen it working in the past is if it used to be in a loop fetching multiple rows, in which case you'd see a null value for the first one and the previous row's value for the rest.
Of course, there's no need to use dynamic SQL for this, but I assume this is a stripped down example to demonstrate the issue.

oracle Stored Procedure to get the sum(balance) given a parameter

I have a supplier_product table ( supp_id, prod_id, invoice_id,price) and an invoice table (invoice_id, balance). I tried a stored proc. given (supp_id) it should all the existing invoice_id and display the balance. here's my code:
set serverouput on;
create or replace
Procedure SUP_loop
(v_SUPPLIER_ID int )
AS
CURSOR c_SUP IS
select SUPPLIER_ID , SUPP_INVOICE_ID, balance
from SUPPLIER_PRODUCT, supplier_invoice
where SUPPLIER_ID=v_SUPPLIER_ID
and supp_invoice_id.supplier_product=supp_invoice_id.supplier_invoice;
BEGIN
--LOOP WITH IMPLICIT VARIABLE DECLARED
--AUTOMATIC, OPEN FETCH, CLOSE
FOR v_SUP_data IN c_SUP LOOP
DBMS_OUTPUT.PUT_LINE(TO_CHAR(v_SUP_data.SUPPLIER_ID) || ' ' ||
TO_CHAR(v_SUP_data.SUPP_INVOICE_ID) || ' ' ||
TO_CHAR(v_SUP_data.balance) );
END LOOP;
END;
/
the error i am getting is v_sup_data Error(20,31): PLS-00364: loop index variable 'V_SUP_DATA' use is invalid
Error(9,74): PL/SQL: ORA-00904: "SUPP_INVOICE_ID"."SUPPLIER_INVOICE": invalid identifier
You have the field and the table names swapped round the wrong way.
You have...
supp_invoice_id.supplier_invoice
...where you should have...
supplier_invoice.supp_invoice_id
:D
The syntax for referring to a column is <>.<>. So your cursor query needs the join condition to be supplier_produce.supp_invoice_id = supplier_invoice.supp_invoice_id, i.e.
create or replace
Procedure SUP_loop
(v_SUPPLIER_ID int )
AS
CURSOR c_SUP IS
select SUPPLIER_ID , SUPP_INVOICE_ID, balance
from SUPPLIER_PRODUCT, supplier_invoice
where SUPPLIER_ID=v_SUPPLIER_ID
and supplier_product.supp_invoice_id = supplier_invoice.supp_invoice_id;
BEGIN
--LOOP WITH IMPLICIT VARIABLE DECLARED
--AUTOMATIC, OPEN FETCH, CLOSE
FOR v_SUP_data IN c_SUP LOOP
DBMS_OUTPUT.PUT_LINE(TO_CHAR(v_SUP_data.SUPPLIER_ID) || ' ' ||
TO_CHAR(v_SUP_data.SUPP_INVOICE_ID) || ' ' ||
TO_CHAR(v_SUP_data.balance) );
END LOOP;
END;
/

Resources