Simple PL/SQL Procedure. Getting PL/SQL: SQL Statement ignored - oracle

Trying to create a very simple authentication procedure for use in Oracle APEX. My procedure is below
create or replace function pmats_authenticate(uname in varchar2, pass in varchar2)
return boolean
as
begin
declare
afound number:=0;
begin
select 1 from dual into afound;
if afound = 1 then
return true;
else
return false;
end if;
end;
end pmats_authenticate;
And I get the old PL/SQL: SQL Statement ignored thing on the select. I got the select down to the simplest thing I could think of 'select 1 from dual'. I'm scratching my head on this one I'm afraid.

The INTO clause goes before the FROM clause, so your SELECT statement should read
SELECT 1 INTO afound FROM DUAL
For reference the general structure of a SELECT is
WITH ...common table expressions...
SELECT ...fields...
INTO ...bind variables...
FROM ...tables...
INNER JOIN ...other tables... ON ...conditions...
LEFT|RIGHT|FULL OUTER JOIN ...other tables... ON ...conditions...
WHERE ...conditions...
GROUP BY ...fields...
HAVING ...conditions...
Share and enjoy.

Related

Oracle PL/SQL SELECT INTO clause thinks it needs another INTO

I have a simple test function where I'm passing in a specific ID (the primary key of the table I'm selecting from), and computing a simple function on it and the parameters.
The skeleton code and test:
create or replace function test(id varchar2, area float) return float is
theRow forest%ROWTYPE;
begin
select * into theRow from forest where Forest_No = id;
return area / theRow.Area;
end;
begin
select test('1', 16000) from dual;
end;
The output:
[2019-10-14 21:19:10] [65000][6550] ORA-06550: line 2, column 5:
[2019-10-14 21:19:10] PLS-00428: an INTO clause is expected in this SELECT statement
I am at a loss for what to do here, as far as I can tell the documentation and examples use the same order and syntax. I have tried moving the into clause to the end as in Postgresql, but that did not work.
What have I missed here?
Issue is in calling statement.
Whenever select statement is used in plsql block it must have into clause to assign return value to variable.
You should remove begin and end from your calling code:
--begin -- remove this
select test('1', 16000) from dual;
--end; -- remove this
Or if you want to use it in plsql block then add into clause:
Declare
Area_ float(precision);
begin
select test('1', 16000) into area_ from dual;
-- use area_ in your code wherever required
dbms_output.put_line('area: ' || area_);
end;
Cheers!!

ORA-22905 - While using DBMS_SQL.Number_Table for Table Operator

All,
Here is a simplified version of what I am attempting to accomplish. Instead of declaring my own type, I used the Number_Table type from the dbms_sql package.
First I created a simple test table:
CREATE TABLE collect_test(id NUMBER(38), other_info VARCHAR2(5));
Then, populated the table with a small amount of data:
INSERT INTO collect_test
SELECT rownum, chr(rownum+60)
FROM dual
CONNECT BY rownum <= 10;
Finally, I attempt to use PL/SQL to select some rows into a collection then use that collection to delete rows from the table:
DECLARE
l_tIDList DBMS_SQL.Number_Table;
BEGIN
SELECT ct.id
BULK COLLECT INTO l_tIDList
FROM collect_test ct
WHERE mod(ct.id, 2) = 0;
DELETE FROM (SELECT ct.id
FROM collect_test ct
INNER JOIN table(l_tIDList) ids ON ct.id = ids.column_value);
ROLLBACK;
END;
/
However, when I run this PL/SQL block I receive these errors:
ORA-06550: line 11, column 33:
PLS-00382: expression is of wrong type
ORA-06550: line 11, column 27:
PL/SQL: ORA-22905: cannot access rows from a non-nested table item
ORA-06550: line 9, column 3: PL/SQL: SQL Statement ignored
In all the other questions/articles I have found it seems like the coder was either trying to use a local type or forgetting to BULK COLLECT. I appreciate any suggestions you may have.
N.B.: I realize that I can do this specific functionality using a single DELETE statement. My actual scenario is more complicated and can't be done with a single statement.
Although it is not clear what you are trying to accomplish, there are two things which are wrong with your pl/sql block.
You can use the TABLE() function on the variables of collection
types defined in the schema and not on those defined locally
The way you are running your DML ( delete ) statement is wrong, it throws the error `
ORA-22841: DML on PL/SQL collections not supported.
So, create a collection TYPE in your schema and declare a collection of that type in your PL/SQL block and for your delete use an IN condition
CREATE OR REPLACE TYPE idlisttable as TABLE OF NUMBER;
DECLARE l_tidlist idlisttable;
BEGIN
SELECT ct.id BULK collect
INTO l_tidlist
FROM collect_test ct
WHERE MOD(ct.id, 2) = 0;
DELETE
FROM collect_test ct
WHERE ct.id IN (
SELECT ids.column_value
FROM TABLE (l_tidlist) ids
);
ROLLBACK;
END;
/
`
I have tried this using a cursor and it worked for me. Please try as below.
DECLARE
l_tIDList DBMS_SQL.Number_Table;
CURSOR c
IS
SELECT ct.id FROM collect_test ct WHERE mod(ct.id, 2) = 0;
BEGIN
OPEN c;
LOOP
FETCH c bulk collect INTO l_tIDList;
forall i IN 1 .. l_tIDList.count
DELETE FROM collect_test t WHERE t.id = l_tIDList(i);
EXIT
WHEN c%notfound;
END LOOP;
COMMIT;
CLOSE c;
END ;
/

