LV_id number;
Cursor CR_test Is
select t.id
from table1 t
where t.foo = p_foo
order by t.creation_date;
Open CR_test;
Fetch CR_test
Into LV_id;
Close CR_test;
or this one :
select x.id
from(select t.id
from table1 t
where t.foo=p_foo
order by t.creation_date) x
where rownum = 1
Both above make similar result but i need known about which one is more efficient!
This is Tom Kyte's mantra:
You should do it in a single SQL statement if at all possible.
If you cannot do it in a single SQL Statement, then do it in PL/SQL.
If you cannot do it in PL/SQL, try a Java Stored Procedure.
If you cannot do it in Java, do it in a C external procedure.
If you cannot do it in a C external routine, you might want to seriously think about why it is you need to do it…
http://tkyte.blogspot.com/2006/10/slow-by-slow.html
Easiest way to find out in this case is to test your queries.
Make sure to test this yourself, indexes and data in your table may produce different results with your table.
Without any index, it looks like there is a better approach using analytic function DENSE_RANK:
SELECT MIN(id) KEEP (DENSE_RANK FIRST ORDER BY creation_date)
INTO lv_id
FROM table1
WHERE foo = p_foo;
I used the following code to test the time consumed by your queries (execute this block several times, results may vary):
DECLARE
p_foo table1.foo%TYPE := 'A';
lv_id table1.id%TYPE;
t TIMESTAMP := SYSTIMESTAMP;
BEGIN
FOR i IN 1 .. 100 LOOP
-- Query here
END LOOP;
dbms_output.put_line(SYSTIMESTAMP - t);
END;
Results:
Using cursor, fetching first row:
2.241 s
Using query with ROWNUM:
1.483 s
Using DENSE_RANK:
1.168 s
Related
Currently, I have some SQL queries which looks like this:
Drop Table X;
Create Table X(id INTEGER);
Insert Into X
select ..
from..
where a.name = GIVENNAME;
Select SUM(..)
from ..
..
order by date desc;
And I want to put all these into a SQL Function, where I can choose the Parameter "GIVENNAME" when I call the function.
Is there a way to make this possible?
I would know how to do it in JSON/Java, but I have really no clue how to make it as a Function in SQL (using Oracle).
Edit:
After pointing out some things, I want to add my current code:
DROP TABLE TEMPTABLE;
CREATE TABLE TEMPTABLE
(mitID INTEGER);
INSERT INTO TEMPTABLE
select m.mitid
from mitarbeiter m
inner join abteilungen a on m.abt = a.abtid
where a.abtname = #GIVENNAME;
select SUM(g.kosten)
from gehaelter g
left outer join gehaelter k
on g.mitarbeiter = k.mitarbeiter
and g.vondatum < k.vondatum
where k.mitarbeiter is null AND g.mitarbeiter in (select * from TEMPTABLE)
order by g.vondatum desc;
I'm currently more interested in a working solution than a nice & clean one
Fortunately you can have both:
create or replace function get_sum_kosten
( p_givenname in abteilungen.abtname%type )
return number
as
return_value number;
begin
select SUM(g.kosten)
into return_value
from gehaelter g
left outer join gehaelter k
on g.mitarbeiter = k.mitarbeiter
and g.vondatum < k.vondatum
where k.mitarbeiter is null
AND g.mitarbeiter in (select m.mitid
from mitarbeiter m
inner join abteilungen a on m.abt = a.abtid
where a.abtname = P_GIVENNAME
)
;
return return_value;
end;
Possible? Yes. Recommended? No.
For any DDL, you'd have to use dynamic SQL (EXECUTE IMMEDIATE). If queries are complex, those commands will be difficult to maintain.
INSERT is a DML, but you can't use it in a function, unless it is an autonomous transaction (and you'll have to commit (or rollback) within the function).
If it were a procedure, you'd - at least - avoid the last problem I mentioned. If you're returning something, use an OUT parameter.
Can't you use a (global) temporary table, instead? Create it once, use it many times. I understand that your code might be very complex and maybe it really can't fit into a single SELECT statement, but you should - at least - try to do that job in an Oracle spirit (i.e. it is not MS SQL Server).
example of procedure
https://www.sitepoint.com/stored-procedures-mysql-php/
like this
DELIMITER $$
CREATE PROCEDURE `avg_sal`(out avg_sal decimal)
BEGIN
select avg(sal) into avg_sal from salary;
END
In my stored proc, I run a query like soi :
SELECT NETWORKOWNER.BUS_SEQ.NEXTVAL#LEGACYSMITH.WORLD
FROM TBLDECIDERCONTRACT#LEGACYSMITH.WORLD
WHER PROVID = 28938
How would obtaon only one NEXTVAL result?
Using dual:
SELECT NETWORKOWNER.BUS_SEQ.NEXTVAL#LEGACYSMITH.WORLD FROM dual
Or more generally: make sure your FROM and WHERE clauses result in just one row found - then NEXTVAL will be executed just once.
In addition, you can always get just a single NEXTVAL execution by invoking it natively in PL/SQL, as in:
DECLARE
l_seq INTEGER;
BEGIN
l_seq := my_sequence.NEXTVAL;
END;
If I'm using oracle sql and plsql to do computations on an employee, and then selecting more data based on the result of those computations... will it be faster to select the all the data I may need all at once when selecting the employee (assume something like 20 rows with 5 columns each) and then use that data as a local array, or select just the one row I will need when finished?
-- Example with multiple selects
declare
l_employeeID number;
l_birthday date;
l_horoscope varchar2;
begin
select employeeID into l_employeeID from employeeTbl t where t.rownum = 1;
l_birthday := get_birthdayFromID(l_employeeID);
select horoscope into l_horoscope from horoscopeTable t where l_birthday between l_horoscope.fromDate and l_horoscope.toDate;
return l_horoscope;
end;
-- Example with table selection, and loop iteration
declare
l_empolyeeID number;
l_birthday date;
l_horoscope varchar2;
l_horoscopeDates date_table;
begin
select employeeID, cast(multiset(select horoscopeDate from horoscopeTable)) as date_table into l_employeeID, l_horoscopeDates from employeeTbl t where t.rownum = 1;
l_birthday := get_birthdayFromID(l_employeeID);
for i in 1 .. l_horoscopeDates.count - 1 loop
if l_birthday between l_horoscopeDates(i) and l_horoscopeDates(i + 1) then
return l_horoscopeDates(i);
end if;
end loop;
return null;
end;
I understand that I'm paying more ram and IO to select additional data, but is it more efficient than incurring another context switch to call the sql when the extra data is not significantly larger than needed?
Context switches are considered very expansive when using Oracle. If the table doesn't contain large amounts of data, you should defeinitely query more data in order to reduce the number of times PL/SQL makes an SQL query.
Having said that, I think your question should be the other way around, why are you using PL/SQL at all, when your entire logic can be summed into a single SQL statement?
example -
select horoscope
from horoscopeTable
where (select get_birthdayFromID(employeeID)
from employeeTbl
where t.rownum = 1) between l_horoscope.fromDate and l_horoscope.toDate;
Syntax might need a little touch ups but the main idea seems right to me.
You can also find more observations in this piece about tuning PL/SQL code - http://logicalread.solarwinds.com/sql-plsql-oracle-dev-best-practices-mc08/
I'm doing some testing to see if I can speed up a particular result set, but can't seem to get this particular solution working. I have data coming a few different tables and want to combine the data. I want to try this without using a union select to see if I get a performance improvement.
When I have a custom table/object type in a function, it seems to delete the existing data from the table when doing the subsequent select. Is there a way to do subsequent selects into the table without having the previous data deleted?
SQL Fiddle
I don't think that approach will be faster, in fact I expect it to be much slower.
But if you do want to do it, you need to put the rows from the second select into an intermediate collection and then join both using multiset union.
Something like this:
create or replace function
academic_history(p_student_id number)
return ah_tab_type
is
result ah_tab_type;
t ah_tab_type;
begin
select ah_obj_type(student_id,course_code,grade)
bulk collect into result
from completed_courses
where student_id = p_student_id;
select ah_obj_type(student_id,course_code,'P')
bulk collect into T
from trans_courses
where student_id = p_student_id;
result := result multiset union t;
return result;
end;
/
As well as the multiset approach, if you really wanted to do this you could also make it a pipelined function:
create or replace function
academic_history(p_student_id number)
return ah_tab_type pipelined
is
T ah_tab_type;
begin
select ah_obj_type(student_id,course_code,grade)
bulk collect
into T
from completed_courses
where student_id = p_student_id;
for i in 1..T.count loop
pipe row (T(i));
end loop;
select ah_obj_type(student_id,course_code,'P')
bulk collect
into T
from trans_courses
where student_id = p_student_id;
for i in 1..T.count loop
pipe row (T(i));
end loop;
return;
end;
SQL Fiddle.
Thanks a_horse_with_no_name for pointing out that doing the multiple selects one at a time will probably be slower. I was able to reduce the execution time by filtering each select by student_id and then union-ing (rather than union-ing everything then filtering). On the data set I'm working with this solution was the fastest taking less than 1/10 of a second...
create or replace function
academic_history(p_student_id number)
return ah_tab_type
is
T ah_tab_type;
begin
select ah_obj_type(student_id,course_code,grade)
bulk collect
into T
from (
select student_id,course_code,grade
from completed_courses
where student_id = p_student_id
union
select student_id,course_code,'P'
from trans_courses
where student_id = p_student_id);
return T;
end;
/
select *
from table(academic_history(1));
and this took 2-3 seconds to execute...
create view vw_academic_history
select student_id,course_code,grade
from completed_courses
union
select student_id,course_code,'P'
from trans_courses;
select *
from vw_academic_history
where student_id = 1;
SQLFiddle.
My problem isn't overly complicated, but I am a newbie to PL/SQL.
I need to make a selection from a COMPANIES table based on certain conditions. I then need to loop through these and convert some of the fields into a different format (I have created functions for this), and finally use this converted version to join to a reference table to get the score variable I need. So basically:
select id, total_empts, bank from COMPANIES where turnover > 100000
loop through this selection
insert into MY_TABLE (select score from REF where conversion_func(MY_CURSOR.total_emps) = REF.total_emps)
This is basically what I am looking to do. It's slightly more complicated but I'm just looking for the basics and how to approach it to get me started!
Here's the basic syntax for cursor loops in PL/SQL:
BEGIN
FOR r_company IN (
SELECT
ID,
total_emps,
bank
FROM
companies
WHERE
turnover > 100000
) LOOP
INSERT INTO
my_table
SELECT
score
FROM
ref_table
WHERE
ref.total_emps = conversion_func( r_company.total_emps )
;
END LOOP;
END;
/
You don't need to use PL/SQL to do this:
insert into my_table
select score
from ref r
join companies c
on r.total_emps on conversion_func(c.total_emps)
where c.turnover > 100000
If you have to do this in a PL/SQL loop as asked, then I'd ensure that you do as little work as possible. I would, however, recommend bulk collect instead of the loop.
begin
for xx in ( select conversion_func(total_emps) as tot_emp
from companies
where turnover > 100000 ) loop
insert into my_table
select score
from ref
where total_emps = xx.tot_emp
;
end loop;
end;
/
For either method you need one index on ref.total_emps and preferably one on companies.turnover