PLsql equivalent query from PLpgsql - mysql

I am new to mysql and used to work in postgresql. I have been trying to make a function in mysql workbench using plsql. I have written a code in plpgsql to describe what I want to do (basically data entry in a table avoiding the redundant entries)
I tried searching relevant answers and founds ways to delete duplicate entries from table. But, what I want to do is avoid duplicate data entry in the table. Following is the workable code but I don't understand how to get out of the loop if the update query runs successfully, if not, then insert query needs to be run. I know I can add IF-then-ELSE here. but what to write in the conditions.
DELIMITER //
CREATE PROCEDURE merge_abc
(IN data CHAR(20))
BEGIN
LOOP
UPDATE abc SET node_name = data WHERE node_name = data;
END LOOP;
END //
DELIMITER ;
*Aim is to avoid duplicate data entry rather than deleting the duplicate entries in the end just like this;
CREATE FUNCTION merge_abc(data TEXT) RETURNS VOID AS
BEGIN
LOOP
-- first try to update the key
UPDATE abc SET node_name = data WHERE node_name = data;
IF found THEN
RETURN;
END IF;
-- not there, so try to insert the key
-- if someone else inserts the same key concurrently,
-- we could get a unique-key failure
BEGIN
INSERT INTO abc(node_name) VALUES (data);
RETURN;
EXCEPTION WHEN unique_violation THEN
-- Do nothing, and loop to try the UPDATE again.
END;
END LOOP;
END;

Related

Is there a DRY way of Triggers plus Trigger Function like in PostgreSQL

In PostgreSQL we can have triggers like this:
CREATE TRIGGER tr_info_changed
BEFORE INSERT OR UPDATE
ON t_info
FOR EACH ROW
EXECUTE PROCEDURE tf_table_changed();
With tf_table_changed() beeing a Trigger Function like this:
CREATE OR REPLACE FUNCTION tf_table_changed()
RETURNS trigger AS
$BODY$
BEGIN
IF (TG_OP = 'INSERT') THEN
-- Keep track of record insertion.
NEW.created = current_timestamp;
NEW.changed = NEW.created; -- Times of change and insertion are identical.
RETURN NEW;
END IF;
IF (TG_OP = 'UPDATE') THEN
-- Keep track of changes.
NEW.created = OLD.created; -- EF would overwrite value.
NEW.changed = current_timestamp;
RETURN NEW;
END IF;
RETURN NULL;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
In Transact-SQL I have this so far:
CREATE TRIGGER [dbo].[Trigger_Setting_Created]
ON [dbo].[T_Setting] AFTER INSERT
AS
UPDATE s SET Created = GETDATE(), Changed = GETDATE()
FROM T_Setting AS s
INNER JOIN inserted AS i
ON s.SessionToken = i.SessionToken
GO
I ommit the second trigger for the update case.
As we can see when we want to keep all Created/Updated up to date in each and every of our tables it is getting very cumbersome in Transact-SQL with lots of copy&paste and modifying each trigger hopefully not forgetting any table-specific changes whereas in PostgreSQL things are DRY and simple.
Question: Is there an equivalent in Transact-SQL with respect for DRYness?

How to write a stored procedure in MySQL that displays two messages when called

The goal here is to write a script attempts to insert a new category into categories table and if the insert was successful the message:
1 row was inserted
If the update is unsuccessful, the procedure should display this message:
Row was not inserted - duplicate entry
Whenever I run this script, I keep getting the second message no matter how many times I run it, when really it should display the first message, followed by the second. Here is my script. Can someone please help me spot what I'm overlooking? Thank you.
use my_guitar_shop;
/*drop procedure if exists add_category;*/
DELIMITER //
CREATE PROCEDURE add_category(
in categories VARCHAR(100)
)
begin
declare duplicate_entry_for_key TinyINT DEFAULT FALSE;
declare continue handler for 1062
SET duplicate_entry_for_key = TRUE;
insert into categories values (5, 'Electric');
select '1 row was inserted.' as message;
if duplicate_entry_for_key = true then
select 'Row was not inserted - duplicate entry.' as message;
end if;
end //
DELIMITER ;
/* call the stored procedure with 'Gibson' */
call add_category('Gibson');
call add_category('Gibson');
You should set duplicate_entry_for_key to True. If data insert is successful.
Do something like below after insert operation.
SET duplicate_entry_for_key = last_insert_id();
OR see the below example for bit check.
For example:
START TRANSACTION; -- Begin a transaction
INSERT INTO categories
VALUES
(
5
,'Electric'
);
IF ROW_COUNT() > 0 THEN -- ROW_COUNT() returns the number of rows updated/inserted/deleted
SET duplicate_entry_for_key = TRUE;
COMMIT; -- Finalize the transaction
ELSE
SET duplicate_entry_for_key = False;
--You can ROLLBACK the transaction also - Revert all changes made before the transaction began
END IF

Handling sql errors (if exists, if constraint), Perl

