I am trying to make a simple project for college assignment. In that,When I am trying to delete a record from a stored procedure in MySQL, that delete statement is deleting all the records.
Here is the code :
DELIMITER $$
CREATE DEFINER=`root`#`localhost` PROCEDURE
`removebus`(in busnumber int,out message varchar(255))
BEGIN
delete from fare where busnumber = busnumber;
delete from bus where busnumber = busnumber;
set message = 'success';
else
set message = 'not found';
end if;
END
And I am executing like call removebus(1,#message);.
When I am trying to only execute the delete statement from command window, then it is deleting one record only but when I executed the call statement, all the records are deleted from my table. Just to add, busnumber is primary key of bus table. I am not able to understand why.
Well, as far as I can see, busnumber is always equals busnumber... You might as well write "1 = 1".
Change the variable name - that should work for you.
Two things. First delete is not working as truncate table. It might have the same results, but the performance is much, much different. delete logs all its transactions; truncate table does not.
Second, get into the habit of naming parameter arguments with a special prefix, such as:
CREATE DEFINER=`root`#`localhost` PROCEDURE
`removebus`(in in_busnumber int, out out_message varchar(255))
. . .
If you don't get into this habit, you are likely to continue making the same mistake for a long time (at least, that is my experience).
You are running
delete from fare where busnumber = busnumber;
for every row in the table busnumber will equal itself, therefor they will all be deleted.
Try changing the input variable to something that doesn't clash with the column name.
Related
I have two tables, one containing candidate information and the other containing the personal information of everyone considered as a person in the database. However, a candidate should not be deleted from the database, their non-personal data should be kept for record. The deletion of a candidate must result in the deletion of his personal data only. So i am trying to write a before delete trigger on the candidate table that takes the id of the candidate we are trying to delete and sets all their personal info to null. When i run the trigger, it only returns a msg saying candidate was deleted however when i check the personal info table, the row for that candidate still has all the information. what might i be doing wrong?
here is the code for the trigger:
USE `agence_interim`;
DELIMITER $$
DROP TRIGGER IF EXISTS agence_interim.candidat_BEFORE_DELETE$$
USE `agence_interim`$$
CREATE DEFINER = CURRENT_USER TRIGGER `agence_interim`.`candidat_BEFORE_DELETE` BEFORE DELETE ON `candidat` FOR EACH ROW
BEGIN
DECLARE errorMessage VARCHAR(255);
Update personne SET personne.id_personne = null,
personne.nom = null,
personne.prenom = null,
personne.email = null,
personne.telephone =null,
personne.date_naissance =null,
personne.description = null
WHERE personne.id_personne = OLD.id_personne;
SET errorMessage = CONCAT('The personal information for candidate number ',
OLD.id_personne,
' has been deleted');
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = errorMessage;
END$$
DELIMITER ;
I'm afraid this is not possible using a trigger in MySQL.
Using a SIGNAL statement in the trigger body aborts the DELETE action that spawned the trigger, but when the trigger is aborted, this also cancels all subordinate changes executed within the trigger body (and also any actions performed by triggers spawned by those changes, etc.).
There is no support for "instead of" triggers in MySQL. Either all changes succeed, or none of them succeed.
To do what you want, you can't use DELETE from the client.
You must use UPDATE.
I have inherited a MySQL InnoDB table with around 500 million rows. The table has IP numbers and the name of the ISP to which that number belongs, both as strings.
Sometimes, I need to update the name of an ISP to a new value, after company changes such as mergers or rebranding. But, because the table is so big, a simple UPDATE...WHERE statement doesn't work - The query usually times out, or the box runs out of memory.
So, I have written a stored procedure which uses a cursor to try and make the change one record at a time. When I run the procedure on a small sample table, it works perfectly. But, when I try to run it against the whole 500 million row table in production, I can see a temporary table gets created (because a /tmp/xxx.MYI and /tmp/xxx.MYD file appear). The temporary table file keeps growing in size until it uses all available disk space on the box (around 40 GB).
I'm not sure why this temporary table is necessary. Is the server trying to maintain some kind of rollback state? My real question is, can I change the stored procedure such that the temporary table is not created? I don't really care if some, but not all of the records get updated - I can easily add some reporting and just keep running the proc until no records are altered.
At this time, architecture changes are not really an option – I can't change the structure of the table, for example.
Thanks in advance for any help.
David
This is my stored proc;
DELIMITER $$
DROP PROCEDURE IF EXISTS update_isp;
CREATE PROCEDURE update_isp()
BEGIN
DECLARE v_finished INT DEFAULT 0;
DECLARE v_num VARCHAR(255) DEFAULT "";
DECLARE v_isp VARCHAR(255) DEFAULT "";
DECLARE ip_cursor CURSOR FOR
SELECT ip_number, isp FROM ips;
DECLARE CONTINUE HANDLER
FOR NOT FOUND SET v_finished = 1;
OPEN ip_cursor;
get_ip: LOOP
IF v_finished = 1 THEN
LEAVE get_ip;
END IF;
FETCH ip_cursor INTO v_num, v_isp;
IF v_isp = 'old name' THEN
UPDATE ips SET isp = 'new name' WHERE ip_number = v_num;
END IF;
END LOOP get_ip;
CLOSE ip_cursor;
END$$
DELIMITER ;
CALL update_isp();
I have also tried wrapping the update statement in a transaction. It didn't make any difference.
[EDIT] My assumption below, that a simple counting procedure does not create a temporary table, was wrong. The temporary table is still created, but it grows more slowly and the box does not run out of disk space before the procedure completes.
So the problem seems to be that any use of a cursor in a stored procedure results in a temporary table being created. I have no idea why, or if there is any way to prevent this.
If your update is essentially:
UPDATE ips
SET isp = 'new name'
WHERE isp = OLDNAME;
I am guessing that this update -- without the cursor -- will work better if you have an index on isp(isp):
create index idx_isp_isp on isp(isp);
Your original query should be fine once this index is created. There should be no performance issue updating a single row even in a very large table. The issue is in all likelihood finding the row, not updating it.
I don't think there is a solution to this problem.
From this page; http://spec-zone.ru/mysql/5.7/restrictions_cursor-restrictions.html
In MySQL, a server-side cursor is materialized into an internal
temporary table. Initially, this is a MEMORY table, but is converted
to a MyISAM table when its size exceeds the minimum value of the
max_heap_table_size and tmp_table_size system variables.
I misunderstood how cursors work. I assumed that my cursor functioned as a pointer to the underlying table. But, it seems MySQL must build the full result set first, and then give you a pointer to that. So, I don't really understand the benefits of cursors in MySQL. Thanks to everyone who tried to help.
David
If the table has some numerical index also you can specify a
WHERE myindex > 123 AND myindex < 456
in your update query and do that for a couple of intevals (with a loop for example) until the whole table is covered.
(sorry, my rep is too low to ask in the comment section, so I'll just post my guess-answer here to be able to comment on)
You could try to fake a numerical index with
SELECT ROW_NUMBER() as n, thetable.* FROM thetable ORDER BY oneofyourcolumns;
and then try what I suggested above.
I'm trying to create a database with history in mind (experience shows you'll have to do this one day or another).
I've asked here database-design-how-to-handle-the-archive-problem but there's no better anser than the link here.
My problem is about where to do the code and technically, how (MySQL gives me headaches). First I've started doing this in Php: before doing any insert, duplicate the record mark it as "obsolete" then modify the record.
But there's a dependency problem (manytomany and manytoone associations must be updated as well) which implies coding (one way or another) all the dependancies and updates that come with the tables (which is not acceptable).
So I'm thinking about doing all the work on the database server side. This would greatly simplify my Php code.
The problem is that I have to "archive" the current record before modifying it. To do so, the code must be in a trigger "before update".
Here's my code:
DELIMITER ;;
DROP TRIGGER IF EXISTS produit_trigger_update_before;
CREATE TRIGGER produit_trigger_update_before
BEFORE UPDATE ON produit
FOR EACH ROW BEGIN
/* */
INSERT INTO produit SET
id_origine = OLD.id_origine,
date_v_creation = OLD.date_v_creation,
date_v_start = OLD.date_v_debut,
date_v_end = NOW(),
...
last_record = OLD.last_record;
/* Dependancies : */
SET #last=LAST_INSERT_ID();
UPDATE categorie_produit SET id_produit=#last
WHERE id_produit = OLD.id;
UPDATE produit_attribut SET id_produit=#last
WHERE id_produit = OLD.id;
END;;
DELIMITER ;;
If I get this code working, all my problems are gone. But damn it, it's not working:
mysql> update produit set importance=3;
ERROR 1442 (HY000): Can't update table 'produit' in stored function/trigger because it is already used by statement which invoked this stored function/trigger.
mysql> Bye
In this page there's a working sample, which uses INSTEAD OF UPDATE clause in the trigger. MySQL doesn't seem to support this.
So my question is both conceptual (= have you any other "principle" that could work) and/or technical (= can you make this trigger work).
If I get this code working, all my problems are gone. But damn it, it's not working:
As a rule you can't have a trigger on table A trigger inserts into table A - since that could cause an endless loop. (Trigger mutation in Oracle terms)
Personally I would not do this using triggers. Triggers can do "audit logging" - but this is not what you want here.
I suggest you solve it programatically - either with a PHP function or a MySQL stored procedure (whatever your preference) that you call something like "ModifyProduit".
The code would then do basically what you have the trigger above do. (It might be easier to just have the code set date_v_end on the current row, and then insert a completly new row. That way you don't have to mess around with updating your referenced tables)
you can do history of a table with an auxiliary table like this (i've done this for many tables on mysql and the speed is very good):
table produit_history has the same structure as produit + 2 additional columns: "history_start DATETIME NOT NULL" and "history_stop DATETIME DEFAULT NULL".
there are 3 triggers on produit table:
AFTER INSERT: in this trigger there is a simple insert into produit_history of the same data with history_start = NOW() and history_stop = NULL (NULL means the current row is valid)
AFTER UPDATE: this trigger performs two queries. The first is un update like this:
UPDATE produit_history set history_stop = NOW() WHERE id_origine = OLD.id_origine AND history_stop IS NULL;
The second query is an insert identical to the one in the AFTER INSERT trigger.
AFTER DELETE: this triggers there is a simple update which is identical to the one in the AFTER UPDATE.
You can then query this history table and obtain snapshots at whatever time you're interested in with the following where condition:
WHERE (history_start <= "interesting_time" AND (history_stop IS NULL OR history_stop > "interesting_time"))
Is it possible to check whether a particular value in a column exists in other databases using trigger? These two databases are located inside the same MYSQL instance. Specifically, what I want to do is this:
Before a row is added to a table ( Document_Index_table) inside Database A ( Document_DB).
A trigger is fired. This trigger carries the one of the column value (usr_id) inside the row and pass it to Database B ( User_Control_DB).
Based on the values, User_Control_DB will check whether the usr_id exists in column usr_id of the table (Usr_Information).
If exists, then return a true to Document_DB and the row in 1. is allowed to add to the Document_DB.
If not, then an error is issued. No row is added to Document_DB.
How can this be done, if it can be done at all?
Edit: Both databases are MySQL databases
So, I'm a complete novice at database development, but you could do something like this:
Create a 'Before' insert trigger on your document_index_table.
The trigger does something like this:
declare numRows integer;
select count(*) from user_control_db.usr_information where usr_id = NEW.usr_id into num_rows;
if (numRows > 0) then
call NonExistentProc();
end if;
I believe that this would accomplish what you wanted. It'll produce an error like "PROCEDURE documentdb.NonExistenProc does not exist" and skip the insert if there isn't at least one row that has the matching usr id in the user control db.
Again, I'm a novice at this DB stuff so there might be a more elegant way, but this worked for my single test case.
Hope that helps.
When inserting a new row in a table T, I would like to check if the table is larger than a certain threshold, and if it is the case delete the oldest record (creating some kind of FIFO in the end).
I thought I could simply make a trigger, but apparently MySQL doesn't allow the modification of the table on which we are actually inserting :
Code: 1442 Msg: Can't update table 'amoreAgentTST01' in stored function/trigger because it is already used by statement which invoked this stored function/trigger.
Here is the trigger I tried :
Delimiter $$
CREATE TRIGGER test
AFTER INSERT ON amoreAgentTST01
FOR EACH ROW
BEGIN
DECLARE table_size INTEGER;
DECLARE new_row_size INTEGER;
DECLARE threshold INTEGER;
DECLARE max_update_time TIMESTAMP;
SELECT SUM(OCTET_LENGTH(data)) INTO table_size FROM amoreAgentTST01;
SELECT OCTET_LENGTH(NEW.data) INTO new_row_size;
SELECT 500000 INTO threshold;
select max(updatetime) INTO max_update_time from amoreAgentTST01;
IF (table_size+new_row_size) > threshold THEN
DELETE FROM amoreAgentTST01 WHERE max_update_time = updatetime; -- and check if not current
END IF;
END$$
delimiter ;
Do you have any idea on how to do this within the database ?
Or it is clearly something to be done in my program ?
Ideally you should have a dedicated archive strategy in a separate process that runs at off-peak times.
You could implement this either as a scheduled stored procedure (yuck) or an additional background worker thread within your application server, or a totally separate application service. This would be a good place to put other regular housekeeping jobs.
This has a few benefits. Apart from avoiding the trigger issue you're seeing, you should consider the performance implications of anything happening in a trigger. If you do many inserts, that trigger will do that work and effectively half the performance, not to mention the lock contention that will arise as other processes try to access the same table.
A separate process that does housekeeping work minimises lock contention, and allows the work to be carried out as a high-performance bulk operation, in a transaction.
One last thing - you should possibly consider archiving records to another table or database, rather than deleting them.