Select Statement in oracle procedure

I can't able to create a select statement in oracle procedure. Please help me to create this.
Now I create the insert,update.delete statement in a procedure but i can't create a select statement. Please help me to create the select statement using cursor.
c_dbuser OUT SYS_REFCURSOR
ELSIF (TYPE_ =1) THEN
OPEN c_dbuser FOR
SELECT * FROM tbl_discount_master ;
CLOSE c_dbuser;
END IF;
call procedure_name(xx,xx,xx,1);
how can i get the selected value using call procedure statement.
In addition to the other suggestion, you have this solution when you are getting exactly one row.
DECLARE
myvar1 mytable.mycolumn1%TYPE;
myvar2 mytable.mycolumn2%TYPE;
BEGIN
SELECT mycolumn1, mycolumn2
INTO myvar1, myvar2
FROM mytable
WHERE …;
END;
This will throw an exception if there is no selected row (NO_DATA_FOUND) or if there is more than one row (TOO_MANY_ROWS).
The difference between select and the insert/update/delete is that you need to select into some structure, either one or more variables or a rowtype variable.
Avoid explicit cursors whenever possible in favour of the faster, less verbose and less error prone implicit cursor.
eg.
for cur_my_query in
select column1,
column2,
...
from ...
where ...
loop
refer here to cur_my_query or my_query.column1 etc
end loop

PL/SQL compile conditionally on existence of database object

