I have a problem at one exercise: I have a table with an ussualy column, and another column which is another table, like this:
CREATE OR REPLACE TYPE list_firstnames AS TABLE OF VARCHAR2(10);
then I create the following table:
CREATE TABLE persons (last_name VARCHAR2(10), first_name list_firstnames)
NESTED TABLE first_name STORE AS lista;
I insert:
INSERT INTO persons VALUES('Stewart', list_firstnames('John', 'Jack'));
INSERT INTO persons VALUES('Bauer', list_firstnames('Helen', 'Audrey'));
INSERT INTO persons VALUES('Obrian', list_firstnames('Mike', 'Logan'));
I want to make a cursor to take all last and first names from persons, and then I want to put all of them in an array. After this, I want to count the first names which contains the 'n' letter.
First of all, I want to know how I can put all the information from the cursor in an array. I try this:
DECLARE
CURSOR firstnames_students IS
select last_name,COLUMN_VALUE as "FIRSTNAME" from persons p, TABLE(p.last_name) p2;
v_lastname VARCHAR(50);
v_firstname VARCHAR(50);
v_I NUMBER := 1;
v_count NUMBER := 0;
v_contor INTEGER := 0;
TYPE MyTab IS TABLE OF persons%ROWTYPE INDEX BY VARCHAR2(20);
std MyTab;
BEGIN
OPEN firstnames_students;
LOOP
FETCH firstnames_students INTO v_lastname, v_firstname;
EXIT WHEN firstnames_students%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(v_lastname || ' ' || v_firstname);
END LOOP;
--select * INTO std FROM firstnames_students;
CLOSE firstnames_students;
END;
SET SERVEROUTPUT ON;
DECLARE
CURSOR students IS
SELECT * FROM persons;
v_row PERSONS%ROWTYPE;
v_names SYS.ODCIVARCHAR2LIST := SYS.ODCIVARCHAR2LIST();
v_count INT := 0;
BEGIN
OPEN students;
LOOP
FETCH students INTO v_row;
EXIT WHEN students%NOTFOUND;
v_names.EXTEND;
v_names(v_names.COUNT) := v_row.last_name;
FOR i IN 1 .. v_row.first_name.COUNT LOOP
v_names.EXTEND;
v_names(v_names.COUNT) := v_row.first_name(i);
END LOOP;
END LOOP;
CLOSE students;
FOR i IN 1 .. v_names.COUNT LOOP
IF INSTR( v_names(i), 'n' ) > 0 THEN
v_count := v_count + 1;
DBMS_OUTPUT.PUT_LINE( v_names(i) );
END IF;
END LOOP;
DBMS_OUTPUT.PUT_LINE( v_count );
END;
Output:
John
Helen
Obrian
Logan
4
Related
I wanted to select 10 highly paid employees from the "employees" table, but the function returnes only 1 row. How to get multiple rows in this case? My subquery for selecting employees works well, but when I call function it returns 1 row.
This is my code:
CREATE OR REPLACE FUNCTION f_sal
RETURN Varchar2
IS cursor c_emp is (select first_name, last_name from (select first_name, last_name, row_number()
over(order by salary desc) as ranking from employees) where ranking <= 10);
v_first employees.first_name%type;
v_last employees.last_name%type;
begin
open c_emp;
fetch c_emp into v_first, v_last;
close c_emp;
return v_first || ' ' || v_last;
EXCEPTION
WHEN NO_DATA_FOUND
THEN
dbms_output.put_line('Error');
when others then dbms_output.put_line('Other Error');
END;
select f_sal from dual;
Option 1: Use a collection
CREATE FUNCTION f_sal
RETURN SYS.ODCIVARCHAR2LIST
IS
v_names SYS.ODCIVARCHAR2LIST;
BEGIN
SELECT first_name || ' ' || last_name
BULK COLLECT INTO v_names
FROM employees
ORDER BY salary DESC
FETCH FIRST 10 ROWS ONLY;
return v_names;
END;
/
Then:
SELECT * FROM TABLE(f_sal);
Option 2: Use a pipelined function and iterate over a cursor into a collection
CREATE OR REPLACE FUNCTION f_sal
RETURN SYS.ODCIVARCHAR2LIST PIPELINED
IS
BEGIN
FOR n IN (
SELECT first_name || ' ' || last_name AS name
FROM employees
ORDER BY salary DESC
FETCH FIRST 10 ROWS ONLY
)
LOOP
PIPE ROW (n.name);
END LOOP;
END;
/
Then:
SELECT * FROM TABLE(f_sal);
Option 3: Return a cursor
CREATE FUNCTION f_sal
RETURN SYS_REFCURSOR
IS
v_names SYS_REFCURSOR;
BEGIN
OPEN v_names FOR
SELECT first_name || ' ' || last_name AS name
FROM employees
ORDER BY salary DESC
FETCH FIRST 10 ROWS ONLY;
return v_names;
END;
/
Then:
DECLARE
v_names SYS_REFCURSOR := f_sal();
v_name VARCHAR2(100);
BEGIN
LOOP
FETCH v_names INTO v_name;
EXIT WHEN v_names%NOTFOUND;
DBMS_OUTPUT.PUT_LINE( v_name );
END LOOP;
END;
/
db<>fiddle here
I am having a problem while concatenating the varchar2 datatype in a cursor loop.
Procedure is iterating in a loop to build the in clause for insert and delete operations in batch.The process will run in batch for every 1000 account numbers.
For small amount of records it works but when it tries to concatenate large amount of records(36451477 in temp table) in a loop it throws.
java.sql.SQLException: ORA-06502: PL/SQL: numeric or value error:
character string buffer too small ORA-06512: at
"QA01BT.LOAD_ITEM_DATA_TO_CONSOLIDATE", line 23 ORA-06512: at line 1
i have put a max limit of search id to 32767 but still it does not work.
is there any other way to achieve this?
create or replace PROCEDURE LOAD_ITEM_DATA_TO_CONSOLIDATE(updatecount OUT NUMBER
)
IS
cnt NUMBER := 0;
c_limit CONSTANT PLS_INTEGER DEFAULT 1000;
search_id varchar2(32727);
TYPE account_array
IS TABLE OF VARCHAR2(255) INDEX BY BINARY_INTEGER;
l_data ACCOUNT_ARRAY;
CURSOR account_cursor IS
SELECT DISTINCT account_no AS account_num
FROM item_temp;
BEGIN
OPEN account_cursor;
LOOP
FETCH account_cursor bulk collect INTO l_data limit c_limit;
search_id := '''';
FOR i IN 1 .. l_data.count LOOP
IF( i != 1 ) THEN
search_id := search_id
|| ','
|| ''''
|| l_data(i)
|| '''';
ELSE
search_id := search_id
|| l_data(i)
|| '''';
END IF;
END LOOP;
BEGIN
SAVEPOINT move_data_to_temp_table;
EXECUTE IMMEDIATE 'delete from item where ACCOUNT_NO IN('||search_id||')';
EXECUTE IMMEDIATE 'insert into item(ID,ACCOUNT_NO,ITEM_ID,ITEM_VALUE) select HIBERNATE_SEQUENCE.nextval,temp.ACCOUNT_NO,temp.ITEM_ID,temp.ITEM_VALUE from item_TEMP temp where ACCOUNT_NO IN('||search_id||')';
cnt := cnt + SQL%rowcount;
COMMIT;
EXCEPTION WHEN OTHERS THEN ROLLBACK to move_data_to_temp_table;
END;
EXIT WHEN account_cursor%NOTFOUND;
END LOOP;
updatecount := cnt;
CLOSE account_cursor;
END LOAD_ITEM_DATA_TO_CONSOLIDATE;
This seems somewhat over-engineered. Why not just this?
create or replace PROCEDURE LOAD_ITEM_DATA_TO_CONSOLIDATE
(updatecount OUT NUMBER)
IS
BEGIN
delete from item
where ACCOUNT_NO IN ( SELECT account_no
FROM item_temp);
insert into item(ID,ACCOUNT_NO,ITEM_ID,ITEM_VALUE)
select HIBERNATE_SEQUENCE.nextval, temp.ACCOUNT_NO, temp.ITEM_ID, temp.ITEM_VALUE
from item_TEMP temp ;
updatecount := SQL%rowcount;
END LOAD_ITEM_DATA_TO_CONSOLIDATE;
If you do decide you need to do this in batches and are worried about that string getting too long or having too many elements in the list (max is 1000), you should try putting your values into an array and then using IN against the array, via a table function or a direct reference to the table.
Extra bonus: no need for dynamic SQL!
Something like this:
CREATE OR REPLACE TYPE strings_t IS TABLE OF VARCHAR2 (255)
/
CREATE OR REPLACE PROCEDURE load_item_data_to_consolidate (
updatecount OUT NUMBER)
IS
cnt NUMBER := 0;
c_limit CONSTANT PLS_INTEGER DEFAULT 1000;
l_data strings_t;
CURSOR account_cursor
IS
SELECT DISTINCT account_no AS account_num FROM item_temp;
BEGIN
OPEN account_cursor;
LOOP
FETCH account_cursor BULK COLLECT INTO l_data LIMIT c_limit;
BEGIN
SAVEPOINT move_data_to_temp_table;
DELETE FROM item
WHERE account_no IN (SELECT COLUMN_VALUE FROM TABLE (l_data));
INSERT INTO item (id,
account_no,
item_id,
item_value)
SELECT hibernate_sequence.NEXTVAL,
temp.account_no,
temp.item_id,
temp.item_value
FROM item_temp temp
WHERE account_no IN (SELECT COLUMN_VALUE FROM TABLE (l_data));
cnt := cnt + SQL%ROWCOUNT;
COMMIT;
EXCEPTION
WHEN OTHERS
THEN
ROLLBACK TO move_data_to_temp_table;
END;
EXIT WHEN account_cursor%NOTFOUND;
END LOOP;
END;
I am new in pl / sql , I want to enter this data in the insert someone help me please?
I do not know how to enter this data in the insert without making a join
I can not find the way to enter the data in the insert
these SELECT PORCENTAJE
FROM RANGO_SUBIDA_COMISION;
VARIABLE B_ANIO NUMBER;
EXECUTE :B_ANIO := 042018;
DECLARE
V_EMPLEADO_ID EMPLEADOS.EMPLEADO_ID%TYPE;
V_NOMBRE EMPLEADOS.NOMBRE%TYPE;
V_APELLIDO EMPLEADOS.APELLIDO%TYPE;
V_VALOR_COMISION COMISIONES.VALOR_COMISION%TYPE;
V_SUELDO EMPLEADOS.SUELDO%TYPE;
BEGIN
FOR R IN
(
SELECT DISTINCT E.EMPLEADO_ID , E.NOMBRE , E.APELLIDO , C.VALOR_COMISION , E.SUELDO
FROM EMPLEADOS E JOIN COMISIONES C
ON (C.EMPLEADO_ID = E.EMPLEADO_ID)
)
LOOP
V_EMPLEADO_ID := R.EMPLEADO_ID;
V_NOMBRE := R.NOMBRE;
V_APELLIDO := R.APELLIDO;
V_VALOR_COMISION := R.VALOR_COMISION;
V_SUELDO := R.SUELDO;
INSERT INTO INFORME_SUBIDA_COMISION
VALUES ( :B_ANIO ,V_EMPLEADO_ID , V_NOMBRE , V_APELLIDO , V_VALOR_COMISION , V_SUELDO );
END LOOP;
END;
I'm not quite sure what your question is. But I don't understand why you are using a cursor for this. Why not just execute a query like this?
INSERT INTO INFORME_SUBIDA_COMISION (ANIO, EMPLEADO_ID, NOMBRE, APELLIDO, VALOR_COMISION, SUELDO)
SELECT DISTINCT :B_ANIO, E.EMPLEADO_ID, E.NOMBRE, E.APELLIDO,
C.VALOR_COMISION, E.SUELDO
FROM EMPLEADOS E JOIN COMISIONES C
ON C.EMPLEADO_ID = E.EMPLEADO_ID;
Note: I'm guessing what the column names are in INFORME_SUBIDA_COMISION, but you should explicitly list them.
you can use this insert in Bulk Mode if you have huge data to insert
SET SERVEROUTPUT ON
/*use this script to insert data in bulk mode.*/
DECLARE
TYPE array IS
TABLE OF informe_subida_comision%rowtype;
v_data array;
dml_errors EXCEPTION;
PRAGMA exception_init ( dml_errors,-24381 );
CURSOR l_cur IS
SELECT DISTINCT
e.empleado_id,
e.nombre,
e.apellido,
c.valor_comision,
e.sueldo
FROM
empleados e
JOIN comisiones c ON (
c.empleado_id = e.empleado_id
);
v_error_count NUMBER;
v_err VARCHAR2(500);
v_err_indx NUMBER;
BEGIN
OPEN l_cur;
LOOP
BEGIN
FETCH l_cur BULK COLLECT INTO v_data LIMIT 1000;
FORALL i IN 1..v_data.count SAVE EXCEPTIONS
--insert data into the table
INSERT INTO informe_subida_comision VALUES v_data ( i );
-- log any dml errors
EXCEPTION
WHEN dml_errors THEN
v_error_count := SQL%bulk_exceptions.count;
FOR i IN 1..v_error_count LOOP
v_err_indx := SQL%bulk_exceptions(i).error_index;
v_err := sqlerrm(-SQL%bulk_exceptions(i).error_code);
dbms_output.put_line(v_data(v_err_indx).empleado_id
|| ''
|| v_err);
END LOOP;
END;
COMMIT;
EXIT WHEN l_cur%notfound;
END LOOP;
CLOSE l_cur;
END;
create table Employee(Id number,
Name varchar(20),
Age number,
DId number,
Salary number,
primary key(Id),
foreign key(DId) references Department on delete cascade);
declare
total number;
procedure myFunction(x in number) is
begin
insert into Employee values(17,'Jaskaran Singh',31,1,200000);
dbms_output.put_line('successfully executed');
select * from Employee;
end;
begin
myFunction(3);
end;
To return data from stored procedure, you should create a cursor and return that select like the following:
CREATE OR REPLACE PACKAGE TYPES
AS
TYPE DATA_CURSOR IS REF CURSOR;
END;
then in your code is going to be like:
CREATE OR REPLACE PROCEDURE MYPROC(RESULTSET OUT TYPES.DATA_CURSOR) AS
BEGIN
INSERT INTO EMPLOYEE VALUES(17,'Jaskaran Singh',31,1,200000);
DBMS_OUTPUT.PUT_LINE('successfully executed');
OPEN RESULTSET FOR
SELECT * FROM EMPLOYEE;
END;
The Execution part like
DECLARE THE_RESULT_SET OUT TYPES.DATA_CURSOR;
BEGIN
MYPROC(3, THE_RESULT_SET);
-- You can now get the THE_RESULT_SET and take the result from it...
END;
Important: if you want to print as I understand the other case, you can get that result (same code), and loop whatever you want and print the result from the THE_RESULT_SET
If you want to print what's in the EMPLOYEES table you have loop a cursor over the EMPLOYEES table, printing each row appropriately. Here's an example:
DECLARE
TOTAL NUMBER;
PROCEDURE MYFUNCTION(X IN NUMBER) IS
BEGIN
INSERT INTO EMPLOYEE VALUES(17,'Jaskaran Singh',31,1,200000);
DBMS_OUTPUT.PUT_LINE('successfully executed');
FOR aRow IN (SELECT * FROM EMPLOYEE) LOOP
DBMS_OUTPUT.PUT_LINE('ID=' || aRow.ID ||
' NAME=''' || aRow.NAME || '''' ||
' AGE=' || aRow.AGE ||
' DID=' || aRow.DID ||
' SALARY=' || aRow.SALARY);
END LOOP;
END;
BEGIN
MYFUNCTION(3);
END;
Share and enjoy.
I give the SQL few inputs and I need to get all the ID's and their count that doesn't satisfy the required criteria.
I would like to know if there are there any alternatives to using cursor.
DECLARE
v_count INTEGER;
v_output VARCHAR2 (1000);
pc table1%ROWTYPE;
unmarked_ids EXCEPTION;
dynamic_sql VARCHAR (5000);
cur SYS_REFCURSOR;
id pp.id%TYPE;
pos INTEGER;
BEGIN
v_count := 0;
SELECT *
INTO pc
FROM table1
WHERE id = '&ID';
DBMS_OUTPUT.ENABLE;
dynamic_sql :=
'SELECT ID from pp
WHERE ( TO_CHAR(cdate, ''yyyy/mm/dd'') =
TO_CHAR (:a, ''yyyy/mm/dd''))
AND aid IN (SELECT aid FROM ppd WHERE TO_CHAR(cdate, ''yyyy/mm/dd'') =
TO_CHAR (:b, ''yyyy/mm/dd'')
AND cid = :c )
AND cid <> :d';
OPEN cur FOR dynamic_sql USING pc.cdate, pc.cdate, pc.id, pc.id;
LOOP
FETCH cur INTO id;
EXIT WHEN cur%NOTFOUND;
v_count := v_count + 1;
DBMS_OUTPUT.PUT_LINE (' Id:' || id);
END LOOP;
CLOSE cur;
IF (v_count > 0)
THEN
DBMS_OUTPUT.PUT_LINE ( 'Count: ' || v_count || ' SQL: ' || dynamic_sql);
RAISE unmarked_ids;
END IF;
DBMS_OUTPUT.PUT_LINE('SQL ended successfully');
EXCEPTION
WHEN unmarked_ids
THEN
DBMS_OUTPUT.put_line (
'Found ID's that not marked with the current id.');
WHEN NO_DATA_FOUND
THEN
DBMS_OUTPUT.put_line (
'No data found in table1 with the current id ' || '&ID');
END;
There are bind variables in the query. One of them is date, there are three more.
The count and ID's are required to be shown which will later be reported.
You could store the rowid in a temporary table along with an index value (0...n) and then use a while loop to go through the index values and join to the real table using the rowid.