Declaring and using variables in PL-SQL - oracle

I am new to PL-SQL. I do not understand why I am getting the error "PLS-00428: an INTO clause is expected in this SELECT statement"
What I'm trying to accomplish is to create a variable c_limit and load it's value. I then want to use that variable later to filter data.
Basically I am playing around in the demo db to see what I can/can't do with PL-SQL.
The code worked up to the point that I added "select * from demo_orders where CUSTOMER_ID = custID;"
declare
c_limit NUMBER(9,2);
custID INT;
BEGIN
custID := 6;
-- Save the credit limit
select credit_limit INTO c_limit
from demo_customers cust
where customer_id = custID;
select * from demo_orders where CUSTOMER_ID = custID;
dbms_output.Put_line(c_limit);
END;

If you are using a SQL SELECT statement within an anonymous block (in PL/SQL - between the BEGIN and the END keywords) you must select INTO something so that PL/SQL can utilize a variable to hold your result from the query. It is important to note here that if you are selecting multiple columns, (which you are by "SELECT *"), you must specify multiple variables or a record to insert the results of your query into.
for example:
SELECT 1
INTO v_dummy
FROM dual;
SELECT 1, 2
INTO v_dummy, v_dummy2
FROM dual;
It is also worth pointing out that if your SELECT * FROM.... will return multiple rows, PL/SQL will throw an error. You should only expect to retrieve 1 row of data from a SELECT INTO.

Looks like the error is from the second select query.
select * from demo_orders where CUSTOMER_ID = custID;
PL-SQL won't allow a standalone sql select query for info.
http://pls-00428.ora-code.com/
You need to do some operation with the second select query

Related

INTO error in SELECT while creating stored procedure in PL/SQL

I am trying to create stored procedure where I want to join two tables and save the result into one of the tables, but I am getting INTO clause is required error.
This is my code:
CREATE PROCEDURE DiorItemMaster
AS
SELECT *FROM pcdo_dior_item_master
INNER JOIN pcdo_itemdata on pcdo_itemdata.vpn = pcdo_dior_item_master.vpn;
GO;
ERROR:
Error(4,1): PLS-00428: an INTO clause is expected in this SELECT statement
You have a query that selects columns from a table in your pl/sql block. What do you want to do with the result of that query ? You cannot just select and not do anything with the results in pl/sql (you can in sql). Oracle expects that you to store the results of that select in variables. That can be done using the SELECT INTO clause.
Example (based on sample schema emp/dept):
DECLARE
l_emp emp%ROWTYPE;
BEGIN
SELECT e.* INTO l_emp FROM emp e WHERE e.ename = 'KING';
END;
/
Note that you can SELECT INTO individual columns and into rows. You cannot use SELECT INTO arrays.
A couple of other remarks about your code:
You perform a SELECT * from a table with a join to another table without using aliases. This will return all columns from both tables. It is a lot more readable to prefix the "*" with a table alias like in the example.
The GO; is not part of the oracle syntax - this will cause a compilation error.

Oracle access varray elements in SQL

