MySQL user-defined functions to generate primary key - mysql

I think in PostgreSQL you can use a function to generate your primary key in a table instead of just using auto_increment. Something similar to:
CREATE TABLE `blah` (
`id` bigint(20) unsigned NOT NULL my_generator_function(),
etc.
Where my_generator_function() would return a bigint.
Is that possible with MySQL or do I have to do that from the application code myself?

You can create a trigger -
CREATE TRIGGER trigger1
BEFORE INSERT
ON table1
FOR EACH ROW
BEGIN
IF NEW.id = 0 THEN -- generate new ID if zero is set
SET NEW.id = ...; -- write your code to generate new ID
END IF;
END

Related

Constrain grandchild table by grandparent key through parent [MySQL] [duplicate]

I would like to add a constraint that will check values from related table.
I have 3 tables:
CREATE TABLE somethink_usr_rel (
user_id BIGINT NOT NULL,
stomethink_id BIGINT NOT NULL
);
CREATE TABLE usr (
id BIGINT NOT NULL,
role_id BIGINT NOT NULL
);
CREATE TABLE role (
id BIGINT NOT NULL,
type BIGINT NOT NULL
);
(If you want me to put constraint with FK let me know.)
I want to add a constraint to somethink_usr_rel that checks type in role ("two tables away"), e.g.:
ALTER TABLE somethink_usr_rel
ADD CONSTRAINT CH_sm_usr_type_check
CHECK (usr.role.type = 'SOME_ENUM');
I tried to do this with JOINs but didn't succeed. Any idea how to achieve it?
CHECK constraints cannot currently reference other tables. The manual:
Currently, CHECK expressions cannot contain subqueries nor refer to
variables other than columns of the current row.
One way is to use a trigger like demonstrated by #Wolph.
A clean solution without triggers: add redundant columns and include them in FOREIGN KEY constraints, which are the first choice to enforce referential integrity. Related answer on dba.SE with detailed instructions:
Enforcing constraints “two tables away”
Another option would be to "fake" an IMMUTABLE function doing the check and use that in a CHECK constraint. Postgres will allow this, but be aware of possible caveats. Best make that a NOT VALID constraint. See:
Disable all constraints and table checks while restoring a dump
A CHECK constraint is not an option if you need joins. You can create a trigger which raises an error instead.
Have a look at this example: http://www.postgresql.org/docs/9.1/static/plpgsql-trigger.html#PLPGSQL-TRIGGER-EXAMPLE
CREATE TABLE emp (
empname text,
salary integer,
last_date timestamp,
last_user text
);
CREATE FUNCTION emp_stamp() RETURNS trigger AS $emp_stamp$
BEGIN
-- Check that empname and salary are given
IF NEW.empname IS NULL THEN
RAISE EXCEPTION 'empname cannot be null';
END IF;
IF NEW.salary IS NULL THEN
RAISE EXCEPTION '% cannot have null salary', NEW.empname;
END IF;
-- Who works for us when she must pay for it?
IF NEW.salary < 0 THEN
RAISE EXCEPTION '% cannot have a negative salary', NEW.empname;
END IF;
-- Remember who changed the payroll when
NEW.last_date := current_timestamp;
NEW.last_user := current_user;
RETURN NEW;
END;
$emp_stamp$ LANGUAGE plpgsql;
CREATE TRIGGER emp_stamp BEFORE INSERT OR UPDATE ON emp
FOR EACH ROW EXECUTE PROCEDURE emp_stamp();
...i did it so (nazwa=user name, firma = company name) :
CREATE TABLE users
(
id bigserial CONSTRAINT firstkey PRIMARY KEY,
nazwa character varying(20),
firma character varying(50)
);
CREATE TABLE test
(
id bigserial CONSTRAINT firstkey PRIMARY KEY,
firma character varying(50),
towar character varying(20),
nazwisko character varying(20)
);
ALTER TABLE public.test ENABLE ROW LEVEL SECURITY;
CREATE OR REPLACE FUNCTION whoIAM3() RETURNS varchar(50) as $$
declare
result varchar(50);
BEGIN
select into result users.firma from users where users.nazwa = current_user;
return result;
END;
$$ LANGUAGE plpgsql;
CREATE POLICY user_policy ON public.test
USING (firma = whoIAM3());
CREATE FUNCTION test_trigger_function()
RETURNS trigger AS $$
BEGIN
NEW.firma:=whoIam3();
return NEW;
END
$$ LANGUAGE 'plpgsql'
CREATE TRIGGER test_trigger_insert BEFORE INSERT ON test FOR EACH ROW EXECUTE PROCEDURE test_trigger_function();

Performing SELECT but INSERT when NOT EXISTS in MySQL

I have a MySQL table created using the following syntax:
CREATE TABLE `name_to_id` (
`id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(128),
PRIMARY KEY (`id`),
UNIQUE KEY `name` (`name`)
);
And a common query this table would like to answer is name to id look-up, but if the <name, id> pair does not exist in the DB, then also insert a new entry and return the newly inserted id.
Can I know should I do that in MySQL?
As commented by Strawberry, this cannot be performed in a single query.
However, here is a stored procedure that should do what you expect. First, it uses the INSERT ... ON DUPLICATE KEYS UPDATE syntax to insert new names ; this actually relies on the UNIQUE key that you correctly set up on the name column.
DELIMITER //
CREATE PROCEDURE get_id_by_name(IN p_name VARCHAR(128))
BEGIN
INSERT INTO name_to_id(name) VALUE(p_name) ON DUPLICATE KEY UPDATE name = p_name;
SELECT id FROM name_to_id WHERE name = p_name;
END //
DELIMITER ;
Demo on DB Fiddle.
This approach is efficient, but the downside of ON DUPLICATE KEYS is that it wastes id sequences : everytime the query is called, the sequence is autoincremented (even if a record already exists). This can be seen in the fiddle.
Here is another approach, that won't burn sequence numbers :
DELIMITER //
CREATE PROCEDURE get_id_by_name(IN p_name VARCHAR(128))
BEGIN
DECLARE p_id bigint(20) unsigned;
SELECT id INTO p_id FROM name_to_id WHERE name = p_name;
IF (p_id IS NULL) THEN
INSERT INTO name_to_id(name) VALUE(p_name);
SELECT LAST_INSERT_ID();
ELSE
SELECT p_id;
END IF;
END //
DELIMITER ;
Demo on DB Fiddle.
you can do this on stored proc, if the select statement did not return a result, then you can execute the insert statement

MYSQL: I am trying to set a trigger to update my current row on update.(not updating all the rows just the updated row) [duplicate]

Here's what I'm trying to do:
When there's a new INSERT into the table ACCOUNTS, I need to update the row in ACCOUNTS where pk = NEW.edit_on by setting status='E' to denote that the particular (old) account has been edited.
DELIMITER $$
DROP TRIGGER IF EXISTS `setEditStatus`$$
CREATE TRIGGER `setEditStatus` AFTER INSERT on ACCOUNTS
FOR EACH ROW BEGIN
update ACCOUNTS set status='E' where ACCOUNTS.pk = NEW.edit_on ;
END$$
DELIMITER ;
The requirement is NOT that I manipulate the newly inserted column, but an already existing column with pk = NEW.edit_on
However, I can't update the same table: Can't update table ACCOUNTS ... already used by the statement that invoked this trigger
Please suggest a workaround
PS: I have already gone through Updating table in trigger after update on the same table, Insert into same table trigger mysql, Update with after insert trigger on same table and mysql trigger with insert and update after insert on table but they dont seem to answer my question.
Edit
ACCOUNTS Table:
CREATE TABLE `ACCOUNTS` (
`pk` bigint(10) unsigned NOT NULL AUTO_INCREMENT,
`user_id` bigint(9) unsigned NOT NULL,
`edit_on` bigint(10) unsigned DEFAULT NULL,
`status` varchar(1) NOT NULL DEFAULT 'A',
PRIMARY KEY (`pk`) USING BTREE) ENGINE=InnoDB AUTO_INCREMENT=2147483726 DEFAULT CHARSET=latin1
It seems that you can't do all this in a trigger. According to the documentation:
Within a stored function or trigger, it is not permitted to modify a table that is already being used (for reading or writing) by the statement that invoked the function or trigger.
According to this answer, it seems that you should:
create a stored procedure, that inserts into/Updates the target table, then updates the other row(s), all in a transaction.
With a stored proc you'll manually commit the changes (insert and update). I haven't done this in MySQL, but this post looks like a good example.
This is how I update a row in the same table on insert
activationCode and email are rows in the table USER.
On insert I don't specify a value for activationCode, it will be created on the fly by MySQL.
Change username with your MySQL username and db_name with your db name.
CREATE DEFINER=`username`#`localhost`
TRIGGER `db_name`.`user_BEFORE_INSERT`
BEFORE INSERT ON `user`
FOR EACH ROW
BEGIN
SET new.activationCode = MD5(new.email);
END
Had the same problem but had to update a column with the id that was about to enter, so you can make an update should be done BEFORE and AFTER not BEFORE had no id so I did this trick
DELIMITER $$
DROP TRIGGER IF EXISTS `codigo_video`$$
CREATE TRIGGER `codigo_video` BEFORE INSERT ON `videos`
FOR EACH ROW BEGIN
DECLARE ultimo_id, proximo_id INT(11);
SELECT id INTO ultimo_id FROM videos ORDER BY id DESC LIMIT 1;
SET proximo_id = ultimo_id+1;
SET NEW.cassette = CONCAT(NEW.cassette, LPAD(proximo_id, 5, '0'));
END$$
DELIMITER ;
On the last entry; this is another trick:
SELECT AUTO_INCREMENT FROM information_schema.tables WHERE table_schema = ... and table_name = ...
DELIMITER $$
DROP TRIGGER IF EXISTS `setEditStatus`$$
CREATE TRIGGER `setEditStatus` **BEFORE** INSERT on ACCOUNTS
FOR EACH ROW BEGIN
SET NEW.STATUS = 'E';
END$$
DELIMITER ;
Instead you can use before insert and get max pkid for the particular table and then update the maximium pkid table record.

How to prevent creation of records where the value of two fields is the same?

I have the following table.
CREATE TABLE people(
first_name VARCHAR(128) NOT NULL,
nick_name VARCHAR(128) NULL
)
I would like to prevent people from having their nickname be the same as their firstname if they attempt that insertion. I do not want to create an index on either of the columns just a rule to prevent the insertion of records where the first_name and nick_name are the same.
Is there a way to create a rule to prevent insertion of records where the first_name would equal the nick_name?
CREATE TRIGGER `nicknameCheck` BEFORE INSERT ON `people` FOR EACH ROW begin
IF (new.first_name = new.nick_name) THEN
SET new.nick_name = null;
END IF;
END
Or you can set first_name to NULL which will cause SQL error and you can handle it and show some warning.
You only need triggers for BEFORE INSERT and BEFORE UPDATE. Let these check the values and abort the operation, if they are equal.
Caveat: On older but still widely used versions of MySQL (before 5.5 IIRC) you need to do something bad, such as read from the written table or easier read from an inexistant table/column (in order to abort).
AFTER INSERT trigger to test and remove if same ...
CREATE TABLE ek_test (
id INT PRIMARY KEY NOT NULL AUTO_INCREMENT,
one INT NOT NULL,
two INT NOT NULL
);
delimiter //
CREATE TRIGGER ek_test_one_two_differ AFTER INSERT ON ek_test
FOR EACH ROW
BEGIN
IF (new.one = new.two) THEN
DELETE FROM ek_test WHERE id = new.id;
END IF;
END//
delimiter ;
INSERT INTO ek_test (one, two) VALUES (1, 1);
SELECT * FROM ek_test;
NOTE you will also need AFTER UPDATE trigger.

MySQL - Trigger for updating same table after insert

Here's what I'm trying to do:
When there's a new INSERT into the table ACCOUNTS, I need to update the row in ACCOUNTS where pk = NEW.edit_on by setting status='E' to denote that the particular (old) account has been edited.
DELIMITER $$
DROP TRIGGER IF EXISTS `setEditStatus`$$
CREATE TRIGGER `setEditStatus` AFTER INSERT on ACCOUNTS
FOR EACH ROW BEGIN
update ACCOUNTS set status='E' where ACCOUNTS.pk = NEW.edit_on ;
END$$
DELIMITER ;
The requirement is NOT that I manipulate the newly inserted column, but an already existing column with pk = NEW.edit_on
However, I can't update the same table: Can't update table ACCOUNTS ... already used by the statement that invoked this trigger
Please suggest a workaround
PS: I have already gone through Updating table in trigger after update on the same table, Insert into same table trigger mysql, Update with after insert trigger on same table and mysql trigger with insert and update after insert on table but they dont seem to answer my question.
Edit
ACCOUNTS Table:
CREATE TABLE `ACCOUNTS` (
`pk` bigint(10) unsigned NOT NULL AUTO_INCREMENT,
`user_id` bigint(9) unsigned NOT NULL,
`edit_on` bigint(10) unsigned DEFAULT NULL,
`status` varchar(1) NOT NULL DEFAULT 'A',
PRIMARY KEY (`pk`) USING BTREE) ENGINE=InnoDB AUTO_INCREMENT=2147483726 DEFAULT CHARSET=latin1
It seems that you can't do all this in a trigger. According to the documentation:
Within a stored function or trigger, it is not permitted to modify a table that is already being used (for reading or writing) by the statement that invoked the function or trigger.
According to this answer, it seems that you should:
create a stored procedure, that inserts into/Updates the target table, then updates the other row(s), all in a transaction.
With a stored proc you'll manually commit the changes (insert and update). I haven't done this in MySQL, but this post looks like a good example.
This is how I update a row in the same table on insert
activationCode and email are rows in the table USER.
On insert I don't specify a value for activationCode, it will be created on the fly by MySQL.
Change username with your MySQL username and db_name with your db name.
CREATE DEFINER=`username`#`localhost`
TRIGGER `db_name`.`user_BEFORE_INSERT`
BEFORE INSERT ON `user`
FOR EACH ROW
BEGIN
SET new.activationCode = MD5(new.email);
END
Had the same problem but had to update a column with the id that was about to enter, so you can make an update should be done BEFORE and AFTER not BEFORE had no id so I did this trick
DELIMITER $$
DROP TRIGGER IF EXISTS `codigo_video`$$
CREATE TRIGGER `codigo_video` BEFORE INSERT ON `videos`
FOR EACH ROW BEGIN
DECLARE ultimo_id, proximo_id INT(11);
SELECT id INTO ultimo_id FROM videos ORDER BY id DESC LIMIT 1;
SET proximo_id = ultimo_id+1;
SET NEW.cassette = CONCAT(NEW.cassette, LPAD(proximo_id, 5, '0'));
END$$
DELIMITER ;
On the last entry; this is another trick:
SELECT AUTO_INCREMENT FROM information_schema.tables WHERE table_schema = ... and table_name = ...
DELIMITER $$
DROP TRIGGER IF EXISTS `setEditStatus`$$
CREATE TRIGGER `setEditStatus` **BEFORE** INSERT on ACCOUNTS
FOR EACH ROW BEGIN
SET NEW.STATUS = 'E';
END$$
DELIMITER ;
Instead you can use before insert and get max pkid for the particular table and then update the maximium pkid table record.