An INTO clause is expected in this SELECT statement - oracle

any help for this please ?
I want to return all those who live in france, then compare them with those who live in BELguim if they have the same name or name_done show the result
declare
cursor c is select namer from poeple where city = 'France';
c_res c%rowtype;
begin
open c;
loop
fetch c into c_res;
exit when c%notfound;
select * from poeple tw where city = 'BELguim' AND (c_res.namer = tw.namer OR c_res.namer = tw.namer || ' _done' );
end loop;
close c;
end;

Why use a cursor? You can write this with a single query:
select pb.*
from poeple pf join
poeple pb
on pf.city = 'France' and pb.city = 'BELgium' and
(pf.namer = pb.namer or pf.namer = pb.namer || ' _done');
The issue with your code is that PL/SQL does not allow queries to return anything from code blocks. You can print out the results, insert values into variables, or put them in another table. But, you cannot just have the results from the query.

The issue is with :
select * from poeple tw where city='BELguim' AND (c_res.namer
=tw.namer OR c_res.namer =tw.namer || ' _done' );
You cannot write the SQL directly in PL/SQL. The PL/SQL engine expects an INTO clause.
Looking at your code, you don't need the cursor c. it could be written in a single SQL. Which brings it back to Gordon's query :
select pb.*
from poeple pf join
poeple pb
on pf.city = 'France' and pb.city = 'BELgium' and
(pf.namer = pb.namer or pf.namer = pb.namer || ' _done');
If you really need PL/SQL for some stuff which you have not explained, for example return the result set as REF CURSOR, then have an OUT parameter as REF CURSOR. But, you need to explain the requirement.
If you can do something in SQL, then don't use PL/SQL.
EDIT If your final goal is to update, then use the above query as USING clause in the MERGE statement.

Related

PL SQL FUNCTION BODY RETURN SQL QUERY GETS ERROR: ORA-20001: Query must begin with SELECT or WITH

I get an ORA-20001: Query must begin with SELECT or WITH when I verify the code below. Does that mean I can't use FOR record (sql query) and a loop? Is it ok to load APEX card info the way I did in the loop? Any help is appreciated.
My goal is to display all media for a project and make the icon turn green for MEDIA that is referenced in a specific STEP_MEDIA row. I have tried outer joins, but I get duplicates. If there is a better way please let me know.
Oracle Cloud
Application Express 21.1.7
Database Version: 19c
return q'~
DECLARE
n_count number;
BEGIN
FOR record in (SELECT *
FROM MEDIA
where KHN_PROJECT_ID = :P300_PROJECT_ID
ORDER BY FILE_NAME)
LOOP
CARD_TITLE := record.FILE_NAME;
CARD_SUBTITLE := record.FILE_MIMETYPE;
CARD_LINK := apex_util.prepare_url( '#' );
CASE WHEN record.FILE_MIMETYPE LIKE 'video%'
THEN
CARD_TEXT := '<video> ...</video>';
ELSE
CARD_TEXT := '<img> ...</img>';
END CASE;
CARD_ICON := 'fa-check';
SELECT COUNT(STEP_MEDIA_ID) INTO n_count
FROM STEP_MEDIA
WHERE MEDIA_ID = record.MEDIA_ID AND STEP_ID = :P300_STEP_ID_LOADED;
IF ncount = 0
THEN
CARD_COLOR := 'u-success';
ELSE
CARD_COLOR := 'u-normal';
END IF;
CARD_SUBTEXT := '<button class="t-Button t-Button--noLabel t-Button--icon add-favorite" id="fav_'||record.MEDIA_ID||'" type="button">Select</span></button>';
END LOOP;
END;
~';
Here is the code I wrote using an outer join. It produces duplicate rows.
select m.FILE_NAME AS CARD_TITLE,
m.FILE_MIMETYPE AS CARD_SUBTITLE,
m.MEDIA_ID,
apex_util.prepare_url( '#' ) CARD_LINK,
CASE WHEN m.FILE_MIMETYPE LIKE 'video%'
THEN
'<video >...</video>'
ELSE
'<img >...</img>'
END
CARD_TEXT,
'fa-check' CARD_ICON,
CASE WHEN sm.step_media_id is NULL
THEN
'u-success'
ELSE
'u-normal'
END
CARD_COLOR,
from MEDIA m
LEFT OUTER JOIN STEP_MEDIA sm
ON m.MEDIA_ID = sm.MEDIA_ID
where m.KHN_PROJECT_ID = :P300_PROJECT_ID OR sm.STEP_ID = :P300_STEP_ID_LOADED
ORDER BY m.FILE_NAME
This is Apex, right?
Errors with codes from -20000 to -20999 are user-defined. In this case, Apex developers created it (so it's not one of built-in Oracle errors).
What does it say? Looks like you want to "dynamically" create a Classic report with the Cards layout. You're supposed to return a valid query, and it begins with a select or the with keyword (if you're using a with factoring clause, i.e. a CTE (common table expression)).
There's no evidence that code you posted returns what I said.
From my point of view, you shouldn't be doing it in a loop anyway. Everything can be done with a single query (in your case, that would be a join of two tables) that uses CASE expressions to set certain CARD_... values.
That query would then be Classic report's source.
This query uses sql which is compatible with Oracle Apex and displays all media and shows which media is used in a step. The subquery was the solution.
select DISTINCT m.FILE_NAME AS CARD_TITLE,
m.FILE_MIMETYPE AS CARD_SUBTITLE,
m.TAGS AS TAGS,
m.MEDIA_ID,
apex_util.prepare_url( '#' ) CARD_LINK,
CASE WHEN m.FILE_MIMETYPE LIKE 'video%'
THEN
'<video controls width = "100%" height= "100%" class="object-fit">... </video>'
ELSE
'<img >...</img>'
END
CARD_TEXT,
'fa-check' CARD_ICON,
CASE WHEN sm.step_media_id is NULL
THEN
'u-normal'
ELSE
'u-success'
END
CARD_COLOR,
'<button class="t-Button t-Button--noLabel t-Button--icon add-favorite" id="fav_'||m.MEDIA_ID||'" type="button">Select</span></button>' CARD_SUBTEXT
from MEDIA m
LEFT OUTER JOIN (select * FROM STEP_MEDIA WHERE STEP_ID = :P300_STEP_ID_LOADED) sm
ON m.MEDIA_ID = sm.MEDIA_ID
where m.KHN_PROJECT_ID = :P300_PROJECT_ID
ORDER BY m.FILE_NAME

