Am currently working on an oracle PL SQL procedure to list the project numbers, titles and names of employees who work on a project.
I am able to write a procedure that is able to get this info. However, I can only draw one output at a time as such:
1001 Computation
Alvin
Peter
How can I change my code to output all of the entries at the same time while printing them as such:
[Fragment Example][Showing only 1st 3 entries]
1001 Computation: Alvin, Peter
1002 Study methods: Bob, Robert
1003 Racing car: Robert
[Current Code]
create or replace procedure PROJECTGROUPS(projectid IN WorksOn.P#%TYPE)
is
PID Project.P#%TYPE;
PNAME Project.PTitle%TYPE;
ENAME Employee.Name%TYPE;
CURSOR query is
select Employee.Name from Employee
left outer join WorksOn On Employee.E# = WorksOn.E#
where WorksOn.P# = projectid
order by Employee.Name ASC
fetch first 20 rows only;
--
--
begin
select P#, PTitle into PID, PNAME from project where project.p# = projectid;
DBMS_OUTPUT.PUT_LINE(PID || ' ' || PNAME);
--
open query;
loop
fetch query into ENAME;
if query%NOTFOUND then exit;
end if;
DBMS_OUTPUT.PUT_LINE(ENAME);
end loop;
close query;
end PROJECTGROUPS;
You can directly use it in a single query and loop through it as follows:
CREATE OR REPLACE PROCEDURE PROJECTGROUPS (
PROJECTID IN WORKSON.P#%TYPE
) IS
BEGIN
FOR I IN (
SELECT PROJECT_NAME,
PTITLE,
LISTAGG(EMP_NAME,
',') WITHIN GROUP(
ORDER BY EMP_NAME
) AS EMP_NAMES
FROM (
SELECT P.P# PROJECT_NAME,
P.PTITLE,
E.EMPLOYEE.NAME EMP_NAME,
ROW_NUMBER() OVER(
PARTITION BY P.P#
ORDER BY E.NAME
) AS RN
FROM PROJECT P
JOIN WORKSON W
ON W.P# = P.P#
JOIN EMPLOYEE E
ON E.E# = W.E#
WHERE P.P# = PROJECTID
)
WHERE RN <= 20
GROUP BY PROJECT_NAME,
PTITLE
) LOOP
DBMS_OUTPUT.PUT_LINE(I.PROJECT_NAME
|| ' '
|| I.PTITLE
|| ' : '
|| I.EMP_NAMES);
END LOOP;
END PROJECTGROUPS;
Also, your query is not actually outer joined as you have used the condition in the WHERE clause.
left outer join WorksOn On Employee.E# = WorksOn.E# -- you want outer join
where WorksOn.P# = projected -- but the outer join is converted to inner join
Related
I am attempting to perform a trend analysis in PLSQL using a for loop nested inside of a cursor for loop. The goal is to return an actor between the years (2000-2013) who have acted in at least 8 movies within a 5 year window.
For example, a desired output would be: Wahlberg, Mark played in 10 movies between 2009 and 2013.
Here is the error I receive:
Here is the code i'm working with so far:
DECLARE
t movie.yr%TYPE;
actor_id actor.id%TYPE;
total INTEGER;
name actor.name%TYPE;
CURSOR c_actor IS
select *
from (select actor.name AS name, count(movie.title) AS total
from actor, movie, casting
where movie.id = casting.movie_id
and actor.id = casting.actor_id
and movie.yr >= 2000 and movie.yr <=2013
group by actor.name
order by count(movie.title) DESC)
where rownum <= 10;
BEGIN
for v_actor in c_actor
LOOP
for t in 2000 .. 2009
LOOP
select name, total
into name, total
from actor, movie
where movie.yr between t and t+4
and actor_id = v_actor.actor_id
and total >= 8
group by name;
dbms_output.put_line(name||' played in '||total||' movies between '||t||' and '||t+4);
END LOOP;
END LOOP;
END;
It seems you overcomplicated it. This should work:
begin
for v_actor in (select a.name, count(*) total
from actor a join casting c on a.id = c.actor_id
join movie m on m.id = c.movie_id
where m.yr between 2000 and 2013
group by a.name
having count(*) >= 8
)
loop
dbms_output.put_line(v_actor.name ||' acted ' || v_actor.total ||' times');
end loop;
end;
I think you created a question a couple of hours before (and deleted it), making same mistakes. For example: you created a variable named total, and - at the same time - have it in cursor's select statement. You want to display value fetched by the cursor, not the variable itself, unless cursor fetches into that variable - but that's done when you explicitly open/fetch from the cursor, not within the cursor FOR loop. With it, you use a cursor variable and use it to display those values.
you dont get actor_id in your cursor query.
select *
from
(
select a.name AS name, count(m.title) AS total
from actor a
join casting c
on a.id = c.actor_id
join movie m
on m.id = c.movie_id
where m.yr >= 2000
and m.yr <=2013
group by actor.name
order by count(movie.title) desc
)
where rownum <= 10;
Here is what I am supposed to do. Assume that the tables are created, and all the columns are correctly named
"Using stored procedures and cursors, display the location (including street, zip code, city and country) of the managers with job id of either IT_PROG or SA_MAN and with salary greater than 3000".
Here is the code I have written so far but the sql statement for the cursor doesn't seem to want to work. For the DEPARTMENTS Table the FK's are MANAGER_ID and LOCATION_ID, for the EMPLOYEES Table the FK is JOB_ID and the LOCATIONS table has no FK. All the primary keys are set
Here is the code:
create or replace procedure mgtLocation
is
cursor getLoc is
select LOCATIONS.STREET_ADDRESS, LOCATIONS.POSTAL_CODE, LOCATIONS.CITY,
LOCATIONS.COUNTRY, LOCATIONS.LOCATIONS_ID, LOCATIONS.LOCATIONS_ID
from LOCATIONS
inner join DEPARTMENTS on DEPARTMENTS.MANAGER_ID = EMPLOYEES.EMPLOYEE_ID
inner join LOCATIONS on LOCATIONS.LOCATION_ID = DEPARTMENTS.LOCATION_ID
where EMPLOYEES.Job_ID in (select Job_ID from EMPLOYEES where Job_ID = 'IT_PROG' or Job_ID = 'SA_MAN' and SALARY > 3000);
EmpLoc getLoc%rowtype;
begin
dbms_output.put_line('=================');
open getLoc;
loop
fetch getLoc into EmpLoc;
EXIT WHEN getLoc%NOTFOUND;
dbms_output.put_line('Street: ' || EmpLoc.STREET_ADDRESS ||
' Zip Code: ' || EmpLoc.POSTAL_CODE ||
' City: ' || EmpLoc.CITY ||
' Country: ' || EmpLoc.COUNTRY);
end loop;
dbms_output.put_line('=================');
close getLoc;
end;
/
execute mgtLocation;
I get an error for the inner joins and I cannot seem to figure out how to fix them in order for this to work.
You could try this:
cursor getLoc is
select LOCATIONS.STREET_ADDRESS,
LOCATIONS.POSTAL_CODE, LOCATIONS.CITY,
LOCATIONS.COUNTRY, LOCATIONS.LOCATIONS_ID,
LOCATIONS.LOCATIONS_ID
from LOCATIONS
inner join DEPARTMENTS on
DEPARTMENTS.LOCATION_ID = LOCATIONS.LOCATION_ID
inner join EMPLOYEES on
EMPLOYEES.EMPLOYEE_ID =
DEPARTMENTS.MANAGER_ID
where (EMPLOYEES.Job_ID = 'IT_PROG' or
EMPLOYEES.Job_ID = 'SA_MAN')
and SALARY > 3000;
I want to write a SQK script with insert query in Oracle where one of the value will be fetched from cursor and rest all will be retrieved from table.
For example, consider Employee table:
Emp_No | Emp_Name
1 | AAA
...........
I am reading the table into a cursor.
Cursor c1 is select emp_no, emp_name from employee;
I am iterating the cursor and adding to a table along with information from another table.
for empCur in c1
loop
insert into employee_info(emp_no, emp_name, address, age, ... ) values (empCur.emp_no, empCur.emp_name, select t.address, t.age, ... from employee_temp_table t where t.emp_no=empCur.emp_no)
end loop;
Is my script valid? If not is there any other way to achieve it? Since few values are in cursor and few are in another table I am not sure how to handle this.
Your script isn't correct because this
select t.address, t.age, ... from employee_temp_table t where t.emp_no=empCur.emp_no
is not a valid expression. You may use a scalar subquery (one-row, one-column subquery) only, like this:
insert into t1(col1, col2, col3) values (1, (select col1 from t2), (select col2 from t2));
Or you may try insert from select:
for empCur in c1 loop
insert into employee_info(emp_no, emp_name, address, age, ... )
select empCur.emp_no, empCur.emp_name, t.address, t.age, ... from employee_temp_table t where t.emp_no=empCur.emp_no;
end loop;
If you want to use the cursor, why not just join the tables inside the cursor?
for empCur in ( select e.emp_no, e.emp_name, t.address, t.age ...
from employee e join employee_temp_table t on ( t.emp_no = e.emp_no )
) loop
insert into employee_info(...) values ( empCur.emp_no, ...);
end loop;
Or with a sql insert: (if you can choose sql over pl/sql - T Kyte says do it)
insert into employee_info
select e.emp_no, e.emp_name, t.address, t.age ...
from employee e join employee_temp_table t on ( t.emp_no = e.emp_no );
I have column name in the table:
select LASTNAME
FROM dbo.Employees
WHERE LASTNAME = 'Smith'
and output of the above query is
LASTNAME
Smith
I want output like
LASTNAME
S
m
i
t
h
With a little help of a numbers table.
SQL Server:
select substring(E.LASTNAME, N.N, 1) as LASTNAME
from Employees as E
inner join Numbers as N
on N.N between 1 and len(E.LASTNAME)
order by E.LASTNAME, N.N
Oracle:
select substr(E.LASTNAME, N.N, 1) as LASTNAME
from Employees E
inner join Numbers N
on N.N between 1 and length(E.LASTNAME)
order by E.LASTNAME, N.N;
SQL Fiddle
In SQL Server, if you don't have a table of numbers, then you can use CTE to generate the list:
;with cte (id, start, numb) as
(
select id, 1 start, len(lastname) numb
from employees
union all
select id, start + 1, numb
from cte
where start < numb
)
select c.id, substring(e.lastname, c.start, 1)
from employees e
inner join cte c
on c.start between 1 and len(e.lastname)
and c.id = e.id
order by e.id, e.lastname;
See SQL Fiddle With Demo
----- function for splitting
CREATE FUNCTION [dbo].[SPLIT_Test] (
#string VARCHAR(8000) )
RETURNS #table TABLE (strval VARCHAR(8000))
AS
BEGIN
IF LEN(#string)>=1
BEGIN
DECLARE #fulllen int=LEN(#string),#lastlen int=0
WHILE #fulllen>#lastlen
BEGIN
INSERT INTO #table
SELECT SUBSTRING(#string,1,1)
SET #string= RIGHT(#String, LEN(#String) - 1)
SET #lastlen=#lastlen+1
END
RETURN
END
RETURN
END
---- query
GO
DECLARE #name table(name varchar(500),row int IDENTITY(1,1))
INSERT INTO #name
select LASTNAME
FROM dbo.Employees
WHERE LASTNAME = 'Smith'
DECLARE #Finalname table(name varchar(50))
DECLARE #startrow int =(SELECT MAX(row) FROM #name)
,#endrow int =1
WHILE #startrow>=#endrow
BEGIN
INSERT INTO #Finalname
Select strval from [dbo].[SPLIT_test] ((SELECT name FROM #name where row=#endrow)) WHERE strval<>''-- removing empty spaces
SET #endrow=#endrow+1
END
SELECT * FROM #Finalname
Is there a way to make selecting random rows faster in oracle with a table that has million of rows. I tried to use sample(x) and dbms_random.value and its taking a long time to run.
Thanks!
Using appropriate values of sample(x) is the fastest way you can. It's block-random and row-random within blocks, so if you only want one random row:
select dbms_rowid.rowid_relative_fno(rowid) as fileno,
dbms_rowid.rowid_block_number(rowid) as blockno,
dbms_rowid.rowid_row_number(rowid) as offset
from (select rowid from [my_big_table] sample (.01))
where rownum = 1
I'm using a subpartitioned table, and I'm getting pretty good randomness even grabbing multiple rows:
select dbms_rowid.rowid_relative_fno(rowid) as fileno,
dbms_rowid.rowid_block_number(rowid) as blockno,
dbms_rowid.rowid_row_number(rowid) as offset
from (select rowid from [my_big_table] sample (.01))
where rownum <= 5
FILENO BLOCKNO OFFSET
---------- ---------- ----------
152 2454936 11
152 2463140 32
152 2335208 2
152 2429207 23
152 2746125 28
I suspect you should probably tune your SAMPLE clause to use an appropriate sample size for what you're fetching.
Start with Adam's answer first, but if SAMPLE just isn't fast enough, even with the ROWNUM optimization, you can use block samples:
....FROM [table] SAMPLE BLOCK (0.01)
This applies the sampling at the block level instead of for each row. This does mean that it can skip large swathes of data from the table so the sample percent will be very rough. It's not unusual for a SAMPLE BLOCK with a low percentage to return zero rows.
Here's the same question on AskTom:
http://asktom.oracle.com/pls/apex/f?p=100:11:0::::P11_QUESTION_ID:6075151195522
If you know how big your table is, use sample block as described above. If you don't, you can modify the routine below to get however many rows you want.
Copied from: http://asktom.oracle.com/pls/apex/f?p=100:11:0::::P11_QUESTION_ID:6075151195522#56174726207861
create or replace function get_random_rowid
( table_name varchar2
) return urowid
as
sql_v varchar2(100);
urowid_t dbms_sql.urowid_table;
cursor_v integer;
status_v integer;
rows_v integer;
begin
for exp_v in -6..2 loop
exit when (urowid_t.count > 0);
if (exp_v < 2) then
sql_v := 'select rowid from ' || table_name
|| ' sample block (' || power(10, exp_v) || ')';
else
sql_v := 'select rowid from ' || table_name;
end if;
cursor_v := dbms_sql.open_cursor;
dbms_sql.parse(cursor_v, sql_v, dbms_sql.native);
dbms_sql.define_array(cursor_v, 1, urowid_t, 100, 0);
status_v := dbms_sql.execute(cursor_v);
loop
rows_v := dbms_sql.fetch_rows(cursor_v);
dbms_sql.column_value(cursor_v, 1, urowid_t);
exit when rows_v != 100;
end loop;
dbms_sql.close_cursor(cursor_v);
end loop;
if (urowid_t.count > 0) then
return urowid_t(trunc(dbms_random.value(0, urowid_t.count)));
end if;
return null;
exception when others then
if (dbms_sql.is_open(cursor_v)) then
dbms_sql.close_cursor(cursor_v);
end if;
raise;
end;
/
show errors
Below Solution to this question is not the exact answer but in many scenarios you try to select a row and try to use it for some purpose and then update its status with "used" or "done" so that you do not select it again.
Solution:
Below query is useful but that way if your table is large, I just tried and see that you definitely face performance problem with this query.
SELECT * FROM
( SELECT * FROM table
ORDER BY dbms_random.value )
WHERE rownum = 1
So if you set a rownum like below then you can work around the performance problem. By incrementing rownum you can reduce the possiblities. But in this case you will always get rows from the same 1000 rows. If you get a row from 1000 and update its status with "USED", you will almost get different row everytime you query with "ACTIVE"
SELECT * FROM
( SELECT * FROM table
where rownum < 1000
and status = 'ACTIVE'
ORDER BY dbms_random.value )
WHERE rownum = 1
update the rows status after selecting it, If you can not update that means another transaction has already used it. Then You should try to get a new row and update its status. By the way, getting the same row by two different transaction possibility is 0.001 since rownum is 1000.
Someone told sample(x) is the fastest way you can.
But for me this method works slightly faster than sample(x) method.
It should take fraction of the second (0.2 in my case) no matter what is the size of the table. If it takes longer try to use hints (--+ leading(e) use_nl(e t) rowid(t)) can help
SELECT *
FROM My_User.My_Table
WHERE ROWID = (SELECT MAX(t.ROWID) KEEP(DENSE_RANK FIRST ORDER BY dbms_random.value)
FROM (SELECT o.Data_Object_Id,
e.Relative_Fno,
e.Block_Id + TRUNC(Dbms_Random.Value(0, e.Blocks)) AS Block_Id
FROM Dba_Extents e
JOIN Dba_Objects o ON o.Owner = e.Owner AND o.Object_Type = e.Segment_Type AND o.Object_Name = e.Segment_Name
WHERE e.Segment_Name = 'MY_TABLE'
AND(e.Segment_Type, e.Owner, e.Extent_Id) =
(SELECT MAX(e.Segment_Type) AS Segment_Type,
MAX(e.Owner) AS Owner,
MAX(e.Extent_Id) KEEP(DENSE_RANK FIRST ORDER BY Dbms_Random.Value) AS Extent_Id
FROM Dba_Extents e
WHERE e.Segment_Name = 'MY_TABLE'
AND e.Owner = 'MY_USER'
AND e.Segment_Type = 'TABLE')) e
JOIN My_User.My_Table t
ON t.Rowid BETWEEN Dbms_Rowid.Rowid_Create(1, Data_Object_Id, Relative_Fno, Block_Id, 0)
AND Dbms_Rowid.Rowid_Create(1, Data_Object_Id, Relative_Fno, Block_Id, 32767))
Version with retries when no rows returned:
WITH gen AS ((SELECT --+ inline leading(e) use_nl(e t) rowid(t)
MAX(t.ROWID) KEEP(DENSE_RANK FIRST ORDER BY dbms_random.value) Row_Id
FROM (SELECT o.Data_Object_Id,
e.Relative_Fno,
e.Block_Id + TRUNC(Dbms_Random.Value(0, e.Blocks)) AS Block_Id
FROM Dba_Extents e
JOIN Dba_Objects o ON o.Owner = e.Owner AND o.Object_Type = e.Segment_Type AND o.Object_Name = e.Segment_Name
WHERE e.Segment_Name = 'MY_TABLE'
AND(e.Segment_Type, e.Owner, e.Extent_Id) =
(SELECT MAX(e.Segment_Type) AS Segment_Type,
MAX(e.Owner) AS Owner,
MAX(e.Extent_Id) KEEP(DENSE_RANK FIRST ORDER BY Dbms_Random.Value) AS Extent_Id
FROM Dba_Extents e
WHERE e.Segment_Name = 'MY_TABLE'
AND e.Owner = 'MY_USER'
AND e.Segment_Type = 'TABLE')) e
JOIN MY_USER.MY_TABLE t ON t.ROWID BETWEEN Dbms_Rowid.Rowid_Create(1, Data_Object_Id, Relative_Fno, Block_Id, 0)
AND Dbms_Rowid.Rowid_Create(1, Data_Object_Id, Relative_Fno, Block_Id, 32767))),
Retries(Cnt, Row_Id) AS (SELECT 1, gen.Row_Id
FROM Dual
LEFT JOIN gen ON 1=1
UNION ALL
SELECT Cnt + 1, gen.Row_Id
FROM Retries
LEFT JOIN gen ON 1=1
WHERE Retries.Row_Id IS NULL AND Retries.Cnt < 10)
SELECT *
FROM MY_USER.MY_TABLE
WHERE ROWID = (SELECT Row_Id
FROM Retries
WHERE Row_Id IS NOT NULL)
Can you use pseudorandom rows?
select * from (
select * from ... where... order by ora_hash(rowid)
) where rownum<100