Refactoring a query - oracle

I have a simplified code like below:
PROCEDURE MY_PROC (d1 IN DATE) IS
CURSOR curs IS SELECT * FROM TAB1 WHERE date1 = d1;
result BOOLEAN;
rTab1 TABLE1%ROWTYPE;
BEGIN
OPEN curs;
FETCH curs INTO rTab1 ;
result := curs%FOUND;
CLOSE curs;
IF result = FALSE THEN
rTab1.NUM_ID := 0;
END IF;
SELECT * FROM TAB2 WHERE NVL(NUM_ID, 0) = rTab1.NUM_ID;
END;
Is it possible to write everything in one query? I mean without having to chceck if rTab1.NUM_ID exists. Maybe with join? And get same results at the end like above?
I mean something like:
SELECT * FROM TAB1, TAB2
WHERE NVL(TAB1.NUM_ID, 0) = TAB2.NUM_ID
AND date1 = d1;

The code is looking for tab1 rows where the date matches the given date d1.
If there is one matching row, then its num_id is used.
If there is more than one matching row, then one of their num_ids is used arbitrarily.
If there is no matching row, the value 0 is used.
Then with that value the code selects all matching tab2 rows.
This can be done with a single query. To get only one tab1.num_id, use MIN, MAX or ANY_VALUE. To get a zero when there is no date match use NVL or COALESCE.
SELECT *
FROM tab2
WHERE NVL(num_id, 0) = (SELECT NVL(MIN(num_id), 0) FROM tab1 WHERE date1 = d1);

Related

PL/SQL error "not enough values" during "select into"

I'm working on creating a pl/sql function that finds the highest average of students from a list of classes. I have the average computation part working correctly; however, I need to return the results as a table of records and I'm running into the error while attempting to store the results into the record.
My record declaration is as follows
create or replace TYPE studentRec as object (
term varchar2(10),
lineNum number(4),
coTitle varchar2(50),
stuId varchar2(5),
average number);
The error comes when I'm trying to fill the record using a select into statement.
create or replace function highest_avg(stu_id scores.sid%type,
line_no scores.lineno%type)
return stuRecTab
as
stuRec stuRecTab;
average number;
studentRec_t studentRec;
begin
stuRec := stuRecTab();
select avg(points)
into average
from scores, courses
where scores.sid = stu_id
and scores.lineno = line_no
and scores.term = courses.term
and scores.lineno = courses.lineno;
SELECT DISTINCT c.term, c.lineno, cc.ctitle, s.sid, average
INTO studentRec_t
from courses c, class_catalog cc, scores s
where s.sid = stu_id
and s.lineno = line_no
and s.term = c.term
and s.lineno = c.lineno
and c.cno = cc.cno;
stuRec := studentRec_t;
return(stuRec);
end;
I've run it as just a query and I'm getting back what I expect so I'm not sure why this error is popping up. Any help would be greatly appreciated as this is my first time working with pl/sql.
First, you can't SELECT INTO the fields of an object instance variable - you have to create an object instance in your select, then SELECT that INTO your object instance variable. You can't simply assign an instance variable to a collection - you need to put it at an appropriate index. So what you end up with is something like:
create or replace function highest_avg(stu_id scores.sid%type,
line_no scores.lineno%type)
return stuRecTab
as
stuRec stuRecTab;
average number;
studentRec_t studentRec;
begin
stuRec := stuRecTab();
select avg(points)
into average
from scores s
inner join courses c
on c.term = s.term and
c.lineno = s.lineno
where s.sid = stu_id and
s.lineno = line_no;
SELECT studentRec(term, lineno, ctitle, sid, average)
INTO studentRec_t
FROM (SELECT DISTINCT c.term, c.lineno, cc.ctitle, s.sid, average
from scores s
INNER JOIN courses c
ON s.term = c.term and
s.lineno = c.lineno
INNER JOIN class_catalog cc
ON cc.cno = c.cno
where s.sid = stu_id and
s.lineno = line_no);
stuRec(1) := studentRec_t;
return(stuRec);
end;
As no test data was provided I haven't tested this - but at least it compiles at dbfiddle.

Cursor loop update Table by many values