I am using perl and mysql on my site.
Is there a mechanism to restrict deleting if data connected to 'deleting' exists? Yes - foreign key constraint. Is it possible to return some code if row wasn't deleted because of foreign key constraint? Somethink like this:
$id = $cgi->param("id");
$query="delete from `Class` where `id` = '$id'";
$sth = $dbh->prepare($query);
$sth->execute or die(print $sth->errstr);
if ($sth->errcode eq '777')
{
print 'error! there are 1 or more rows, connected with row you want to delete';
}
else
{
print 'ok! deleted';
}
Same for inserting row and there are row with same data existing. Sure It is possible to create trigger on insert and try to return some code. Give me example of such trigger, please? And again : how to handle this error?
Some help please! Sorry for my not very good english :)
If you want to return your own success/failure code then you can use procedures. create a procedure
delimiter $$
CREATE PROCEDURE `deleteProcedure`(in Id INT)
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
select 0; -- number that you want to return on failure
ROLLBACK;
END;
START TRANSACTION;
-- put any number of statement, including delete, insert, update
delete from `Class` where `id` = Id;
COMMIT;
select 1; -- number that you want to return on success
END$$
call this procedure from your perl script, and it will return the values you put in your procedure.

How to translate PostgreSQL "merge_db" (aka upsert) function into MySQL

Straight from the manual, here's the canonical example of merge_db in PostgreSQL:
CREATE TABLE db (a INT PRIMARY KEY, b TEXT);
CREATE FUNCTION merge_db(key INT, data TEXT) RETURNS VOID AS
$$
BEGIN
LOOP
-- first try to update the key
UPDATE db SET b = data WHERE a = key;
IF found THEN
RETURN;
END IF;
-- not there, so try to insert the key
-- if someone else inserts the same key concurrently,
-- we could get a unique-key failure
BEGIN
INSERT INTO db(a,b) VALUES (key, data);
RETURN;
EXCEPTION WHEN unique_violation THEN
-- Do nothing, and loop to try the UPDATE again.
END;
END LOOP;
END;
$$
LANGUAGE plpgsql;
SELECT merge_db(1, 'david');
SELECT merge_db(1, 'dennis');
Can this be expressed as a user-defined function in MySQL, and if so, how? Would there be any advantage over MySQL's standard INSERT...ON DUPLICATE KEY UPDATE?
Note: I'm specifically looking for a user-defined function, not INSERT...ON DUPLICATE KEY UPDATE.
Tested on MySQL 5.5.14.
CREATE TABLE db (a INT PRIMARY KEY, b TEXT);
DELIMITER //
CREATE PROCEDURE merge_db(k INT, data TEXT)
BEGIN
DECLARE done BOOLEAN;
REPEAT
BEGIN
-- If there is a unique key constraint error then
-- someone made a concurrent insert. Reset the sentinel
-- and try again.
DECLARE ER_DUP_UNIQUE CONDITION FOR 23000;
DECLARE CONTINUE HANDLER FOR ER_DUP_UNIQUE BEGIN
SET done = FALSE;
END;
SET done = TRUE;
SELECT COUNT(*) INTO #count FROM db WHERE a = k;
-- Race condition here. If a concurrent INSERT is made after
-- the SELECT but before the INSERT below we'll get a duplicate
-- key error. But the handler above will take care of that.
IF #count > 0 THEN
UPDATE db SET b = data WHERE a = k;
ELSE
INSERT INTO db (a, b) VALUES (k, data);
END IF;
END;
UNTIL done END REPEAT;
END//
DELIMITER ;
CALL merge_db(1, 'david');
CALL merge_db(1, 'dennis');
Some thoughts:
You can't do an update first and then check #ROW_COUNT() because it returns the number of rows actually changed. This could be 0 if the row already has the value you are trying to update.
Also, #ROW_COUNT() is not replication safe.
You could use REPLACE...INTO.
If using InnoDB or a table with transaction support you might be able to use SELECT...FOR UPDATE (untested).
I see no advantage to this solution over just using INSERT...ON DUPLICATE KEY UPDATE.

Return error message when duplicate unique fields

I want to return error message when duplicate records occur, groupName is unique field in group table.
I make like this, how can I make error handleing whithout using mysql-get-diagnostics because the server I am working on is version 5.0.77-log.
DELIMITER //
CREATE PROCEDURE addNewGroup(IN groupName varchar(128), IN addedBy INT,OUT message varchar(128) )
BEGIN
insert IGNORE into `group`( `group_name`,`Date_time_ added`,`added_by`) values (groupName ,CURRENT_TIMESTAMP(),addedBy) ;
END //
DELIMITER ;
I want to return error message when duplication occur?
Try this method -
BEGIN
INSERT IGNORE INTO table VALUES...;
IF ROW_COUNT() = 0 THEN
SET message = 'duplicate records occur';
END IF;
END
ROW_COUNT() function returns the number of inserted rows for the last statement.
In MySQL 5.5. you can use SIGNAL statement to generate a warning.