I'm playing around with array support in Oracle and hit a roadblock regarding array access within a SQL query. I'm using the following schema:
create type smallintarray as varray(10) of number(3,0);
create table tbl (
id number(19,0) not null,
the_array smallintarray,
primary key (id)
);
What I would like to do is get the id and the first element i.e. at index 1 of the array. In PostgreSQL I could write select id, the_array[1] from tbl t but I don't see how I could do that with Oracle. I read that array access by index is only possible in PL/SQL, which would be fine if I could return a "decorated cursor" to achieve the same result through JDBC, but I don't know if that's possible.
DECLARE
c1 SYS_REFCURSOR;
varr smallintarray2;
BEGIN
OPEN c1 FOR SELECT t.id, t.THE_ARRAY from tbl t;
-- SELECT t.THE_ARRAY INTO varr FROM table_with_enum_arrays2 t;
-- return a "decorated cursor" with varr(1) at select item position 1
dbms_sql.return_result(c1);
END;
You can do this in plain SQL; it's not pretty, but it does work. You would prefer that Oracle had syntax to hide this from the programmer (and perhaps it does, at least in the most recent versions; I am still stuck at 12.2).
select t.id, q.array_element
from tbl t cross apply
( select column_value as array_element,
rownum as ord
from table(the_array)
) q
where ord = 1
;
EDIT If order of generating the elements through the table operator is a concern, you could do something like this (in Oracle 12.1 and higher; otherwise the function can't be part of the query itself, but it can be defined on its own):
with
function select_element(arr smallintarray, i integer)
return number
as
begin
return arr(i);
end;
select id, select_element(the_array, 1) as the_array_1
from tbl
/
First of all, please don't do that on production. Use tables instead of storing arrays within a table.
Answer to your question is to use column as a table source
SELECT t.id, ta.*
from tbl t,
table(t.THE_ARRAY) ta
order by column_value
-- offset 1 row -- in case if sometime you'll need to skip a row
fetch first 1 row only;
UPD: as for ordering the array I can only say playing with 2asc/desc" parameters provided me with results I've expected - it has been ordered ascending or descending.
UPD2: found a cool link to description of performance issues might happen

PLSQL - Parameter cursor with different return

I know the title is saying nothing... but the argument is a little "complex" to explain in a single row.
All the code i'm writing is just an example, my current code is from other table etc, but the behaviour is the same.
i have defined a cursor like this:
CURSOR emp_cur (l_type)
IS
with emp_general AS (select *
from emp
where type = l_type),
emp_active AS (select *
from emp_geral
where status = ACTIVE_STATUS),
emp_inactive AS (select *
from emp_general
where status = INACTIVE_STATUS)
select distinct name, department
from emp_active
minus
select distinct name, department
from emp_inactive;
This cursor take a parameter for filter emp type and make a minus to fetch ACTIVE - INACTIVE emp.
This cursor return name and department.
Now have to declare different cursor with different "select" statemant, for example:
select location
from emp_active
select location
from emp_active
I would like to dont duplicate my cursor just to change the select. There is a way to do this and avoid code duplication (withuout using DynamicSQL - Difficult to debug in production enviroment)?
You could create two Global temporary tables explicitly once(not on the fly):
emp_active_gtt
emp_inactive_gtt
Such that, each temp table will have the entire result set of active and inactive records respectively.
For example, in the code you would do:
insert into emp_active_gtt
select *
from ....
where status ='ACTIVE'
Similarly, for inactive records:
insert into emp_inactive_gtt
select *
from ....
where status ='INACTIVE'
You could now use the two tables in the scope of the session anywhere to get the required rows.
Read more about GTT in the documentation here https://docs.oracle.com/database/121/SQLRF/statements_7002.htm#i2153132

Update or insert based on if employee exist in table

Do want to create Stored procc which updates or inserts into table based on the condition if current line does not exist in table?
This is what I have come up with so far:
PROCEDURE SP_UPDATE_EMPLOYEE
(
SSN VARCHAR2,
NAME VARCHAR2
)
AS
BEGIN
IF EXISTS(SELECT * FROM tblEMPLOYEE a where a.ssn = SSN)
--what ? just carry on to else
ELSE
INSERT INTO pb_mifid (ssn, NAME)
VALUES (SSN, NAME);
END;
Is this the way to achieve this?
This is quite a common pattern. Depending on what version of Oracle you are running, you could use the merge statement (I am not sure what version it appeared in).
create table test_merge (id integer, c2 varchar2(255));
create unique index test_merge_idx1 on test_merge(id);
merge into test_merge t
using (select 1 id, 'foobar' c2 from dual) s
on (t.id = s.id)
when matched then update set c2 = s.c2
when not matched then insert (id, c2)
values (s.id, s.c2);
Merge is intended to merge data from a source table, but you can fake it for individual rows by selecting the data from dual.
If you cannot use merge, then optimize for the most common case. Will the proc usually not find a record and need to insert it, or will it usually need to update an existing record?
If inserting will be most common, code such as the following is probably best:
begin
insert into t (columns)
values ()
exception
when dup_val_on_index then
update t set cols = values
end;
If update is the most common, then turn the procedure around:
begin
update t set cols = values;
if sql%rowcount = 0 then
-- nothing was updated, so the record doesn't exist, insert it.
insert into t (columns)
values ();
end if;
end;
You should not issue a select to check for the row and make the decision based on the result - that means you will always need to run two SQL statements, when you can get away with one most of the time (or always if you use merge). The less SQL statements you use, the better your code will perform.
BEGIN
INSERT INTO pb_mifid (ssn, NAME)
select SSN, NAME from dual
where not exists(SELECT * FROM tblEMPLOYEE a where a.ssn = SSN);
END;
UPDATE:
Attention, you should name your parameter p_ssn(distinguish to the column SSN ), and the query become:
INSERT INTO pb_mifid (ssn, NAME)
select P_SSN, NAME from dual
where not exists(SELECT * FROM tblEMPLOYEE a where a.ssn = P_SSN);
because this allways exists:
SELECT * FROM tblEMPLOYEE a where a.ssn = SSN

PL/SQL procedure - too many values

I'm sure this is something simple, but I'm really new to PL/SQL and this has me stuck.
I've written a simple stored procedure to return a few values about a customer. Right off the bat, the %rowtype's are not coming up as reserved keywords but the compiler isn't flagging those as errors.
It is, however, ignoring the entire SQL statement flagging the line FROM demo_customers as too many values. Even if I try reducing it to only select one column it still gives me the same error.
create or replace
PROCEDURE GETCUSTOMER
(
arg_customerID demo_customers.customer_id%type,
returnRec OUT demo_customers%rowtype
)
AS
BEGIN
SELECT customer_id, cust_first_name, cust_last_name, cust_email
INTO returnRec
FROM demo_customers
WHERE customer_id = arg_customerID ;
END GETCUSTOMER;
If you want to select into a %ROWTYPE record, you'll want to do a SELECT * rather than selecting individual columns
create or replace
PROCEDURE GETCUSTOMER
(
arg_customerID demo_customers.customer_id%type,
returnRec OUT demo_customers%rowtype
)
AS
BEGIN
SELECT *
INTO returnRec
FROM demo_customers
WHERE customer_id = arg_customerID ;
END GETCUSTOMER;
If you select 4 columns explicitly, Oracle expects you to have 4 variables to select those values into.

Resources