Executing the PL/SQL block gives an error which is not understandable - oracle

The question given was to write a PL/SQL block to print the details of the customers whose total order quantity is greater than 200 where the Customer table had ID number(5) primary key, Name varchar2(20), Contact_No varchar2(10) and the Order table had Order_Id number(5) primary Key, Quantity number(4) not null, C_id number(5) references Customer(ID).
If no record is found "No Records Found" is to be printed out.
This is the Code I wrote:
SET SERVEROUTPUT ON;
begin
dbms_output.put_line('Customer_Id ' || 'Customer_Name '|| 'Customer_Phone');
for cur_d in (select o.C_ID,total AS sum(o.QUANTITY) from Orders o group by o.C_ID) loop
from Customers c
where c.ID = cur_d.C_ID and cur_d.total > 200;
dbms_output.put_line(c.ID || c.Name || c.Contact_No);
end loop;
end;
/
The error I faced was -
for cur_d in (select o.C_ID,total AS sum(o.QUANTITY) from Orders o group by o.C_ID) loop
ERROR at line 2:
ORA-06550: line 2, column 41:
PL/SQL: ORA-00923: FROM keyword not found where expected
ORA-06550: line 2, column 15:
PL/SQL: SQL Statement ignored
ORA-06550: line 3, column 1:
PLS-00103: Encountered the symbol "FROM" when expecting one of the following:
( begin case declare exit for goto if loop mod null pragma
raise return select update while with '<'an identifier'>'
'<'a double-quoted delimited-identifier'> ')

Even after you correct the substantial that have been pointed out your procedure will still fail. You indicate that if no record is found printing a message to that effect. That in itself is ambiguous. Does that mean for the no records in the data table or no records for a given customer? Either way you have no code to produce such a message. How do you expect it to be written. Finally, SQL is set based processing so you need to start thinking in terms of sets instead of loops. The following reduces the db access to a single query; the loop is only to print results.
begin
dbms_output.put_line('Customer_Id ' || 'Customer_Name '|| 'Customer_Phone' || 'Total Orders');
for cur_d in (
with order_totals as
( select c_id, sum (quantity) order_total
from orders
group by c_id
having sum(quantity) > 200
)
select c.id, c.name, c.contact_no
, case when o.c_id is null
then 'No Records Found'
else to_char(o.order_total)
end order_total
from customers c
left join order_totals o
on c.id = o.c_id
order by c.id
)
loop
dbms_output.put_line(cur_d.ID || cur_d.Name || cur_d.Contact_No || cur_d.order_total);
end loop;
end;
The results are jammed together just as you initially had them. You need to workout their presentation.

There is a "select into" part missing between "for .. loop" and "from" parts. It has to be like this in order to work
for ..... loop
select some_column -- <-- this line is missing
into some_variable -- <-- this line is missing too
from ..........

