Using variables in PLSQL SELECT statement - sql

I have a query that queries on ReportStartDate and ReportEndDate so I thought I would use variables in PLSQL. Not sure what I am missing here, but I get an error:
CLEAR;
DECLARE
varReportStartDate Date := to_date('05/01/2010', 'mm/dd/yyyy');
varReportEndDate Date := to_date('05/31/2010', 'mm/dd/yyyy');
BEGIN
SELECT
'Value TYPE',
1 AS CountType1,
2 AS CountType2,
3 AS CountType3
FROM DUAL;
SELECT COUNT (*)
FROM CDR.MSRS_E_INADVCH
WHERE 1=1
AND ReportStartDate = varReportStartDate
AND ReportEndDate = varReportEndDate
;
END;
/
The Error is:
Error starting at line 2 in command:
Error report:
ORA-06550: line 6, column 5:
PLS-00428: an INTO clause is expected in this SELECT statement
ORA-06550: line 8, column 5:
PLS-00428: an INTO clause is expected in this SELECT statement
06550. 00000 - "line %s, column %s:\n%s"
*Cause: Usually a PL/SQL compilation error.
*Action:
This happens in Toad as well as in SQL Developer.
What is the proper way of using the variables in my WHERE clause?

You cannot use SQL statements directly in a PL/SQL block ( unless you use EXECUTE IMMEDIATE). The columns will need to be fetched into variables ( which is what PL/SQL is telling you with PLS-00428: an INTO clause is expected in this SELECT statement error). So you'll have to rewrite your statements as below.
SELECT
'Value TYPE',
1 AS CountType1,
2 AS CountType2,
3 AS CountType3
INTO
V_VALUE_TYPE,
V_CountType1,
V_CountType2,
V_CountType3
FROM DUAL;
SELECT COUNT(*)
INTO V_COUNT
FROM CDR.MSRS_E_INADVCH
WHERE 1=1
AND ReportStartDate = varReportStartDate
AND ReportEndDate = varReportEndDate
Be sure to add Exception Handlers, since PL/SQL expects only 1 row to be returned. If the statement returns no rows, you'll hit a NO_DATA_FOUND exception - and if the statement fetches too many rows, you'll hit a TOO_MANY_ROWS exception.

The question you have to answer is what do you want to do with the data that has been selected?
Sathya gave you one approach - declare variables in your PL/SQL block and select the columns INTO those variables. Note that this requires that the SELECT statement returns exactly one row - any more or less rows will throw an error. Another way is to declare collection types using the BULK COLLECT option: http://oracletoday.blogspot.com/2005/11/bulk-collect_15.html
Yet another option is to have the procedure return a cursor. This is useful in the case where the calling code expects to be able to fetch the data that the procedure has selected:
PROCEDURE GET_MY_REPORT( varReportStartDate in date, varReportEndDate in date, cur out sys_refcursor) is
begin
OPEN cur FOR SELECT *
FROM CDR.MSRS_E_INADVCH
WHERE 1=1
AND ReportStartDate = varReportStartDate
AND ReportEndDate = varReportEndDate;
END GET_MY_REPORT;

Related

MERGE statement with "WITH FUNCTION" definition

I am trying to use a function declared in WITH clause, into a MERGE statement.
Here is my code:
create table test
(c1 varchar2(10),
c2 varchar2(10),
c3 varchar2(10));
insert into test(c1, c2) values ('a', 'A');
insert into test(c1, c2) values ('b', 'A');
select * from test;
begin
with function to_upper(val varchar2) return varchar is
begin
return upper(val);
end;
merge into test a
using (select * from test) b
on (upper(a.c1) = upper(b.c2))
when matched then
update set a.c3 = to_upper(a.c1);
end;
but I am getting this error:
Error report - ORA-06550: line 2, column 15: PL/SQL: ORA-00905:
missing keyword ORA-06550: line 2, column 1: PL/SQL: SQL Statement
ignored ORA-06550: line 6, column 1: PLS-00103: Encountered the symbol
"MERGE"
06550. 00000 - "line %s, column %s:\n%s"
*Cause: Usually a PL/SQL compilation error.
*Action:
Can someone explain why it is not working, please?
Thank you,
The with clause is part of the select syntax. As the railroad diagram for that shows, the only thing that's valid after the with clause is the select keyword - you don't have that, hence the error you are seeing.
As the documentation also says says:
The plsql_declarations clause lets you declare and define PL/SQL functions and procedures. You can then reference the PL/SQL functions in the query in which you specify this clause, as well as its subqueries, if any.
and
If the query in which you specify this clause is not a top-level SELECT statement, then the following rules apply to the top-level SQL statement that contains the query:
If the top-level statement is a SELECT statement, then it must have either a WITH plsql_declarations clause or the WITH_PLSQL hint.
If the top-level statement is a DELETE, MERGE, INSERT, or UPDATE statement, then it must have the WITH_PLSQL hint.
So, you can't apply the with clause to the whole merge statement, you can only use it as part of a query within that, i.e. in the using clause:
merge /*+ WITH_PLSQL */ into test a
using (
with function to_upper(val varchar2) return varchar is
begin
return upper(val);
end;
select to_upper(c2) as c2 from test
) b
on (upper(a.c1) = b.c2)
when matched then
update set a.c3 = upper(a.c1);
Without the /*+ WITH_PLSQL */ hint this would error with "ORA-32034: unsupported use of WITH clause" (though only if you actually call the function, otherwise the complier seems to strip the unused function out and not complain about it).
But the function is still only in scope for the using clause; you can't refer to it in the on or update clauses. You would need to do any function calls within the using clause; and if you need the same function applied to anything from the target table, you would need to repeat the function and call that with the into clause, e.g.:
merge /*+ WITH_PLSQL */ into (
with function to_upper(val varchar2) return varchar is
begin
return upper(val);
end;
select to_upper(c1) as c1, c3 from test
) a
using (
with function to_upper(val varchar2) return varchar is
begin
return upper(val);
end;
select to_upper(c2) as c2 from test
) b
on (a.c1 = b.c2)
when matched then
update set a.c3 = a.c1;
db<>fiddle showing things that do and don't work.
It's quite a contrived example so it would probably be clearer with a more real-world scenario.