Iterate through cursor and storing the output of the cursor in another table

I am trying to iterate through a cursor which stores the value of the table. I use a FOR Loop to iterate and IF one of the conditions is met, I store the output in another table. I am not sure of the approach I am following and also getting error(ORA-00933: SQL command not ended properly). Stats_Queries is my reference table where I iterate my cursor through. STATS_RESULT_CARD is my output table where I have to store the results. Please help.
DECLARE
CURSOR c1 IS
select Stats_Queries.OBJECTTYPE, Stats_Queries.CATEGORY, Stats_Queries.QUERY
from Stats_Queries;
r1 c1%ROWTYPE;
BEGIN
FOR r1 IN c1 LOOP
If (r1.OBJECTTYPE = 'CARD') THEN
INSERT INTO STATS_RESULTS_CARD (NODETYPENAME, NODEDEFNAME , CARDTYPENAME, PROVISIONSTATUSNAME, STATDATE, CARDCOUNT)
select nt.name, nd.name, ct.name, ps.name, sysdate, count(c.cardid)
from cardtype ct, card c, node n, nodetype nt, nodedef nd, provisionstatus ps
where ct.name in ('SRA AMP', 'XLA AMP', 'SAM', 'ESAM')
and ct.cardtypeid = c.card2cardtype
and c.card2node = n.nodeid
and n.node2nodetype = nt.nodetypeid
and n.node2nodedef = nd.nodedefid
and c.card2provisionstatus = ps.provisionstatusid
group by nt.name, nd.name, ct.name, ps.name
END If;
END LOOP;
END;
As an aside from the answer that Finbarr has provided (which is perfectly correct; add in the missing semi-colon and your procedure should work), why do you need to loop through the cursor at all? That's the slow way of doing it.
You could just do a single insert statement instead, such as:
insert into stats_results_card (nodetypename,
nodedefname,
cardtypename,
provisionstatusname,
statdate,
cardcount)
select x.nt_name,
x.nd_name,
x.ct_name,
x.ps_name,
x.statdate,
x.cnt_cardid
from (select nt.name nt_name,
nd.name nd_name,
ct.name ct_name,
ps.name ps_name,
sysdate statdate,
count (c.cardid) cnt_cardid
from cardtype ct,
card c,
node n,
nodetype nt,
nodedef nd,
provisionstatus ps
where ct.name in ('SRA AMP',
'XLA AMP',
'SAM',
'ESAM')
and ct.cardtypeid = c.card2cardtype
and c.card2node = n.nodeid
and n.node2nodetype = nt.nodetypeid
and n.node2nodedef = nd.nodedefid
and c.card2provisionstatus = ps.provisionstatusid
group by nt.name,
nd.name,
ct.name,
ps.name) x
cross join (select stats_queries.objecttype,
stats_queries.category,
stats_queries.query
from stats_queries
where objecttype = 'CARD');
N.B. This assumes that there really isn't any link between the original cursor and the select statement that was inside the loop; we do a cross join to replicate the rows the required number of times.
If there was an actual join between the two queries, you would put that in place of the cross join.
ORA-00933: SQL command not ended properly
Probably occurring because you missed a semicolon after
group by nt.name, nd.name, ct.name, ps.name

