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.
Related
I am trying to get my last 5 employees (ones with lowest salary) and raise their salary by 5%;
I am using a varray to store their id's but i don't know how to use those ids in a update statement (something like update employees \ set salary = salary * 1.05 \ where id_employee in varray)
here's what i have for now:
DECLARE
TYPE tip_cod IS VARRAY(20) OF NUMBER;
coduri tip_cod;
BEGIN
SELECT employee_id
BULK COLLECT INTO coduri
FROM (
SELECT employee_id
from employees
where commission_pct IS NULL
order by salary asc
)
WHERE ROWNUM < 6;
-- after i store their ids in coduri i want to update their salary
FOR i IN 1 .. coduri.COUNT LOOP
DBMS_OUTPUT.PUT_LINE(coduri(i));
END LOOP;
END;
/
If you are practicing the use of loops to do things one at a time (not a good approach for this task!) you can replace your calls to put_line with insert statements, something like
...
update employees set salary = 1.05 * salary where employee_id = coduri(i);
...
The beauty of PL/SQL is that you can embed such plain-SQL statements directly within PL/SQL code, no need for preparation of any kind.
After you are done with the updates, you will need to commit for the changes to be committed - usually after the procedure, not within it.
Alternatively, if you want a single update (with an in condition), you will need to define the varray table at the schema level, not within the anonymous block (or procedure). This is because the update statement is a SQL statement, which can't "see" locally defined data types. Then, in the update statement you will need to use the table operator to unwind its members. Something like this:
create type tip_cod is varray(20) of number;
/
DECLARE
coduri tip_cod;
BEGIN
SELECT employee_id
BULK COLLECT INTO coduri
FROM (
SELECT employee_id
from employees
where commission_pct IS NULL
order by salary asc
)
WHERE ROWNUM < 6;
update employees set salary = 1.05 * salary
where employee_id in (select * from table(coduri));
END;
/
commit;
Notice how the varray type is defined on its own, then it is used in the PL/SQL block. Also don't forget the commit at the end.
When you work with collection types, there is also the member of predicate, as in employee_id member of coduri. Alas, this only works with locally-defined data types; since the varray type must be declared at the schema level (so that it can be used in a SQL statement within the PL/SQL code), you can't use member of and you must unwind the array explicitly, with the table operator.
There id much more to collections (Oracle term for array). There are 3 types:
Varrays
Associative Arrays
Nested Tables
If you want to understand collections you must understand all 3. (imho: Of the 3 Varrays are the most limited).
Mathguy presents 1 option, "casting" the array as a table, via the TABLE(...) function. I'll present another: Nested Table combined with Bulk Collect/Forall combination to accomplish he update.
declare
type employee_id_att is table of hr.employees.employee_id%type;
employee_id_array employee_id_att;
begin
select employee_id
bulk collect
into employee_id_array
from hr.employees
where commission_pct is null
order by salary
fetch first 5 rows only;
forall emp_indx in 1 .. employee_id_array.count
update hr.employees
set salary = 1.05 * salary
where employee_id = employee_id_array(emp_indx);
end ;
/
Take Away: There is much, much more to collections than defining a LOOP. Spend some time with the documentation and write tests and examine the results. But the important thing when you do not understand write some code. It will probably fail, that is good, so write something else. Do not be afraid of errors/ exceptions, in development they are friend. And if there is something you cannot understand then post a specific question. Be prepared to show several failed attempts; that will give the community an idea of your thinking and whether you are on the correct path or not.
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
I have function which returns column names and i am trying to use the column name as part of my select statement, but my results are coming as column name instead of values
FUNCTION returning column name:
get_col_name(input1, input2)
Can И use this query to the results of the column from table -
SELECT GET_COL_NAME(input1,input2) FROM TABLE;
There are a few ways to run dynamic SQL directly inside a SQL statement. These techniques should be avoided since they are usually complicated, slow, and buggy. Before you do this try to find another way to solve the problem.
The below solution uses DBMS_XMLGEN.GETXML to produce XML from a dynamically created SQL statement, and then uses XML table processing to extract the value.
This is the simplest way to run dynamic SQL in SQL, and it only requires built-in packages. The main limitation is that the number and type of columns is still fixed. If you need a function that returns an unknown number of columns you'll need something more powerful, like the open source program Method4. But that level of dynamic code gets even more difficult and should only be used after careful consideration.
Sample schema
--drop table table1;
create table table1(a number, b number);
insert into table1 values(1, 2);
commit;
Function that returns column name
create or replace function get_col_name(input1 number, input2 number) return varchar2 is
begin
if input1 = 0 then
return 'a';
else
return 'b';
end if;
end;
/
Sample query and result
select dynamic_column
from
(
select xmltype(dbms_xmlgen.getxml('
select '||get_col_name(0,0)||' dynamic_column from table1'
)) xml_results
from dual
)
cross join
xmltable
(
'/ROWSET/ROW'
passing xml_results
columns dynamic_column varchar2(4000) path 'DYNAMIC_COLUMN'
);
DYNAMIC_COLUMN
--------------
1
If you change the inputs to the function the new value is 2 from column B. Use this SQL Fiddle to test the code.
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
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