Subquery in SQL with LOOP

I am getting an error when trying to run a select statement in a PLSQL loop. How can I get it to work?
DECLARE
v_gsp_cnt number := 0;
BEGIN
WHILE v_gsp_cnt > (select max(eachesrem) from salesplan)
LOOP
insert into salesplanexp (select SPNUM,ORDNUM,DMDTYPE,ORDSTATUS,ITEM,LOC,ITEMDESCR,FCST,ORDTYPE,DELSERVLVL,SHIPMETHOD,CARRIER,EACHES,SHIPPEDEACHES,EACHESREM,WEIGHT,VOLUME,STARTSHIPDATE,ENDSHIPDATE,ACTSHIPDATE,STARTDELDATE,ENDDELDATE,ACTDELDATE,ORDCREATEDATE,ORDCONFDATE,BILLCUSTNAME,BILLADD1,BILLADD2,BILLCITY,BILLSTATE,BILLCOUNTRY,BILLZIP,SHIPCUSTNAME,SHIPADD1,SHIPADD2,SHIPCITY,SHIPSTATE,SHIPCOUNTRY,SHIPZIP,LOCPHONE,LOCEMAIL,ACTITEMCOST,ACTITEMPRICE,ACTITEMMARGIN,ACTSHIPCOST,ACTSHIPPRICE,ACTSHIPMARGIN,ACTTOTCOST,ACTTOTPRICE,ACTTOTMARGIN,BRAND,CHANNEL,EXTORDID,EXTLINEID,SOURCESYS,CREATEDATE,LASTUPDATEDDATE from salesplan);
commit;
select x.new_cnt into v_gsp_cnt from (select v_gsp_cnt+1 from dual) x;
commit;
END LOOP;
END;
The error is
ORA-06550: line 4, column 23:
PLS-00405: subquery not allowed in this context
ORA-06550: line 4, column 5:
PL/SQL: Statement ignored
The error, I believe, is from the select statement. But with the loop, I'm not sure another way to make it run properly
The error is coming from this line:
WHILE v_gsp_cnt > (select max(eachesrem) from salesplan)
the actual error is a bit deceiving, but that's the source (note it says "line 4" .. count from DECLARE).
You can't have a query like this in this place.
try this instead:
select max(eachesrem) into v_max from salesplan;
WHILE v_gsp_cnt > v_max
(and as well mentioned by JNevill: you need to alias the inner "v_gsp_cnt+1" as new_cnt ... ) but that's a different error which will show up after you fix the one above. ;)

Declaring variables and select statement in a procedure

I'm writing a SQL procedure which should use calculated date stored as a local variable in a select statement. I'm using Oracle SQL developer. My code is:
create or replace PROCEDURE
my_procedure
AS
BEGIN
DECLARE
l_max_dt DATE;
BEGIN
SELECT MAX(TRX_DT)
INTO l_max_dt
FROM TABLE
WHERE 1=1;
end;
select * from TABLE where trx_dt = l_max_dt;
end;
This code gives me an error : " Error(14,48): PL/SQL: ORA-00904: "L_MAX_DT": invalid identifier" when select statement is present.
How can I store variables to use them in statements?
This is how you write a Procedure. Syntax is incorrect. Read about syntax Here
CREATE OR REPLACE PROCEDURE my_procedure
AS
l_max_dt DATE;
v_var TABLE2%ROWTYPE;
BEGIN
SELECT MAX (TRX_DT)
INTO l_max_dt
FROM TABLE1
WHERE 1 = 1;
-- Assuming the query will retrun only 1 row.
SELECT *
INTO v_var
FROM TABLE2
WHERE trx_dt = l_max_dt;
END;
Your issue is one of scope. In your procedure, you have a nested block, in which you declare the l_max_dt variable. Once the code has exited that block, the l_max_dt variable is no longer in scope - i.e. the outer block does not know anything about it.
There is no need to have a nested block in this instance - you can do it all in the same block, like so:
create or replace PROCEDURE my_procedure
AS
l_max_dt DATE;
BEGIN
SELECT MAX(TRX_DT)
INTO l_max_dt
FROM TABLE
WHERE 1=1;
-- commented out as this isn't valid syntax; there is a missing INTO clause
-- select *
-- from TABLE where trx_dt = l_max_dt;
END my_procedure;
However, you could simply do the query in one fell swoop - e.g.:
select *
from your_table
where trx_dt = (select max(trx_dt) from your_table);
A couple of points about your procedure:
In PL/SQL, if you use an implicit cursor (i.e. when you put a select statement directly in the body of the code) you need to have something to put the results into. You could bulk collect the results into an array, or you could ensure that you will receive exactly one row (or code error handling for NO_DATA_FOUND and TOO_MANY_ROWS) into a record or corresponding scalar variables.
You shouldn't use select * in your procedure - instead, you should explicitly state the columns being returned, because someone adding a column to that table could cause your procedure to error. There are exceptions to this "rule", but explicitly stating the columns is a good habit to get into.