Refactor code without cursor PL/SQL

How can I refactor these lines of code without using CURSOR?
I am beginner in PL/SQL.
Any help would be appreciated. Thank you
DECLARE
CURSOR c_emps IS
SELECT employee_id
FROM bonus;
v_region HR.REGIONS.region_name%TYPE;
v_salary hr.employees.salary%TYPE;
BEGIN
FOR r_emps IN c_emps LOOP
SELECT reg.region_name, emp.salary
INTO v_region, v_salary
FROM hr.employees emp,
hr.departments dep,
hr.Locations loc,
hr.countries cot,
hr.regions reg
WHERE emp.department_id = dep.department_id AND
dep.location_id = loc.location_id AND
loc.country_id = cot.country_id AND
cot.region_id = reg.region_id AND
employee_id = r_emps.employee_id;
IF v_region = 'Europe' THEN
UPDATE bonus
SET bonus = bonus + (v_salary * .01)
WHERE employee_id = r_emps.employee_id;
ELSE
UPDATE bonus
SET bonus = v_salary * .01
WHERE employee_id = r_emps.employee_id;
END IF;
END LOOP;
COMMIT;
END;
/
CURSOR c_emps IS
SELECT employee_id
FROM bonus;
You don't need to explicitly declare the CURSOR. You could do it in the CURSOR FOR LOOP itself:
FOR r_emps IN (SELECT employee_id FROM bonus)
LOOP
If PL/SQL is not mandatory, then you could do it in plain SQL using CASE expression in the UPDATE statement.
Something like,
UPDATE bonus
SET bonus =
CASE
WHEN region = 'Europe'
THEN bonus + (v_salary * .01)
ELSE v_salary * .01
...
and so on
Yes, you need to rewrite the entire PL/SQL code into a SQL update statement. But, it would be much better and faster. For loop is row-by-row processing, thus it is slow-by-slow. Avoid PL/SQL if you could do the same in SQL.
When working with SQL Server, an awful lot of effort is taken to avoid cursors because they are handled very poorly. Using a cursor in SQL Server is like slogging waist-deep through molasses. Oracle handles cursors much better so you see a lot more row-by-row work in Oracle. Too much, really. Even in Oracle, if something can be done with a single SQL statement, it is far superior than using PL/SQL cursors and looping.
Unfortunately, Oracle doesn't allow joins in UPDATE statements. But not to worry, the more recent innovation, the MERGE statement does.
MERGE INTO BONUS B
USING(
SELECT EMP.EMPLOYEE_ID, EMP.SALARY, REG.REGION_NAME
FROM HR.EMPLOYEES EMP
JOIN HR.DEPARTMENTS DEP
ON DEP.DEPARTMENT_ID = EMP.DEPARTMENT_ID
JOIN HR.LOCATIONS LOC
ON LOC.LOCATION_ID = DEP.LOCATION_ID
JOIN HR.COUNTRIES COT
ON COT.COUNTRY_ID = LOC.COUNTRY_ID
JOIN HR.REGIONS REG
ON REG.REGION_ID = COT.REGION_ID ) U
ON( U.EMPLOYEE_ID = b.EMPLOYEE_ID )
WHEN MATCHED THEN
UPDATE SET B.BONUS =( u.SALARY * 0.01 ) +
CASE U.REGION_NAME WHEN 'Europe' THEN B.BONUS ELSE 0 END;
How nice that the when not matched clause is not required, effectively turning the merge into a very flexible update.

Replace SELECT INTO statement with a cursor ORACLE

