In my Pl/SQL procedure, I have a request that select PL/SQL Objects and bulk collect them into a Table.
Here is the declaration of the object and table types:
CREATE OR REPLACE TYPE TypPerson AS OBJECT(
NoSeq NUMBER(9),
Name VARCHAR2(50),
Age NUMBER(22),
constructor function TypPerson return self as RESULT
);
CREATE OR REPLACE TYPE TypTblPerson as table of TypPerson;
And here is the statements form my procedure
req := 'SELECT TypPerson(a.num, a.name, b.age)
FROM tab1 a inner join tab2 b ON condition';
EXECUTE IMMEDIATE req BULK COLLECT INTO tblPersons;
This code works fine, but i want to sort the results by a field (of TypPerson) name, and i dont know how to do it.
If I try this
req := 'SELECT TypPerson(a.num, a.name, b.age)
FROM tab1 a inner join tab2 b ON condition
ORDER BY NoSeq';
I get the error ORA-00904 invalid identifier.
So I want to know how can I do this sort. My procedure receive the name of the field to order by as an input parameter.
May be it is easier to sort the tblPersons resulting from the request? In this case how can I do it?
Thank you
Related
This is PL/SQL in a 12c database.
I need to be able to pass a cursor record to a function. The problem is that the cursor is from three different tables and one of them must use a * to select all of the (hundreds) of fields in that table.
It works if I select distinct fields (from a smaller test table) and it works if I use a select * from one table and no other fields (no other tables involved), but I can't find any way to make this work when selecting from three tables (examples only show two) and using a * (to select all fields) from one of the tables.
I've tried FETCH INTO and SELECT INTO with a pre-defined cursor. I've tried using a cursor record with a FOR myRecord IN (SELECT a.*, ...) and
creating a matching object (since records can't be created at the schema level)
These work
myVarA a%ROWTYPE;
SELECT a.* INTO myVarA
FROM a;
SELECT a.myAfield, b.myBfield INTO myVarA, myVarB
FROM a, b;
But I need this to work:
myVarA a%ROWTYPE;
SELECT a.*, b.myBfield INTO myVarA, myVarB
FROM a, b;
Using a cursor record
myRecord myObjectTypeWithAllFields; -- ojbect, not record as record can't be declared at schema level
FOR myRecord IN (SELECT a.*, b.myBfield FROM a, b);
newVar := myFunction(myRecord); -- this won't work
fails on the function call with PLS-00306: wrong number or types of arguments in call to myFunction;
The function will do a lot of grunt work that is similar in many packages, but there is also a large amount of unique work
in each package, so I can't just process the entire cursor loop in the function. I really need to pass one row at a time to the function.
Is there a way to do this?
You can define a cursor in a package - whether or not you will actually use it - with the same select list:
create package p as
cursor c is select a.*, b.myBField
from a, b; -- but use proper join syntax
end;
/
Then define your function argument using that cursor's %rowtype:
create function myFunction(p_record p.c%rowtype) return ... as ...
Then your block will work:
FOR myRecord IN (SELECT a.*, b.myBfield FROM a, b) LOOP
newVar := myFunction(myRecord);
END LOOP;
db<>fiddle demo
The cursor for loop could then use the cursor defined in the package if you wanted it to.
Incidentally, in your object version, the variable declaration:
myRecord myObjectTypeWithAllFields;
is redundant; the myRecord in FOR myRecord... is completely independent. So it doesn't even attempt to use the object type.
You are using a %rowtype anchor so I am assuming an exact fetch. Oracle does not allow to use of multiple records with "into" clauses. But we can have a workaround on that.
DECLARE
emprec EMPLOYEES%ROWTYPE;
deptname DEPARTMENTS.department_name%type;
BEGIN
select a.* into emprec
from EMPLOYEES a
WHERE EMPLOYEE_ID = 100;
SELECT DEPARTMENT_NAME into deptname
from DEPARTMENTS where DEPARTMENT_ID = emprec.DEPARTMENT_ID;
END;
This example shows how you can fetch your a.* in a record and have inner join with another table and have b.myBfield into another record.
And once again these are records so I am assuming it is an exact fetch.
If it is not what you want let me know in the comment.
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
Is it possible to select from a bulk collection?
Something along these lines:
DECLARE
CURSOR customer_cur IS
SELECT CustomerId,
CustomerName
FROM Customers
WHERE CustomerAreaCode = '576';
TYPE customer_table IS TABLE OF customer_cur%ROWTYPE;
my_customers customer_table;
BEGIN
OPEN customer_cur;
FETCH customer_cur
BULK COLLECT INTO my_customers;
-- This is what I would like to do
SELECT CustomerName
FROM my_customers
WHERE CustomerId IN (1, 2, 3);
END;
I don't seem to be able to select from the my_customers table.
Yes, you can. Declare yourself schema-level types as follows:
create or replace rec_customer_cur
as
object (
customerid integer, -- change to the actual type of customers.customerid
customername varchar2(100) -- change to the actual type of customers.customername
);
/
create or replace type customer_table
as
table of rec_customer_cur;
/
Then, in your PLSQL code, you can declare
CURSOR customer_cur IS
SELECT new rec_customer_cur(CustomerId, CustomerName)
FROM Customers
WHERE CustomerAreaCode = '576';
... and then use ...
SELECT CustomerName
INTO whatever
FROM table(my_customers)
WHERE CustomerId IN (1, 2, 3);
This is because schema-level types can be used in SQL context.
If you want to also display the dataset returned by the select, then just use a REF CURSOR as an OUT parameter.
The SELECT ...FROM TABLE is a SQL statement, which needs a STATIC TABLE NAME, as a database object. It throws an error since the collection name is not actually a database table as an object.
To return the dataset, use SYS_REFCURSOR as OUT parameter.
open cur as select....
I have the following table, two types based on it, and a function that reads from this table:
CREATE TABLE myTable (
ID RAW(16) NULL,
NAME NVARCHAR2(200) NULL,
ENTITYID RAW(16) NOT NULL
);
CREATE TYPE myRowType AS OBJECT (
NAME NVARCHAR2(200),
ENTITYID RAW(16)
);
CREATE TYPE myTableType IS TABLE OF myRowType;
CREATE FUNCTION myFunction(...) RETURN myTableType ...
As you can see, the type myRowType is similar to myTable, but not exactly.
My goal is to insert rows into myTable based on the results of myFunction.
The naive approach would be to just write:
INSERT INTO myTable(ID, NAME, ENTITYID)
SELECT sys_guid(), NAME, ENTITYID
FROM TABLE(myFunction(...));
But since myFunction reads from myTable, this leads to the following error:
ORA-04091: table myTable is mutating, trigger/function may not see it
So I have to split the myFunction call from the insert statement. I tried it like this:
DECLARE
tbl myTableType;
BEGIN
SELECT myRowType(x.NAME, x.ENTITYID)
BULK COLLECT INTO tbl
FROM TABLE(myFunction(...)) x;
INSERT INTO myTable
(ID, NAME, ENTITYID)
SELECT sys_guid(), x.NAME, x.ENTITYID
FROM tbl x;
END;
But here, Oracle doesn't seem to understand the FROM tbl clause. It shows the error
ORA-00942: table or view does not exist
How can I insert the rows in tbl into myTable?
Since you can't use a locally defined nested table as an argument for TABLE function, maybe you would consider using the FORALL bulk insert? I see you are using Oracle 11g, so you will be able to access fields of myRowType. You would then replace your INSERT from your PL/SQL block with this:
FORALL v_i IN tbl.FIRST..tbl.LAST
INSERT INTO myTable VALUES (sys_guid(), tbl(v_i).name, tbl(v_i).entityid);
I recommend this great article by Tim Hall: BULK COLLECT & FORALL
I have a stored procedure that is doing a two-step query. The first step is to gather a list of VARCHAR2 type characters from a table and collect them into a table variable, defined like this:
TYPE t_cids IS TABLE OF VARCHAR2(50) INDEX BY PLS_INTEGER;
v_cids t_cids;
So basically I have:
SELECT item BULK COLLECT INTO v_cids FROM table_one;
This works fine up until the next bit.
Now I want to use that collection in the where clause of another query within the same procedure, like so:
SELECT * FROM table_two WHERE cid IN v_cids;
Is there a way to do this? I am able to select an individual element, but I would like to use the table variable like a would use a regular table. I've tried variations using nested selects, but that doesn't seem to work either.
Thanks a lot,
Zach
You have several choices as to how you achieve this.
If you want to use a collection, then you can use the TABLE function to select from it but the type of collection you use becomes important.
for a brief example, this creates a database type that is a table of numbers:
CREATE TYPE number_tab AS TABLE OF NUMBER
/
Type created.
The next block then populates the collection and performs a rudimentary select from it using it as a table and joining it to the EMP table (with some output so you can see what's happening):
DECLARE
-- Create a variable and initialise it
v_num_tab number_tab := number_tab();
--
-- This is a collection for showing the output
TYPE v_emp_tabtype IS TABLE OF emp%ROWTYPE
INDEX BY PLS_INTEGER;
v_emp_tab v_emp_tabtype;
BEGIN
-- Populate the number_tab collection
v_num_tab.extend(2);
v_num_tab(1) := 7788;
v_num_tab(2) := 7902;
--
-- Show output to prove it is populated
FOR i IN 1 .. v_num_tab.COUNT
LOOP
dbms_output.put_line(v_num_tab(i));
END LOOP;
--
-- Perform a select using the collection as a table
SELECT e.*
BULK COLLECT INTO v_emp_tab
FROM emp e
INNER JOIN TABLE(v_num_tab) nt
ON (e.empno = nt.column_value);
--
-- Display the select output
FOR i IN 1 .. v_emp_tab.COUNT
LOOP
dbms_output.put_line(v_emp_tab(i).empno||' is a '||v_emp_tab(i).job);
END LOOP;
END;
You can see from this that the database TYPE collection (number_tab) was treated as a table and could be used as such.
Another option would be to simply join your two tables you are selecting from in your example:
SELECT tt.*
FROM table_two tt
INNER JOIN table_one to
ON (to.item = tt.cid);
There are other ways of doing this but the first might suit your needs best.
Hope this helps.
--Doesn't work.
--SELECT item BULK COLLECT AS 'mySelectedItems' INTO v_cids FROM table_one;
SELECT table_two.*
FROM table_two INNER JOIN v_cids
ON table_two.paramname = v_cids.mySelectedItems;
Unless I'm misunderstanding the question, this should only return results that are in the table variable.
Note: I've never used Oracle, but I imagine this case would be the same.