DECLARE
CURSOR contacts
IS
SELECT SUM (budget) AS budget
FROM et_bp_gl_account a, et_bp_fact f
WHERE f.gl_account_id = a.gl_account_id
AND total_flag = 0
GROUP BY month_id, org_unit_id;
BEGIN
FOR r IN contacts
LOOP
UPDATE et_bp_fact
SET budget = r.budget
WHERE gl_account_id IN (SELECT total_element
FROM et_bp_gl_account g, et_bp_fact f
WHERE f.gl_account_id = g.gl_account_id);
END LOOP;
END;
I want to update the table ET_BP_FACT by many values example(25,50,75)
returned from Cursor but when i execute the table updated by (25,25,25)
I think there an issue in the loop
That's because the UPDATE's WHERE clause doesn't "reference" cursor's values. Something like this:
DECLARE
CURSOR contacts
IS
SELECT month_id, org_unit_id, --> include additional columns here ...
SUM (budget) AS budget
FROM et_bp_gl_account a, et_bp_fact f
WHERE f.gl_account_id = a.gl_account_id
AND total_flag = 0
GROUP BY month_id, org_unit_id;
BEGIN
FOR r IN contacts
LOOP
UPDATE et_bp_fact
SET budget = r.budget
WHERE gl_account_id IN
(SELECT total_element
FROM et_bp_gl_account g, et_bp_fact f
WHERE f.gl_account_id = g.gl_account_id
--
AND f.org_unit_id = r.org_unit_id --> ... and reference them here ...
AND g.month_id = r.month_id);
--> ... or, possibly, here
END LOOP;
END;
I'd suggest you to always precede column names with table aliases, because - looking at your code, there's no way to guess which table(s) MONTH_ID and ORG_UNIT_ID belong to, so my code might (or might not) work, but I hope you got the general idea.

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

The first row when single-row subquery returns more than one row

My subquery returns more than one row and I need the first row and keep my max function
Select ...
Where GHTY_FIRME.FIRME_ID= (Select FIRME_ID
FROM imag_verification_tube
WHERE (numero_dossier = '12004' OR NUMERO_TIRE= '12004')
AND CREE_LE = (select max (CREE_LE)
from tableX where(numero_dossier ='12004' OR
NUMERO_PQDCS= '12004 ')));
Using the rownum pseudocolumn:
Select ...
Where GHTY_FIRME.FIRME_ID= (Select FIRME_ID
FROM imag_verification_tube
WHERE (numero_dossier = '12004' OR NUMERO_TIRE= '12004')
AND CREE_LE = (select max (CREE_LE)
from tableX
where(numero_dossier ='12004'
OR NUMERO_PQDCS= '12004 ')
and rownum < 2));
See also On ROWNUM and Limiting Results.
That said, you can try to optimize the sql. One of the first things I would do is to convert the inner SQLs to joins. Also, depending on your business logic, the or condition could also be elminated.

Copy data from a column in some rows to another column in other rows in oracle

quite the same question as here : Copy data from one existing row to another existing row in SQL?
but in Oracle, where update ... from and update t1, t2 are not supported.
I'll repeat it here in my own words ;
I have a table T, which looks like this :
and as the arrow shows it, I want to copy everything from r where c = 1 to e where c = 2, with t matching.
I have the select statement to get what I want to copy :
select
told.t,
told.r
from
T told
inner join
T tnew
on
told.t= tnew.t
where
told.c = 1
and
tnew.c = 2
I just don't know how to put this together in an update. An Oracle update, specifically.
try this:
update T tnew
set tnew.e = (select told.r from T told where told.c = 2 and told.t = tnew.t)
where tnew.c = 1
Sounds like the time for a bulk collects! Not as pretty as AB Cade's solution but more efficient.
declare
c_data is
select t1.rowid as rid, t2.r
from my_table t1
join my_table t2
on t1.t = t2.t
where t1.c = 2
and t2.c = 1
;
type t__data is table of c_data index by binary_integer;
t_data t__data;
begin
open c_data;
loop
fetch c_data bulk collect into t_data limit 25000;
exit when t_data.count = 0;
forall i in t_data.first .. t_data.last loop
update my_table
set e = t_data(i).r
where rowid = t_data(i).rid
;
commit;
end loop;
close c_data;
end;
/

Resources