oracle PL/SQL stored function ORA-01422 and ORA-06512 - oracle

I want to write a PL/SQL stored function that takes a driver's employee number as a parameter and returns the driver's full name, cities he visited and how many times he visited the city as a nested table.
I wrote the function and it was compiled successfully. Here is the code for the nested table:
CREATE OR REPLACE TYPE C_V
AS OBJECT
( FULLNAME VARCHAR(150),
CITIES_VISITED VARCHAR(30),
TOT_VISITS NUMBER(3)
);
CREATE OR REPLACE TYPE D_V_C
IS TABLE OF C_V;
Here is the function:
CREATE OR REPLACE FUNCTION DRIVERVISITEDCITIES ( D_E# NUMBER)
RETURN D_V_C
IS
D_FULLNAME VARCHAR(150);
CITIES_VISITED_BY VARCHAR (30);
TOTAL_VISITS NUMBER(3);
CITY_VIS_DETAIL D_V_C := D_V_C();
BEGIN
CITY_VIS_DETAIL.EXTEND();
SELECT DISTINCT EMPLOYEE.FNAME || EMPLOYEE.INITIALS || EMPLOYEE.LNAME AS FULLNAME,
UPPER(TRIPLEG.DESTINATION),
COUNT(TRIPLEG.DESTINATION)
INTO
D_FULLNAME,
CITIES_VISITED_BY,
TOTAL_VISITS
FROM EMPLOYEE
INNER JOIN DRIVER
ON DRIVER.E# = EMPLOYEE.E#
INNER JOIN TRIP
ON TRIP.L# = DRIVER.L#
INNER JOIN TRIPLEG
ON TRIPLEG.T# = TRIP.T#
WHERE EMPLOYEE.E# = D_E#
GROUP BY EMPLOYEE.FNAME||EMPLOYEE.INITIALS||EMPLOYEE.LNAME, TRIPLEG.DESTINATION
ORDER BY COUNT(TRIPLEG.DESTINATION) DESC;
RETURN CITY_VIS_DETAIL;
END;
However, when I tried to test the function it shows:
Error starting at line 1 in command:
SELECT DRIVERVISITEDCITIES(1) FROM DUAL
Error report:
SQL Error: ORA-01422: exact fetch returns more than requested number of rows
ORA-06512: at "SYS.DRIVERVISITEDCITIES", line 13
01422. 00000 - "exact fetch returns more than requested number of rows"
*Cause: The number specified in exact fetch is less than the rows returned.
*Action: Rewrite the query or change number of rows requested
Could anyone help me with this?

You have defined a collection variable but you're not populating it. Instead you're selecting into scalar variables. Clearly your query returns more than one row (because one driver has been on more than one trip) and that's why you're getting TOO_MANY_ROWS exception.
You need to select into that collection. Easiest way is with BULK COLLECT:
SELECT DISTINCT EMPLOYEE.FNAME || EMPLOYEE.INITIALS || EMPLOYEE.LNAME AS FULLNAME,
UPPER(TRIPLEG.DESTINATION),
COUNT(TRIPLEG.DESTINATION)
bulk collect into city_vis_detail -- populate the collection like this
D_FULLNAME,
CITIES_VISITED_BY,
TOTAL_VISITS
FROM EMPLOYEE
INNER JOIN DRIVER
ON DRIVER.E# = EMPLOYEE.E#
INNER JOIN TRIP
ON TRIP.L# = DRIVER.L#
INNER JOIN TRIPLEG
ON TRIPLEG.T# = TRIP.T#
WHERE EMPLOYEE.E# = D_E#
GROUP BY EMPLOYEE.FNAME||EMPLOYEE.INITIALS||EMPLOYEE.LNAME, TRIPLEG.DESTINATION
ORDER BY COUNT(TRIPLEG.DESTINATION) DESC;

SELECT DISTINCT EMPLOYEE.FNAME || EMPLOYEE.INITIALS || EMPLOYEE.LNAME AS FULLNAME,
UPPER(TRIPLEG.DESTINATION),
COUNT(TRIPLEG.DESTINATION)
INTO
D_FULLNAME,
CITIES_VISITED_BY,
TOTAL_VISITS
FROM EMPLOYEE
Your query is returning the multiple value and your storing it into scalar variable. replace scalar variable with your object type variable and use bulk collect.

Related

How to define a parameter that will be used as an IN argument inside a function?

I want to make a function that receives two parameters. The first one represents the salaries from a group of employees, and second one, the codes from a group of departments. Both, P_IN_SALARIES and P_IN_DEPARTMENTS_CODES parameters, will be used as
arguments in an IN function of a query, just as demonstrated in the code below:
CREATE OR REPLACE FUNCTION public.get_employees_id(P_IN_SALARIES WHICH_TYPE_COMES_HERE, P_IN_DEPARTMENTS_CODES WHICH_TYPE_COMES_HERE)
RETURNS text
LANGUAGE plpgsql
AS $function$
declare
v_employees_ids text;
begin
select STRING_AGG(employee.id || '', ',') into v_employees_ids
from employee
inner join departament on department.id = employee.departament_id
where employee.salary in (P_IN_SALARIES)
and department.code in (P_IN_DEPARTMENTS_CODES);
RETURN v_employees_ids;
END;
$function$
What is the type of a IN parameter in a SQL statement?
Is there a generic one that I might use in order to allow a kind of portability on an occasional database exchange (e.g. to Oracle)?
How to call this function in a hibernate query?
In Oracle, you can use a collection data type:
CREATE TABLE number_list IS TABLE OF NUMBER;
Then you can use the MEMBER OF operator rather than IN:
CREATE FUNCTION public.get_employees_id(
P_IN_SALARIES IN number_list,
P_IN_DEPARTMENTS_CODES IN number_list
) RETURNS VARCHAR2
IS
v_employees_ids VARCHAR2(4000);
BEGIN
SELECT LISTAGG( id, ',' ) WITHIN GROUP ( ORDER BY id )
INTO v_employees_ids
FROM employee e
inner join departament d
on ( d.id = e.departament_id )
WHERE e.salary MEMBER OF P_IN_SALARIES
AND d.department.code MEMBER OF P_IN_DEPARTMENTS_CODES;
RETURN v_employees_ids;
END;
/

ORA-00947 not enough values with function returning table of records

So I'm trying to build a function that returns the records of items that are included in some client subscription.
So I've been building up the following:
2 types:
CREATE OR REPLACE TYPE PGM_ROW AS OBJECT
(
pID NUMBER(10),
pName VARCHAR2(300)
);
CREATE OR REPLACE TYPE PGM_TAB AS TABLE OF PGM_ROW;
1 function:
CREATE OR REPLACE FUNCTION FLOGIN (USER_ID NUMBER) RETURN PGM_TAB
AS
SELECTED_PGM PGM_TAB;
BEGIN
FOR RESTRICTION
IN ( SELECT (SELECT LISTAGG (ID_CHANNEL, ',')
WITHIN GROUP (ORDER BY ID_CHANNEL)
FROM (SELECT DISTINCT CHA2.ID_CHANNEL
FROM CHANNELS_ACCESSES CHA2
JOIN CHANNELS CH2
ON CH2.ID = CHA2.ID_CHANNEL
WHERE CHA2.ID_ACCESS = CMPA.ID_ACCESS
AND CH2.ID_CHANNELS_GROUP = CG.ID))
AS channels,
(SELECT LISTAGG (ID_SUBGENRE, ',')
WITHIN GROUP (ORDER BY ID_SUBGENRE)
FROM (SELECT DISTINCT SGA2.ID_SUBGENRE
FROM SUBGENRES_ACCESSES SGA2
JOIN CHANNELS_ACCESSES CHA2
ON CHA2.ID_ACCESS = SGA2.ID_ACCESS
JOIN CHANNELS CH2
ON CH2.ID = CHA2.ID_CHANNEL
WHERE SGA2.ID_ACCESS = CMPA.ID_ACCESS
AND CH2.ID_CHANNELS_GROUP = CG.ID))
AS subgenres,
CG.NAME,
A.BEGIN_DATE,
A.END_DATE,
CMP.PREVIEW_ACCESS
FROM USERS U
JOIN COMPANIES_ACCESSES CMPA
ON U.ID_COMPANY = CMPA.ID_COMPANY
JOIN COMPANIES CMP ON CMP.ID = CMPA.ID_COMPANY
JOIN ACCESSES A ON A.ID = CMPA.ID_ACCESS
JOIN CHANNELS_ACCESSES CHA
ON CHA.ID_ACCESS = CMPA.ID_ACCESS
JOIN SUBGENRES_ACCESSES SGA
ON SGA.ID_ACCESS = CMPA.ID_ACCESS
JOIN CHANNELS CH ON CH.ID = CHA.ID_CHANNEL
JOIN CHANNELS_GROUPS CG ON CG.ID = CH.ID_CHANNELS_GROUP
WHERE U.ID = USER_ID
GROUP BY CG.NAME,
A.BEGIN_DATE,
A.END_DATE,
CMPA.ID_ACCESS,
CG.ID,
CMP.PREVIEW_ACCESS)
LOOP
SELECT PFT.ID_PROGRAM, PFT.LOCAL_TITLE
BULK COLLECT INTO SELECTED_PGM
FROM PROGRAMS_FT PFT
WHERE PFT.ID_CHANNEL IN
( SELECT TO_NUMBER (
REGEXP_SUBSTR (RESTRICTION.CHANNELS,
'[^,]+',
1,
ROWNUM))
FROM DUAL
CONNECT BY LEVEL <=
TO_NUMBER (
REGEXP_COUNT (RESTRICTION.CHANNELS,
'[^,]+')))
AND PFT.ID_SUBGENRE IN
( SELECT TO_NUMBER (
REGEXP_SUBSTR (RESTRICTION.SUBGENRES,
'[^,]+',
1,
ROWNUM))
FROM DUAL
CONNECT BY LEVEL <=
TO_NUMBER (
REGEXP_COUNT (RESTRICTION.SUBGENRES,
'[^,]+')))
AND (PFT.LAUNCH_DATE BETWEEN RESTRICTION.BEGIN_DATE
AND RESTRICTION.END_DATE);
END LOOP;
RETURN SELECTED_PGM;
END FLOGIN;
I expect the function tu return a table with 2 columns containing all the records from table PROGRAMS_FT that are included in the user access.
For some reason, I'm getting compilation warning ORA-000947.
My understanding of the error code is that it occurs when the values inserted does not match the type of the object receiving the values, and I can't see how this can be the case here.
You're selecting two scalar values and trying to put them into an object. That doesn't happen automatically, you need to convert them to an object:
...
LOOP
SELECT PGM_ROW(PFT.ID_PROGRAM, PFT.LOCAL_TITLE)
BULK COLLECT INTO SELECTED_PGM
FROM PROGRAMS_FT PFT
...
(It's an unhelpful quirk of PL/SQL that it says 'not enough values' rather than 'too many values', as you might expect when you try to put two things into one; I'm sure I came up with a fairly convincing explanation/excuse for that once but it escapes me at the moment...)
I'm not sure your loop makes sense though. Assuming your cursor query returns multiple rows, each time around the loop you're replacing the contents of the SELECTED_PGM collection - you might think you are appending to it, but that's not how it works. So you will end up returning a collection based only on the final iteration of the loop.
Aggregating and then splitting the data seems like a lot of work too. You could maybe use collections for those; but you can probably get rid of the cursor and loop and combine the cursor query with the inner query, which would be more efficient and would allow you to do a single bulk-collect for all the combined data.

Write an Oracle procedure

I must create an oracle procedure to display a list of persons (parlimentaries) with an index for tuples.
For now, I wrote this piece of code (I haven't implemented the index)
create or replace procedure parlamentarieslist as
begin
select
ssn,
name,
surname,
from
parlimentaries p,
mandate m
where
p.ssn = m.parlamentaries AND m.legislature= (select
max(legislature) "m"
from mandate);
end parlamentarieslist;
However, oracle give me these errors
Error(5,3): PL/SQL: SQL Statement ignored
Error(12,3): PL/SQL: ORA-00936: missing expression
Why?
As I mentioned before in the comment part, the problem is due to
the missing INTO clause
existing typo(comma) after surname column in the uppermost select list.
Mostly, Procedures are used to return one column or single row and in results of SELECT statements may be returned to the output parameters by INTO clause. But, If you want to return list of persons (multi-rows), the following style may be more suitable :
SQL> set serveroutput on;
SQL> create or replace procedure parlamentarieslist as
begin
for c in
(
select p.ssn, p.name, p.surname,
max(m.legislature) over (partition by p.ssn ) m
from parlimentaries p inner join mandate m
on ( p.ssn = m.parlamentaries )
order by m.legislature desc
)
loop
dbms_output.put_line(' SSN : '||c.ssn||' Name : '||c.name||' Surname : '||c.surname);
end loop;
end parlamentarieslist;
/
SQL> exec parlamentarieslist;
Where Use a SQL of explict ANSI JOIN style, instead of old-fashioned comma seperated JOIN style.

PL/SQL: ORA-30485: missing ORDER BY expression in the window specification

I am compiling this Function but getting ERROR : missing ORDER BY expression in the window specification
CREATE OR REPLACE FUNCTION WFir_get_act_section_cd(firnum IN NUMBER,langcd IN NUMBER) RETURN VARCHAR2
as
ACTSEC VARCHAR2(1500);
BEGIN
begin
--- ERROR START
select ltrim(max(sys_connect_by_path(NVL(act_long,' ') || '/' || NVL(section,' '),',')),',') as FIR_ACT_SEC into ACTSEC from(select NVL(act_long,' ') || '/' || NVL(section,' '), row_number() over() rn from rep_fir_sections sec
INNER JOIN m_act a on sec.act_cd = a.act_cd
INNER JOIN m_section c on sec.section_cd = c.section_code
and FIR_REG_NUM = FIRNum
and a.lang_cd = langcd) WHERE ROWNUM <=1 start with rn = 1
connect by prior rn = rn -1;
EXCEPTION
WHEN NO_DATA_FOUND THEN
NULL;
end;
return ACTSEC;
END;
When we are compiling above function after migrate this function form MYSQL to Oracle i am getting error "missing ORDER BY expression in the window specification"
I don't know why i am getting this ERROR, Please help me to resolve this ERROR
Row_number() over () is incorrect.
order by is REQUIRED for row_number to work.
Row_number() over (order by Null /*or you decide what field list*/) .
To assign a row_number you have to specify order by some column. The system doesn't know what row to assign to #1 if you don't specify an order for the system to put them into. Even order by null or order by 1 should work; but you'll probably want a specific field or fields to order by.
Put another way, order by isn't optional on a Row_number window function.
Doc Link
"...
ROW_NUMBER is an analytic function. It assigns a unique number to each row to which it is applied (either each row in the partition or each row returned by the query), in the ordered sequence of rows specified in the order_by_clause, beginning with 1. ..."
This implies without an order by, no row number can be assigned.

How to count the number of elements in all Oracle varrays from table?

I have a table like this:
CREATE TABLE spatial_data (
id NUMBER PRIMARY KEY,
geometry SDO_GEOMETRY);
SDO_GEOMETRY has a field sdo_ordinates with the following type:
TYPE SDO_ORDINATE_ARRAY AS VARRAY(1048576) OF NUMBER
I can get the number of points for specified object:
select count(*)
from table(
select s.geometry.sdo_ordinates
from spatial_data s
where s.id = 12345
);
How can I get count for several objects? It's not possible to use
where s.id in (1, 2, 3, 4, 5)
And I really care about performance. Maybe PL/SQL would be the right choice?
I think that you can do it with one query:
select s.id, count(*)
from spatial_data s, table(s.geometry.sdo_ordinates)
group by s.id
or you can write a plsql simple function that returns the count attribute of the SDO_ORDINATE_ARRAY VARRAY:
create or replace function get_count(ar in SDO_ORDINATE_ARRAY) return number is
begin
return ar.count;
end get_count;
or even nicer add a member function to SDO_GEOMETRY TYPE which return the count attribute

Resources