I'm trying to create a function that compares 2 collections with each other. Let me explain what I'm trying to do first: I have a table for accounts and a table with teachers. I want to use this function to see if there are any accounts that aren't linked to a teacher and delete them. I have most of the function done, but I can't seem to figure out how to delete the account that isn't linked from the database. Does anyone have any ideas? Here's my code:
declare
type type_coll_accnr is table of account.account_id%type;
type type_coll_teachernr is table of teacher.teacher_id%type;
t_teachernr type_coll_teachernr;
t_accnr type_coll_accnr;
begin
select account_account_id
bulk collect into t_teachernr
from teacher;
select account_id
bulk collect into t_accnr
from account
where acces = 'Teacher';
for i_counter IN 1 .. t_teachernr.count
loop
if t_teachernr(i_counter) member of t_accnr then
dbms_output.put_line(t_accnr(i_counter));
else
delete from account where account_id = t_accnr(i_counter);
-- It should delete the account here, but I have no clue how.
end if;
end loop;
end;
I had to translate all of this, so please let me know if I missed something. I'm also pretty new to PL/SQL, so I know this might look like a stupid question!
Ok, I was right - loop should be on t_accnr list. And it should checks if current value from t_accnr is member of t_teachernr.
I create tables and checked it.
t_accnr is large, but it is not a problem
declare
type type_coll_accnr is table of account.account_id%type;
type type_coll_teachernr is table of teacher.teacher_id%type;
t_teachernr type_coll_teachernr;
t_accnr type_coll_accnr;
begin
select account_account_id
bulk collect into t_teachernr
from teacher;
select account_id
bulk collect into t_accnr
from account where acces = 'Teacher';
for i_counter IN 1 .. t_accnr.count
loop
if t_accnr(i_counter) member of t_teachernr then
dbms_output.put_line(t_accnr(i_counter));
else
dbms_output.put_line('delete from account where account_id ='|| t_accnr(i_counter));
delete from account where account_id = t_accnr(i_counter);
-- It should delete the account here, but I have no clue how.
end if;
end loop;
end;
Hope it helps
Related
I have a standard DB procedure to drop indexes on a table (code below) created in user1 schema. When run as user2 the "execute immediate 'drop index" fails with ORA-01418: specified index does not exist.
I don't understand how the index can be returned by the cursor, but not exist when the drop command is run.
Can someone help explain the privs user2 needs on the user1 table/indexes and what AUTHID should be used?
create or replace procedure dropIndexes(pTableName IN VARCHAR2) AUTHID
CURRENT_USER IS
CURSOR c1(pTableName VARCHAR2) IS
select index_name
from all_indexes
where table_name = pTableName;
BEGIN
FOR c1Rec IN c1(pTableName) LOOP
execute immediate 'drop index user1.'||c1Rec.index_name;
END LOOP;
END dropIndexes;
I wonder why not construct the SQL using the owner field of all_indexes. Better yet, push user1 into the query so we only get the index names that belong to user1.
How to fetch all the column from the table and store into the collection variable.(as single variable)
Hi guys,
please help me to write code among these questions in oracle plsql
"How to fetch all the column from the table and store into the collection variable.(as single variable" - What if your table has millions/billions of rows? You want to flood your PGA memory!
You can use Bulk collect with a limit to fetch a limited number of rows and process them, when processing is complete you may store them somewhere and fetch the next set of rows.
By default, the optimizer puts Limit 100 after the BULK COLLECT clause. The PLSQL_OPTIMIZE_LEVEL needs to be set at level 2 or more.
Here is an example
DECLARE
TYPE yourtable_nt IS TABLE OF yourtable%rowtype;
tab yourtable_nt;
CURSOR cur IS SELECT * FROM yourtable;
BEGIN
OPEN cur;
LOOP
FETCH cur BULK COLLECT INTO tab LIMIT 100; -- FOR EVERY FETCH DATA IS STORED IN tab FROM INDEX 1
FOR i in 1..tab.COUNT LOOP
Null;
-- PROCESS YOUR COLLECTION DATA
END LOOP;
EXIT WHEN cur%NOTFOUND;
END LOOP;
CLOSE cur;
EXCEPTION
WHEN OTHERS THEN
IF cur%ISOPEN THEN
CLOSE cur;
END IF;
END;
/
You may need to find a sweet number for BULK COLLECT LIMIT for your table.
If your table has lots of rows try different numbers for LIMIT otherwise the difference in execution time is insignificant.
The question is too vague to offer a certain answer but this code is the solution to one interpretation of it: "how to populate a PL/SQL collection which matches a table's projection?"
declare
--
-- collection of records which match EMP table
type emp_nt is table of emp%rowtype;
--
-- variable instance of that type
emp_recs emp_nt;
begin
-- use BULK COLLECT clause to populate the collection with records
select e.*
bulk collect into emp_recs
from emp e
where e.deptno = 20;
end;
Hi I am a newbie to Oracle. I have a query that I need to run against all the schemas in database lets say (scott,hr,exampleetc) that has the table transaction in the database.
Can some one help what is the best way to do it ?? I have about 30 schemas in database I can't do it by running this against all schemas as it is time consuming..
I was thinking a plsql will be the best way to do it but I am not enough knowledgeable to do this myself ..
query example:
select sum(amount)
from abc.transaction t1
where t1.payment_method ='transfer'
and TO_char(t1.result_time_stamp,'MONTH') = TO_char(sysdate,'MONTH')
order by t1.time_stamp asc;
Thanks is advance for help ..
I'm not sure what kind of queries you want to run, if they need to aggregate data from all tables at once or if you want to look through and run a command on each, but here's something that should get you started. It's a cursor that will find all tables named "transaction". If these are going to be ad-hoc queries, I would just copy the table name output from the below cursor, write a basic query and save it somewhere or make it into a procedure of some sort. Then just modify and re-use it in the future.
DECLARE
CURSOR c1
IS
SELECT owner, table_name
FROM all_tables
WHERE upper(table_name) = 'TRANSACTION';
BEGIN
FOR c1rec in c1
LOOP
dbms_output.put_line(c1rec.owner||'.'||c1rec.table_name);
END LOOP;
END;
I have the following tables: replies and messages. I would like to imitate Facebook's behavior: when a message is deleted, all related replies are deleted as well. My tables look like this:
REPLIES
messageId replyId
6b61d107-dff3-4374-a3a2-75ac7478a2f2 865c873d-0210-482a-b8bd-371c4f07f0cf
MESSAGES
id body
865c873d-0210-482a-b8bd-371c4f07f0cf this is the reply
6b61d107-dff3-4374-a3a2-75ac7478a2f2 this is the message
I have created one first trigger, which works, which deletes the related lines in Replies when a message is deleted. I now would like to create another trigger which would delete the related message every time a line in Replies is deleted. Right now the replies become messages of their own, which doesn't make sense. Here's the second trigger:
CREATE TRIGGER TRG_DEL_MESSAGES
ON Replies
FOR DELETE
AS
DELETE FROM Messages WHERE id = (SELECT replyId FROM DELETED)
Which brings the following error when I try to delete something:
Maximum stored procedure, function, trigger, or view nesting level exceeded (limit 32).
Apparently there's an infinite loop going and I think it is because the DELETED table is filled by data from the first trigger (which provoked the second). But really I'm not sure and I'd appreciate some help. Thanks!
Will something like this work for you? Remove the recursion by just using a single trigger on Messages, that calculates all of the related messages up front, so you should get at most one nested call? It deletes all the related messages from both messages and replies
CREATE TRIGGER TRG_DEL_REPLIES
ON [Messages]
FOR DELETE
AS
BEGIN
DECLARE #Related TABLE (MessageId uniqueidentifier)
--get all related messages so that we don't recurse
BEGIN
WITH AllReplies (MessageId)
AS
(
--Anchor
SELECT D.MessageId
FROM Deleted D
UNION ALL
--Recurse
SELECT R.ReplyId
FROM AllReplies AR
JOIN Replies R
ON AR.MessageId = R.MessageId
)
INSERT INTO #Related
SELECT *
FROM AllReplies
END
--delete the replies
DELETE R
FROM Replies R
JOIN #Related REL
ON R.MessageId = REL.MessageId
--delete the messages
DELETE M
FROM [Messages] M
JOIN #Related REL
ON REL.MessageId = M.MessageId
LEFT
JOIN DELETED D
ON REL.MessageId = D.MessageId
WHERE D.MessageId IS NULL
END
To translate this into a stored proc as you wanted, i would do this, rather than a loop that does multiple separate deletes:
CREATE PROCEDURE DeleteMessageWithReplies(#messageId uniqueidentifier)
AS
BEGIN
DECLARE #Related TABLE (MessageId uniqueidentifier)
--get all related messages
BEGIN
WITH AllReplies (MessageId)
AS
(
--Anchor
SELECT #messageId
UNION ALL
--Recurse
SELECT R.ReplyId
FROM AllReplies AR
JOIN Replies R
ON AR.MessageId = R.MessageId
)
INSERT INTO #Related
SELECT *
FROM AllReplies
END
--delete the replies
DELETE R
FROM Replies R
JOIN #Related REL
ON R.MessageId = REL.MessageId
--delete the messages that haven't already been deleted
DELETE M
FROM [Messages] M
JOIN #Related REL
ON REL.MessageId = M.MessageId
END
Thanks for your help Fergus, I appreciate it. However, as #Ben pointed, a stored procedure is easier and simpler to code. This is what I've just written, it could probably be improved but at least it works.
EXEC('CREATE PROCEDURE deleteMessageWithReplies(#messageId uniqueidentifier)
AS
BEGIN
DECLARE #repliesCount int
SELECT #repliesCount = (SELECT COUNT(*) FROM Replies WHERE messageId=#messageId)
DECLARE #cpt int
SET #cpt = 0
DELETE FROM Messages WHERE id = #messageId
WHILE #cpt < #repliesCount
BEGIN
DECLARE #replyId uniqueidentifier
SELECT #replyId = (SELECT TOP 1 replyId FROM Replies WHERE messageId=#messageId)
DELETE FROM Replies WHERE replyId = #replyId
DELETE FROM Messages WHERE id=#replyId
SET #cpt = #cpt+1
END
END')
I would recommend either generating all the deletes at once in a trigger, as Fergus Bown suggested, or moving your delete logic to a stored procedure call. In our application, we use the stored procedure approach for all CRUD operations (create, read, update, delete).
The downside is that a rookie support DBA might goof if they use SQL to delete a single Reply without deleting all the other Messages associated with it. But such a DBA ought to know to use the stored procedures instead (or get the SQL right in the first place).
I would like to ask you how would you increase the performance on Insert cursor in this code?
I need to use dynamic plsql to fetch data but dont know how to improve the INSERT in best way. like Bulk Insert maybe?
Please let me know with code example if possible.
// This is how i use cur_handle:
cur_HANDLE integer;
cur_HANDLE := dbms_sql.open_cursor;
DBMS_SQL.PARSE(cur_HANDLE, W_STMT, DBMS_SQL.NATIVE);
DBMS_SQL.DESCRIBE_COLUMNS2(cur_HANDLE, W_NO_OF_COLS, W_DESC_TAB);
LOOP
-- Fetch a row
IF DBMS_SQL.FETCH_ROWS(cur_HANDLE) > 0 THEN
DBMS_SQL.column_value(cur_HANDLE, 9, cont_ID);
DBMS_SQL.COLUMN_VALUE(cur_HANDLE, 3, proj_NR);
ELSE
EXIT;
END IF;
Insert into w_Contracts values(counter, cont_ID, proj_NR);
counter := counter + 1;
END LOOP;
You should do database actions in sets whenever possible, rather than row-by-row inserts. You don't tell us what CUR_HANDLE is, so I can't really rewrite this, but you should probably do something like:
INSERT INTO w_contracts
SELECT ROWNUM, cont_id, proj_nr
FROM ( ... some table or joined tables or whatever... )
Though if your first value there is a primary key, it would probably be better to assign it from a sequence.
Solution 1) You can populate inside the loop a PL/SQL array and then just after the loop insert the whole array in one step using:
FORALL i in contracts_tab.first .. contracts_tab.last
INSERT INTO w_contracts VALUES contracts_tab(i);
Solution 2) if the v_stmt contains a valid SQL statement you can directly insert data into the table using
EXECUTE IMMEDIATE 'INSERT INTO w_contracts (counter, cont_id, proj_nr)
SELECT rownum, 9, 3 FROM ('||v_stmt||')';
"select statement is assembled from a website, ex if user choose to
include more detailed search then the select statement is changed and
the result looks different in the end. The whole application is a web
site build on dinamic plsql code."
This is a dangerous proposition, because it opens your database to SQL injection. This is the scenario in which Bad People subvert your parameters to expand the data they can retrieve or to escalate privileges. At the very least you need to be using DBMS_ASSERT to validate user input. Find out more.
Of course, if you are allowing users to pass whole SQL strings (you haven't provided any information regarding the construction of W_STMT) then all bets are off. DBMS_ASSERT won't help you there.
Anyway, as you have failed to give the additional information we actually need, please let me spell it out for you:
will the SELECT statement always have the same column names from the same table name, or can the user change those two?
will you always be interested in the third and ninth columns?
how is the W_STMT string assembled? How much control do you have over its projection?