PL/SQL Error assign sysdate value to variable (date) using select into clause

I was writing a PL/SQL procedure to come up with a report.
Here is part of my scripts where I tested and had the compilation error.
I believe it's not the syntax of the select into clause, but I don't know the exact problem when assigning value to the date variables...
Did anyone encounter this error before?
DECLARE
v_bc_mth DATE;
BEGIN
SELECT TRUNC(ADD_MONTHS(SYSDATE, -1)) INTO v_bc_mth FROM dual; --what's wrong with the clause
SELECT * FROM bc WHERE v_bc_mth BETWEEN bc_start_date AND bc_end_date;
END;
PLS-00428: an INTO clause is expected in this SELECT statement
06550. 00000 - "line %s, column %s:\n%s"
*Cause: Usually a PL/SQL compilation error.
You need the INTO clause for both SELECT statements.
However, do you really need to use PL/SQL?
You could do it all in SQL, avoiding context switches with:
SELECT *
FROM bc
WHERE TRUNC(ADD_MONTHS(SYSDATE, -1)) BETWEEN bc_start_date AND bc_end_date;
Or, you could rewrite what you have so there is only one switch to SQL from within your PL/SQL block as:
DECLARE
TYPE bc_tabtype IS TABLE OF bc%ROWTYPE
INDEX BY pls_integer;
--
bc_tab bc_tabtype;
BEGIN
SELECT *
BULK COLLECT INTO bc_tab
FROM bc
WHERE TRUNC(ADD_MONTHS(SYSDATE, -1)) BETWEEN bc_start_date AND bc_end_date;
-- Do what you want with the results you now have in the Associative Array bc_tab
END;
You might need to look up associative arrays and BULK COLLECT etc. to understand them.
Tom Kyte, the Oracle VP states it succinctly when he says:
I have a pretty simple mantra when it comes to developing database
software, and I have written this many times over the years:
You should do it in a single SQL statement if at all possible.
If you cannot do it in a single SQL statement, do it in PL/SQL.
If you cannot do it in PL/SQL, try a Java stored procedure.
If you cannot do it in Java, do it in a C external procedure.
If you cannot do it in a C external procedure, you might want to seriously think about why it is you need to do it.
EDIT:
In light of your comment, try this:
DECLARE
v_bc_mth DATE := TRUNC(ADD_MONTHS(SYSDATE, -1));
--
TYPE bc_tabtype IS TABLE OF bc%ROWTYPE
INDEX BY pls_integer;
--
bc_tab bc_tabtype;
BEGIN
SELECT *
BULK COLLECT INTO bc_tab
FROM bc
WHERE v_bc_mth BETWEEN bc_start_date AND bc_end_date;
END;

How to store query result in a variable in sql

I am using following query:
DECLARE
result varchar2(100);
BEGIN
select (systimestamp - (select date_time from test where id=2945134)) into result from dual;
SELECT SUBSTR(result, 3,1) Final_result
FROM DUAL;
END;
It is showing an error like:
PLS-00428: an INTO clause is expected in this SELECT statement
06550. 00000 - "line %s, column %s:\n%s"
Cause: Usually a PL/SQL compilation error.
I need to store result value from first query into result variable and then use that result variable to show the substring (3,1) as a Final_result.
DECLARE
result varchar2(100);
BEGIN
select substr((systimestamp - (select date_time from test where id=2945134)), 3,1)
into result
from dual;
dbms_output.put_line(result);
END;
/
But this is a highly dubious thing you are doing. You are relying on implicit datatype conversion (interval --> varchar) which will not work properly if your NLS settings change.
You would better use to_char() to format the resulting interval to something appropriate.
Instead of making 2 select from the same table, just 1 should suffice.
Also as a practice, always try using the fully qualified name of your objects
Why do you need the "DUAL" table
DECLARE
#final_result varchar2(100);
BEGIN
select #final_result= SUBSTR((systimestamp - (select date_time from test where id=2945134)),3,1);
END;