This is the query. How to replace SELECT INTO statement with a cursor?
I'm newbie in Oracle
Thanks for your help
SELECT CEQ_LISTE_TYPE_QUESTIONS.ID_LISTE_TYPE_QUESTION
INTO vintIdListeTypeQuestion
FROM CEQ_FORMULAIRES
inner join CEQ_LISTE_TYPE_QUESTIONS
on CEQ_LISTE_TYPE_QUESTIONS.ID_LISTE_TYPE_FORMULAIRE=CEQ_FORMULAIRES.ID_TYPE_FORMULAIRE
AND CEQ_LISTE_TYPE_QUESTIONS.WEBCODE='ITEM_BETA_LACTAMASE'
WHERE CEQ_FORMULAIRES.ID_FORMULAIRE=to_number(out_rec.ID_FORMULAIRE)
and ceq_formulaires.date_inactive is null;
The error tells you that the query returns more than 1 row, so you should determine which row you need. Here an example how to fetch the most recent row based on a date field I thought up in ceq_list_type_questions "some_date".
select max(q.id_liste_type_question) keep (dense_rank last order by q.some_date) into vintidlistetypequestion
from ceq_formulaires f
join ceq_liste_type_questions q on q.id_liste_type_formulaire = f.id_type_formulaire
where f.id_formulaire = to_number(out_rec.id_formulaire)
and f.date_inactive is null
and q.webcode = 'ITEM_BETA_LACTAMASE'
Well, if you want to process your multiple rows in a loop, it's as simple as
BEGIN
FOR curs IN (SELECT ceq_liste_type_questions.id_liste_type_question
FROM ceq_formulaires
INNER JOIN ceq_liste_type_questions ON ceq_liste_type_questions.id_liste_type_formulaire=ceq_formulaires.id_type_formulaire
AND ceq_liste_type_questions.webcode = 'ITEM_BETA_LACTAMASE'
WHERE ceq_formulaires.id_formulaire = TO_NUMBER(out_rec.id_formulaire)
AND ceq_formulaires.date_inactive IS NULL)
LOOP
DBMS_OUTPUT.PUT_LINE(curs.id_liste_type_question); -- do what you need to do
END LOOP;
END;
/
But, as BazzPsychoNut mentions, if it's a requirement that your SQL return/operate on a single row, you'll need to modify your query to meet that requirement.

How to loop in sql?

I dont want to use the "loop" related keyword, how can I implement loop with basic sql command in oracle ?
I have two table :
A:
ID, Color
B,
ID, AID, Type
I want to loop all records in B, and if ID = AID, then set the A.Color = B.Type
Thanks in advance !
Looping is, by definition, a procedural construct.
SQL is declarative: tell the database what you want done, not how to do it.
If you're absolutely convinced that you need to program such a thing, then write it in PL/SQL, Oracle's procedural language.
Bu I'm sure that it's possible to do what you want in SQL using an UPDATE with a WHERE clause.
Something like this (corrected per NullUserException):
UPDATE A SET A.Color = (SELECT B.Type FROM B WHERE A.ID = B.AID)
An alternate method:
MERGE INTO a
USING b
ON (b.aid = a.id)
WHEN MATCHED THEN UPDATE SET a.color = b.type;
You could just do:
UPDATE tablea a
SET a.color = (SELECT b.type
FROM tableb b
WHERE b.aid = a.id)
See this SQL script.
To do that you will have to write a stored procedure using PL/SQL. Here is the oracle page with some info and papers on the topic.
As others pointed out, you can probably solve your problem with a normal DML statement, without any looping involved. But to give you some basics on how to accomplish what you asked for in PL/SQL, here's an example...
DECLARE
CURSOR c IS
SELECT id, aid, type FROM b;
statement VARCHAR2(200);
BEGIN
FOR iterator IN c LOOP
IF iterator.id = iterator.aid THEN
statement := 'UPDATE a SET color = ' || iterator.type || 'WHERE id = ' || iterator.id;
EXECUTE IMMEDIATE statement;
END IF;
END LOOP;
END;
This anonymous PL/SQL block will iterate through each record in table b, and if b.id = b.aid, it will update table a and set a.color = b.type where a.id = b.id.
This seems to be what you were asking for. It's not exactly an efficient way to go about doing things, since you're firing off one DML statement per row in table b that has b.id=b.aid. But I wanted more to give this as a syntax example. This is just one way to iterate through a cursor by the way; you can also explicitly open cursors and fetch records, but it's easier this way if you don't need to do anything but iterate over the entire result set.

Resources