Is it possible to have conditional compilation in Oracle, where the condition is the existence of a database object (specifically, a table or view or synonym)? I'd like to be able to do something like this:
sp_some_procedure is
$IF /*check if A exists.*/ then
/* read from and write to A as well as other A-related non-DML stuff...*/
$ELSE /*A doesn't exist yet, so avoid compiler errors*/
dbms_output.put_line('Reminder: ask DBA to create A!')
$ENDIF
end;
Yes it is. Here a sample where the first stored procedure wants to select from XALL_TABLES, but if this table doesn't exist, select from dual. Finally, because I haven't got an XALL_TABLES object, the first stored procedure selects from dual. The second one, does the same thing on the ALL_TABLES object. Because the ALL_TABLES exists, the second stored procedure selects from all_tables but not from DUAL.
This kind of construction is useful where the package have to be deployed on all your database and use tables that are not deployed everywhere ... (ok, perhaps there is a conceptual problem, but it happens).
--conditionals compilation instructions accept only static condition (just with constants)
--passing sql bind variable doesn't work
--To pass a value to a conditional compilation instruction, I bypasses the use of input parameters of the script
--these 4 next lines affect a value to the first and the second input parameter of the script
--If your originally script use input script parameter, use the next free parameter ...
column param_1 new_value 1 noprint
select nvl(max(1), 0) param_1 from all_views where owner = 'SYS' and view_name = 'XALL_TABLES';
column param_2 new_value 2 noprint
select nvl(max(1), 0) param_2 from all_views where owner = 'SYS' and view_name = 'ALL_TABLES';
CREATE or replace PACKAGE my_pkg AS
function test_xall_tables return varchar2;
function test_all_tables return varchar2;
END my_pkg;
/
CREATE or replace PACKAGE BODY my_pkg AS
function test_xall_tables return varchar2 is
vch varchar2(50);
begin
$IF (&1 = 0) $THEN
select 'VIEW XALL_TABLES D''ONT EXISTS' into vch from dual;
$ELSE
select max('VIEW XALL_TABLES EXISTS') into vch from XALL_TABLES;
$END
return vch;
end test_xall_tables;
function test_all_tables return varchar2 is
vch varchar2(50);
begin
$IF (&2 = 0) $THEN
select 'VIEW ALL_TABLES D''ONT EXISTS' into vch from dual;
$ELSE
select max('VIEW ALL_TABLES EXISTS') into vch from ALL_TABLES;
$END
return vch;
end test_all_tables;
END my_pkg;
/
the test :
select my_pkg.test_xall_tables from dual;
give
VIEW XALL_TABLES D'ONT EXISTS
select my_pkg.test_all_tables from dual;
give
VIEW ALL_TABLES EXISTS
I would use 'EXECUTE IMMEDIATE' and a EXCEPTION clause.
Use dynamic SQL to create package constants to track which objects exist, and then use those constants in conditional compilation.
--E.g., say there are two possible tables, but only one of them exists.
--create table table1(a number);
create table table2(a number);
--Create a package with boolean constants to track the objects.
--(Another way to do this is to use ALTER SESSION SET PLSQL_CCFLAGS)
declare
table1_exists_string varchar2(10) := 'true';
table2_exists_string varchar2(10) := 'true';
temp number;
begin
begin
execute immediate 'select max(1) from table1 where rownum <= 1' into temp;
exception when others then
table1_exists_string := 'false';
end;
begin
execute immediate 'select max(1) from table2 where rownum <= 1' into temp;
exception when others then
table2_exists_string := 'false';
end;
execute immediate '
create or replace package objects is
table1_exists constant boolean := '||table1_exists_string||';
table2_exists constant boolean := '||table2_exists_string||';
end;
';
end;
/
--Look at the results in the source:
select * from user_source where name = 'OBJECTS';
--Create the object that refers to the tables.
create or replace function compile_test return varchar2 is
v_test number;
begin
$if objects.table1_exists $then
select max(1) into v_test from table1;
return 'table1 exists';
$elsif objects.table2_exists $then
select max(1) into v_test from table2;
return 'table 2 exists';
$else
return 'neither table exists';
$end
end;
/
--Check the dependencies - only TABLE2 is dependent.
select * from user_dependencies where name = 'COMPILE_TEST';
--Returns 'table 2 exists'.
select compile_test from dual;
Mixing dynamic SQL, dynamic PL/SQL, and conditional compilation is usually a very evil idea. But it will allow you to put all of your ugly dynamic SQL in one installation package, and maintain real dependency tracking.
This may work well in a semi-dynamic environment; for example a program that is installed with different sets of objects but does not frequently change between them.
(Also, if the whole point of this is just to replace scary error messages with friendly warnings, in my opinion that is a very bad idea. If your system is going to fail, the failure should be obvious so it can be immediately fixed. Most people ignore anything that starts with "Reminder...".)
No - that is not possible... but if you create a stored procedure referencing a non-existent DB object and try to compile it the compilation will show errors... the stored procedure will be there but "invalid"... and the compilation errors are accessible for the DBA whenever he looks at it... so I would just go ahead and create all needed stored procedures, if any compilation errors arise ask the DBA (sometimes the object exists but the stored procedure need permissions to access it...)... after the reason for the error(s) is fixed you can just recompile the stored procedure (via ALTER PROCEDURE MySchema.MyProcName COMPILE;) and all is fine...
IF you don't want code to be there you can just DROP the strored procedure and/or replace is via CREATE OR REPLACE... with dbms_output.put_line('Reminder: ask DBA to create A!') in the body.
The only other alternative is as kevin points out EXECUTE IMMEDIATE with proper EXCEPTION handling...
What I would do is check the existence via all_objects, something like:
declare
l_check_sql varchar2(4000);
l_cnt number;
begin
l_check_sql := q'{
select count(1)
from all_objects
where object_name = 'MY_OBJ'
and owner = 'MY_OWNER'
}';
execute immediate l_check_sql into l_cnt;
if (l_cnt > 0) then
-- do something referring to MY_OBJ
else
-- don't refer to MY_OBJ
end if;
end;

Reasonable SELECT ... INTO Oracle solution for case of multiple OR no rows

I just want to SELECT values into variables from inside a procedure.
SELECT blah1,blah2 INTO var1_,var2_
FROM ...
Sometimes a large complex query will have no rows sometimes it will have more than one -- both cases lead to exceptions. I would love to replace the exception behavior with implicit behavior similiar to:
No rows = no value change, Multiple rows = use last
I can constrain the result set easily enough for the "multiple rows" case but "no rows" is much more difficult for situations where you can't use an aggregate function in the SELECT.
Is there any special workarounds or suggestions? Looking to avoid significantly rewriting queries or executing twice to get a rowcount before executing SELECT INTO.
Whats wrong with using an exception block?
create or replace
procedure p(v_job VARCHAR2) IS
v_ename VARCHAR2(255);
begin
select ename into v_ename
from (
select ename
from scott.emp
where job = v_job
order by v_ename desc )
where rownum = 1;
DBMS_OUTPUT.PUT_LINE('Found Rows Logic Here -> Found ' || v_ename);
EXCEPTION WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('No Rows found logic here');
end;
SQL> begin
p('FOO');
p('CLERK');
end; 2 3 4
5 /
No Rows found logic here
Found Rows Logic Here -> Found SMITH
PL/SQL procedure successfully completed.
SQL>
You could use a for loop. A for loop would do nothing for no rows returned and would be applied to every row returned if there where multiples. You could adjust your select so that it only returns the last row.
begin
for ARow in (select *
from tableA ta
Where ta.value = ???) loop
-- do something to ARow
end loop;
end;

Resources