This is the kind of issue where formatting your code will make the problem obvious. If you always start a new line and indent after for xxx in ( and also place the closing bracket on its own line, and include some gaps between commands, you'll get this, which is clearly wrong:
begin
dbms_output.put_line('Customer_Id ' || 'Customer_Name '|| 'Customer_Phone');
for cur_d in (
select o.c_id, total as sum(o.quantity)
from orders o
group by o.c_id
)
loop
from customers c
where c.id = cur_d.c_id
and cur_d.total > 200;
dbms_output.put_line(c.id || c.name || c.contact_no);
end loop;
end;
The first statement inside the loop seems to be missing something, as ekochergin mentioned.
total as sum(o.quantity) is backwards as Turo mentioned.
If you want id, name and contact_no to be printed in columns, you should look at lpad and rpad for formatting them. Just concatenating them together will produce something unreadable.
The dbms_output inside the loop refers to c.id, c.name and c.contact_no, but the record is called cur_d, not c.
Also cur_d is a slightly confusing name for a record (it's not a cursor). I always use r for cursor records unless there is some other r involved that it could be confused with.

Related

Oracle Cursors : Invalid Identifier

I need to write code that increases the salary of an employee who is over 40 years old.
Here is my code:
DECLARE
CURSOR kurs IS SELECT ID_PRACOWNIKA , pensja_BR, wiek FROM PRACOWNICY p , OSOBY o ;
ID_PRACOWNIKA decimal(2):=0;
pensja DECIMAL(8,2);
wiek DECIMAL(2);
BEGIN
OPEN kurs;
LOOP
IF wiek > 40
THEN
UPDATE PRACOWNICY
SET pensja = PENSJA_BR * 1.02
WHERE ID_PRACOWNIKA = ID_PRACOWNIKA;
dbms_OUTPUT.put_line( ID_PRACOWNIKA|| '-'||pensja);
END IF;
ID_PRACOWNIKA := ID_PRACOWNIKA+1;
EXIT WHEN ID_PRACOWNIKA=6;
END LOOP;
CLOSE kurs;
END;
Unfortunately I have SQL error
SQL Error [6550] [65000]: ORA-06550: line 14, column 12:
PL/SQL: ORA-00904: "PENSJA": invalid identifier
ORA-06550: line 13, column 7:
PL/SQL: SQL Statement ignored
Osoby table strucuture:
Id_osoby NUMBER CONSTRAINT osoby_pk PRIMARY KEY,
Imie VARCHAR2(15) NOT NULL,
Nazwisko VARCHAR2(30) NOT NULL,
Wiek NUMBER NOT NULL CONSTRAINT ch_wiek CHECK((Wiek>=0) AND (Wiek<=125)),
Stan_cywilny VARCHAR2(12) NOT NULL,
Telefon VARCHAR2(20),
Pesel CHAR(11) NOT NULL CONSTRAINT osoba_uni UNIQUE,
Id_adresu NUMBER NOT NULL,
CONSTRAINT os_ad_fk FOREIGN KEY (Id_adresu) REFERENCES Adresy(Id_adresu)
Pracownicy table structure:
Id_pracownika NUMBER CONSTRAINT pracownik_pk PRIMARY KEY,
Id_osoby NUMBER NOT NULL CONSTRAINT pr_unique UNIQUE,
Id_stanowiska NUMBER NOT NULL,
Staz NUMBER NOT NULL CONSTRAINT ch_staz CHECK((Staz>=0) AND (Staz<=45)),
Pensja_br NUMBER NOT NULL CONSTRAINT pen_staz CHECK(Pensja_br>=1226),
CONSTRAINT pr_os_fk FOREIGN KEY (Id_osoby) REFERENCES Osoby(Id_osoby),
CONSTRAINT pr_st_fk FOREIGN KEY (Id_stanowiska) REFERENCES Stanowiska(Id_stanowiska)
I think you can use the single update statement but as you want to print the details also. You can go with FOR loop as follows:
BEGIN
FOR I IN (
SELECT P.ID_PRACOWNIKA,
P.PENSJA_BR * 1.02 AS PENSJA_BR
FROM PRACOWNICY P
JOIN OSOBY O
ON P.ID_OSOBY = O.ID_OSOBY
WHERE O.WIEK > 40
) LOOP
UPDATE PRACOWNICY P
SET P.PENSJA_BR = I.PENSJA_BR
WHERE P.ID_PRACOWNIKA = I.ID_PRACOWNIKA;
DBMS_OUTPUT.PUT_LINE(I.ID_PRACOWNIKA || '-' || I.PENSJA_BR);
END LOOP;
END;
If you just want to update the data with a single query then you can use the following update SQL:
UPDATE PRACOWNICY P
SET P.PENSJA_BR = P.PENSJA_BR * 1.02
WHERE EXISTS (
SELECT 1
FROM OSOBY O
WHERE P.ID_OSOBY = O.ID_OSOBY
AND O.WIEK > 40
);
I am not quite sure what the expected result would be but I hava question. If you already have the columns ID_PRACOWNIKA , pensja_BR, wiek retrieved from a table why do you create some variables with the same name?
Now, what I understood from your problem is that you try to change the PENSJA_BR Column to have the value of PENSJA_BR * 1.02 for each row which has the value >40 in the column WIEK.
I think that the following code might help you with your Problem. I have tested it within my testing environment and it updates the column PENSJA_BR accordingly. Nevertheless, you will have to update it to your needs and add what you need extra.
DECLARE
check_stauts NUMBER;
CURSOR kurs IS
SELECT id_pracownika, pensja_br, wiek
FROM pracownicy;
BEGIN
FOR i IN kurs LOOP
if i.wiek > 40 THEN
UPDATE pracownicy
SET
pensja_br = i.pensja_br * 1.02
WHERE
id_pracownika = i.id_pracownika;
END IF;
END LOOP;
END;
PS: when trying to assign a value to a variable use:
pensja := PESNJA_BR * 1.02
I believe it's UPDATE clause causing the issue (and the error description say the error is on line 14 of your script)
SET pensja = PENSJA_BR * 1.02
As I see in the beginning, there is a column named pensja_BR in the table PRACOWNICY but you are trying to update the "pensija" column. Which presumably does not exist in the table.
Another things to mention here is there is a cartesian join in your cursor because you're joining two tables without any join/where conditions
UPD: the loop will probably not work here as well because you did not fetched data from. Opening a cursor will not fetch data automatically. You have to either fetch it explicitly every time in the loop
open kurs;
loop
fetch kurs into some_variable
...
end loop;
or to use another for..loop statement in order to loop through the cursor
for k in kurs loop
...
end loop;
So, you need to update cursor definition with following where clause
WHERE p.id_osoby = o.id_osoby and wiek > 40
Then remove the IF > 40 statement, you don't need it anymore.
Declare a rowtype variable in DECLARE:
kurs_l kurs%rowtype;
in PLSQL:
open kurs;
loop
fetch kurs into kurs_l;
exit when kurs%notfound;
... do your stuff ...
end loop;
close kurs;

Find value by any column in table

I have columns in table something like
answeremail,
answertime,
ata,
atacomment,
ataid,
atainternalnumber,
atanumber,
atatype,
author,
becomeatafromdeviation,
becomeexternalatafrominternal,
becomefastfromothertype,
briefdescription,
city,
client_answer_attachment,
clientcomment,
confirmstatus,
created_at,
deviation,
deviationnumber,
deviationtype,
duedate,
emailsent,
financeid,
forfortnox,
fromfortnox,
is_deleted,
locked,
name,
parentata,
paymenttype,
pdfurl,
projectid,
quantity,
reason,
revisiondate,
startdate,
status,
street,
suggestion,
token,
type,
unit,
userid,
zip
I want to create SELECT statment to retrive some of this column but without specify column name something like this.
SELECT * FROM ata WHERE 'field' = 'argument'
Is there any solution for this problem or either I need to specify all those column in SELECT statment ?
Not (just) in SELECT, but in WHERE. Otherwise, how will query know which columns to check?
But - beware of datatypes. Oracle will try to implicitly convert one datatype to another. Sometimes, it'll succeed (e.g. to convert number 1 to string '1'), sometimes it'll fail (e.g. convert string 'A' to number or string 'AB23F' to date value).
Therefore, although you can try to write query which will write query for you, it might take some time to actually make it work properly. PL/SQL is probably what you'll end up with.
An example which checks all tables in my schema that contain a column named PHONE_NUMBER and searches for a row whose phone number contains 654. This script returns tables that contain such a value; you'd return something else - list of columns? All columns? Can't tell.
That's just a starting point. Good luck!
DECLARE
l_str VARCHAR2(500);
l_cnt NUMBER := 0;
BEGIN
FOR cur_r IN (SELECT u.table_name, u.column_name
FROM user_tab_columns u, user_tables t
WHERE u.table_name = t.table_name
AND u.column_name = 'PHONE_NUMBER'
)
LOOP
l_str := 'SELECT COUNT(*) FROM ' || cur_r.table_name ||
' WHERE ' || cur_r.column_name || ' like (''%654%'')';
EXECUTE IMMEDIATE (l_str) INTO l_cnt;
IF l_cnt > 0 THEN
dbms_output.put_line(l_cnt ||' : ' || cur_r.table_name);
END IF;
END LOOP;
END;
There is one short solution using xml:
https://dbfiddle.uk/?rdbms=oracle_18&fiddle=e5efc164b77a55b2ecf5d4bf85d589a5
select/*+ no_xml_query_rewrite */ *
from
xmltable(
'for $row in ora:view("SAMPLE_DATA")
let $col := $row/ROW/*[text() eq $search]
where $col ne ""
return element R {
attribute X {$col},
$row
}'
passing
'BB' as "search"
columns
found_col varchar2(30) path '#X'
,row_data xmltype path '.'
);
Results:
FOUND_COL ROW_DATA
------------------------------ ----------------------------------------------------------------------------------------------------
BB <R X="BB"><ROW><COLA>10A</COLA><COLB>BB</COLB><COLC>00</COLC></ROW></R>
BB <R X="BB"><ROW><COLA>10A</COLA><COLB>BB</COLB><COLC>10</COLC></ROW></R>
BB <R X="BB"><ROW><COLA>10A</COLA><COLB>BB</COLB><COLC>20</COLC></ROW></R>
BB <R X="BB"><ROW><COLA>10A</COLA><COLB>BB</COLB><COLC>30</COLC></ROW></R>
BB <R X="BB"><ROW><COLA>10A</COLA><COLB>BB</COLB><COLC>40</COLC></ROW></R>
From your description, I think you want to retrive the columns which spicified by the where clause only, and to omit the other columns.
I don't think this is allowed by simple sql statement. In the oracle document, at the select_list section, I can't see any solution for this question.
see https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/SELECT.html#GUID-CFA006CA-6FF1-4972-821E-6996142A51C6
If you really want, you can use pl/sql.

Why is the output only the last value? Oracle loop cursor

I'm trying to output a list of the courses a professor teaches, by receiving the prof's id by parameter to my function, and showing all courses, each separated by a comma. For example, if a Professor teaches Humanities, Science and Math, I want the output to be: 'Humanities, Science, Math'. However, I'm getting just 'Math,'. It only shows the last field that it found that matched with the prof's id.
CREATE OR REPLACE FUNCTION listar_cursos(prof NUMBER) RETURN VARCHAR
IS
CURSOR C1 IS
SELECT subject.name AS name FROM subject
INNER JOIN course_semester
ON subject.id = course_semester.id_subject
WHERE course_semester.id_profesor = prof
ORDER BY subject.name;
test VARCHAR(500);
BEGIN
FOR item IN C1
LOOP
test:= item.name ||',';
END LOOP;
RETURN test;
END;
/
I am aware that listagg exists, however I do not wish to use it.
In your loop, you re-assign to the test variable, instead of appending to it. This is why, at the end of the loop, it will just hold the last value of item.name.
The assignment should instead be something like
test := test || ',' || item.name
Note also that this will leave a comma at the beginning of the string. Instead of returning test, you may want to return ltrim(test, ',').
Note that you don't need to declare a cursor explicitly. The code is easier to read (in my opinion) with an implicit cursor, as shown below. I create sample tables and data to test the function, then I show the function code and how it's used.
create table subject as
select 1 id, 'Humanities' name from dual union all
select 2 , 'Science' from dual union all
select 3 , 'Math' from dual
;
create table course_semester as
select 1 id_subject, 201801 semester, 1002 as id_profesor from dual union all
select 2 , 201702 , 1002 as id_profesor from dual union all
select 3 , 201801 , 1002 as id_profesor from dual
;
CREATE OR REPLACE FUNCTION listar_cursos(prof NUMBER) RETURN VARCHAR IS
test VARCHAR(500);
BEGIN
FOR item IN
(
SELECT subject.name AS name FROM subject
INNER JOIN course_semester
ON subject.id = course_semester.id_subject
WHERE course_semester.id_profesor = prof
ORDER BY subject.name
)
LOOP
test:= test || ',' || item.name;
END LOOP;
RETURN ltrim(test, ',');
END;
/
select listar_cursos(1002) from dual;
LISTAR_CURSOS(1002)
-----------------------
Humanities,Math,Science

Error code ORA-06550 - Trouble concatenating strings in Oracle

I am making a table that contains the earnings for each month.
To do that I am using a for loop with an insert statement inside.
What I am having trouble with is converting the number into a month then into a char.
This is what my code looks like:
BEGIN
FOR i IN 1..12
LOOP
INSERT INTO REVENUE ( TO_CHAR(TO_DATE(i, 'MM'), 'MON') || '2009'
, select sum(transaction_amount)
but when I run this I get an error saying:
INSERT INTO REVENUE ( TO_CHAR(TO_DATE(i, 'MM'), 'MON') || '2009'
*
ERROR at line 4:
ORA-06550: line 4, column 31:
PL/SQL: ORA-00917: missing comma
What am I doing wrong here?
go-oleg is right, it isn't the concatenation that's the problem, it's that your syntax for the insert statement is wrong. You are missing the values keyword from the values clause:
BEGIN
FOR i IN 1..12
LOOP
INSERT INTO REVENUE VALUES ( TO_CHAR(TO_DATE(i, 'MM'), 'MON') || '2009'
, select sum(transaction_amount)
...
or ideally specify the column names you're inserting into:
BEGIN
FOR i IN 1..12
LOOP
INSERT INTO REVENUE ( <column1>, <column2> )
VALUES ( TO_CHAR(TO_DATE(i, 'MM'), 'MON') || '2009'
, select sum(transaction_amount)
...
Because you don't have the values keyword the parser thinks that the parentheses are enclosing a column list, and it's therefore confused when it sees the next opening bracket from the to_char - the error is against the bracket, which is char 31 if it starts with ma tab, which would also poibably explain why the asterisk marking the error position appears in slightly the wrong place. It's expecting a comma there as a delimiter in the column list. It apparently hasn't got as far as ecaluating whether 'to_char' is a valid column name.
Actually the select you're using for the second value suggests you might be trying to,use the subquery version; depending on what else you're doing in the rest of that statement, you might want:
BEGIN
FOR i IN 1..12
LOOP
INSERT INTO REVENUE ( <column1>, <column2> )
SELECT TO_CHAR(TO_DATE(i, 'MM'), 'MON') || '2009'
, sum(transaction_amount)
FROM <some other table>
...
I suspect you could probably do that with a single insertnrather than a loop, but hard to be sure without seeing the whole thing.

Procedures in PLSQL using ORACLE DB

CREATE OR REPLACE PROCEDURE ABC ( REG_NO IN CO_ENROLMENT.S_REGNO%TYPE,
TERM IN COURSEOFFERING.CO_TERMNUMBER%TYPE,
YEAR IN COURSEOFFERING.CO_YEAR%TYPE,
CO_TITLE IN COURSE.C_TITLE%TYPE,
EN_DATE OUT CO_ENROLMENT.COE_ENROLDATE%TYPE,
COM_ST OUT CO_ENROLMENT.COE_COMPLETIONSTATUS%TYPE)
AS
BEGIN
SELECT M.COE_COMPLETIONSTATUS, M.COE_ENROLDATE
INTO COM_ST, EN_DATE
FROM COURSEOFFERING O
INNER JOIN COURSE C
ON C.C_ID = O.C_ID
INNER JOIN CO_ENROLMENT M
ON M.CO_ID = O.CO_ID
WHERE M.S_REGNO LIKE REG_NO AND
O.CO_TERMNUMBER LIKE TERM AND
O.CO_YEAR LIKE YEAR AND
C.C_TITLE LIKE CO_TITLE;
END ABC;
I have written down above procedure.
Below is PL/SQL block to call above procedure,
DECLARE
COMPL_STATUS CO_ENROLMENT.COE_COMPLETIONSTATUS%TYPE;
ENROL_DATE CO_ENROLMENT.COE_ENROLDATE%TYPE;
BEGIN
ABC (44444444, 2009, 2, 'PLSQL Programming',
EN_DATE => enrol_date, COM_ST =>compl_status);
DBMS_OUTPUT.PUT_LINE ('STUDENT COMPLETION STATUS AND ENROLMENT DATE IS '
|| ENROL_DATE
|| ' '
|| compl_status );
END;
It returns me error as no data found, but when I run query separately I get the output. I couldn't figure out what is wrong. Did I wrote procedure block correctly and Parameters passed in PL/SQL block are correct?
In proc you have year on third place buth you call it on second place. And how are types
(COURSEOFFERING.CO_TERMNUMBER%TYPE, COURSEOFFERING.CO_TERMNUMBER%TYPE) declared?
When select statement in your ABC procedure returns no rows the NO_DATA_FOUND exception will be raised and execution of your stored-procedure will be halted. To avoid such behavior you need to add EXCEPTION section in the stored procedure to catch the exception and react appropriately. To that end the execution section of your stored procedure might look:
BEGIN
SELECT M.COE_COMPLETIONSTATUS, M.COE_ENROLDATE
INTO COM_ST, EN_DATE
FROM COURSEOFFERING O
INNER JOIN COURSE C
ON C.C_ID = O.C_ID
INNER JOIN CO_ENROLMENT M
ON M.CO_ID = O.CO_ID
WHERE M.S_REGNO LIKE REG_NO AND
O.CO_TERMNUMBER LIKE TERM AND
O.CO_YEAR LIKE YEAR AND
C.C_TITLE LIKE CO_TITLE;
EXCEPTION
WHEN NO_DATA_FOUND
THEN DBMS_OUTPUT.PUT_LINE('No data found') -- for example
END ABC
Or if you want to proceed even if select statement raises exception you might enclose your select statement with nested BEGIN .. END block.
BEGIN
-- some code before
BEGIN
SELECT M.COE_COMPLETIONSTATUS, M.COE_ENROLDATE
INTO COM_ST, EN_DATE
FROM COURSEOFFERING O
INNER JOIN COURSE C
ON C.C_ID = O.C_ID
INNER JOIN CO_ENROLMENT M
ON M.CO_ID = O.CO_ID
WHERE M.S_REGNO LIKE REG_NO AND
O.CO_TERMNUMBER LIKE TERM AND
O.CO_YEAR LIKE YEAR AND
C.C_TITLE LIKE CO_TITLE;
EXCEPTION
WHEN NO_DATA_FOUND
THEN DBMS_OUTPUT.PUT_LINE('No data found') -- for example
END;
-- some code after
END ABC
You probably lack wildcards in your procedure query.
16:02:06 SYSTEM#dwh-prod> select * from dual where 'abc' like 'ab';
no rows selected
16:02:18 SYSTEM#dwh-prod> select * from dual where 'abc' like 'ab%';
D
-
X
Without them, C.C_TITLE LIKE CO_TITLE is equal to C.C_TITLE = CO_TITLE, which is not neccessarily what you want.
Try C.C_TITLE LIKE '%'||CO_TITLE||'%' etc.
You're calling your stored procedure incorrectly. You have to specify parameters in the correct order, or use named parameter notation, ie
ABC (44444444, 2, 2009, 'PLSQL Programming', enrol_date, compl_status);
or
ABC (REG_NO => 44444444, YEAR => 2009, TERM => 2,
CO_TITLE => 'PLSQL Programming',
EN_DATE => enrol_date, COM_ST =>compl_status);
You can see that if you use named parameter notation you can specify parameters in a different order (or not at all if the parameters have DEFAULT values).

Resources