MySQL: Auto-increment based on multi-column PK with transaction - mysql

I have table with this structure:
CREATE TABLE IF NOT EXISTS `message` (
`id` bigint(20) NOT NULL,
`appKey` varchar(64) COLLATE utf8_unicode_ci NOT NULL,
`text` text COLLATE utf8_unicode_ci NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
and primary key (id,appKey)
I would like to have id autoincrement for every appKey independently. So I use trigger.
CREATE TRIGGER `messageId` BEFORE INSERT ON `message`
FOR EACH ROW begin
declare v_messageId bigint(20) unsigned default 0;
SELECT max(id) INTO v_messageId FROM message WHERE appKey = new.appKey;
if (v_messageId IS NULL) THEN
set v_messageId=0;
end if;
SET new.id=v_messageId + 1;
end
It works fine until i tried to insert two rows from two database connection (I am using connection pool in application) at same time. First row is inserted. But second throws error ER_DUP_ENTRY: Duplicate entry '18-secretkey' for key 'PRIMARY'.
I know why this is happening. My question is: Is MySQL possible to achieves this task or I have to use different database (probably PostgreSQL because of advisory lock)?
EDIT:
In table I have this rows:
id | appKey | text
---+--------+-----------
1 | key 1 | something
2 | key 1 | something
1 | key 2 | something
2 | key 2 | something
3 | key 1 | something
And the error is after I try to insert this two rows:
appKey | text
-------+-------
key1 | something
key1 | something

Personally I'd strongly suggest you don't manage ids on your own. There is no business value in doing so.
Now in case you want to stick with it, for some reason, at least make use of LAST_INSERT_ID(expr). It's the only multi-user safe way to generate sequences in MySQL. Also you would need additional table to store sequences per appKey.
CREATE TABLE message_seq (
appKey VARCHAR(64) NOT NULL PRIMARY KEY,
seq BIGINT(20) NOT NULL
);
Your trigger then will look like
DELIMITER //
CREATE TRIGGER `messageId`
BEFORE INSERT ON `message`
FOR EACH ROW
BEGIN
INSERT INTO message_seq(appkey, seq) VALUES (NEW.appKey, LAST_INSERT_ID(1))
ON DUPLICATE KEY UPDATE seq = LAST_INSERT_ID(seq + 1);
SET NEW.id = LAST_INSERT_ID();
END//
DELIMITER ;
Here is a SQLFiddle demo
Further reading:
LAST_INSERT_ID()

Related

Auto increment string in mySQL [duplicate]

I want to add a column to a mysql table where its cells will get values like:
newColumn
-----------
log-00001
log-00002
log-00003
....
the values log-0000x will automatically be created by mysql. This is like an "auto incremented" column but with the 'log-' prefix. Is this possible?
Thx
MySQL doesn't auto-increment anything other than integers. You can't auto-increment a string.
You can't use a trigger to populate a string based on the auto-increment value. The reason is that the auto-increment value isn't generated yet at the time "before" triggers execute, and it's too late to change columns in "after" triggers.
See also my answer to https://stackoverflow.com/a/26899091/20860
You can't use a virtual column, probably for the same reason.
mysql> create table t (id int(5) zerofill auto_increment primary key,
virtcolumn char(8) as (concat('log-', id)));
ERROR 3109 (HY000): Generated column 'virtcolumn' cannot refer to auto-increment column.
You'll have to let the integer auto-increment, and then subsequently use UPDATE to populate your "log-nnnnnn" string after the insert is done.
CREATE TABLE `t` (
`id` int(5) unsigned zerofill NOT NULL AUTO_INCREMENT,
`log` char(9) DEFAULT NULL,
PRIMARY KEY (`id`)
);
INSERT INTO `t` () VALUES ();
UPDATE `t` SET `log` = CONCAT('log-', `id`) WHERE `id` = LAST_INSERT_ID();
SELECT * FROM `t`;
+-------+-----------+
| id | log |
+-------+-----------+
| 00001 | log-00001 |
+-------+-----------+

Query fails to load data in the way I expect, overwrites existing data

I have the following query:
INSERT INTO `user_pen_names` (`uid`,`pnid`,`my_name`) VALUES ('7','200','stink') ON DUPLICATE KEY UPDATE `my_name`=values(`my_name`)
My table has the following columns defined:
id INT primary, auto-increment
uid INT unsigned, unique
pnid INT unsigned, unique
my_name VARCHAR(24)
I have one table entry already:
id(0), uid(7), pnid(100), my_name(test)
When I execute the above query, what I expected to see was two rows:
id(0), uid(7), pnid(100), my_name(test)
id(1), uid(7), pnid(200), my_name(stink)
What's happening, and I am one confused puppy because of it, is that the existing row is being modified...
id(0), uid(7), pnid(100), my_name(stink)
The same thing happens if I modify uid and pnid so they are no longer unique. Can anyone explain to me why this is happening?
EDIT I made the two columns combinationally unique using the following command:
ALTER TABLE `user_pen_names` ADD UNIQUE KEY `upn_unique_id` (`uid`, `pnid`)
I've not done this before, but theoretically, the INSERT command should only shift to its UPDATE sub-command when uid AND pnid match a row already in the table. Nevertheless, this also didn't work.
It works fine for me. I suspect you didn't run the test you thought you were running.
I tested on a Macbook with MySQL 8.0.1:
mysql> CREATE TABLE `user_pen_names` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`uid` int(10) unsigned DEFAULT NULL,
`pnid` int(10) unsigned DEFAULT NULL,
`my_name` varchar(24) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uid` (`uid`,`pnid`)
);
mysql> INSERT INTO `user_pen_names` (id, `uid`,`pnid`,`my_name`) VALUES (0, '7','100','test');
mysql> INSERT INTO `user_pen_names` (`uid`,`pnid`,`my_name`) VALUES ('7','200','stink')
ON DUPLICATE KEY UPDATE `my_name`=values(`my_name`);
mysql> SELECT * FROM user_pen_names;
+----+------+------+---------+
| id | uid | pnid | my_name |
+----+------+------+---------+
| 1 | 7 | 100 | test |
| 2 | 7 | 200 | stink |
+----+------+------+---------+
Note that when you insert a 0 into an auto-increment column, it generates a new id, starting at 1.
You have uid & pnid set as unique. So because you can't insert another uid=7, it's modifying the 7 row that has uid of 7 already.

adding a kind of auto increment column to a mysql table

I want to add a column to a mysql table where its cells will get values like:
newColumn
-----------
log-00001
log-00002
log-00003
....
the values log-0000x will automatically be created by mysql. This is like an "auto incremented" column but with the 'log-' prefix. Is this possible?
Thx
MySQL doesn't auto-increment anything other than integers. You can't auto-increment a string.
You can't use a trigger to populate a string based on the auto-increment value. The reason is that the auto-increment value isn't generated yet at the time "before" triggers execute, and it's too late to change columns in "after" triggers.
See also my answer to https://stackoverflow.com/a/26899091/20860
You can't use a virtual column, probably for the same reason.
mysql> create table t (id int(5) zerofill auto_increment primary key,
virtcolumn char(8) as (concat('log-', id)));
ERROR 3109 (HY000): Generated column 'virtcolumn' cannot refer to auto-increment column.
You'll have to let the integer auto-increment, and then subsequently use UPDATE to populate your "log-nnnnnn" string after the insert is done.
CREATE TABLE `t` (
`id` int(5) unsigned zerofill NOT NULL AUTO_INCREMENT,
`log` char(9) DEFAULT NULL,
PRIMARY KEY (`id`)
);
INSERT INTO `t` () VALUES ();
UPDATE `t` SET `log` = CONCAT('log-', `id`) WHERE `id` = LAST_INSERT_ID();
SELECT * FROM `t`;
+-------+-----------+
| id | log |
+-------+-----------+
| 00001 | log-00001 |
+-------+-----------+

Can I add a check constraint to a child checking the value of its parent in MySQL?

I have a similar database with two tables as follow:
+----------------------+ +------------------------+
|Persons | |phones |
+----------------------+ +------------------------+
|id int +-----+ |id int |
|name varchar(100) | +---->+person_id int |
|allowed tinyint(1) | |number int |
+----------------------+ +------------------------+
One person could have as many phones as he wants but he has to be allowed to it (allowed > 0).
So, I created both tables using
CREATE TABLE `phones` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`persons_id` int(11) DEFAULT NULL,
`phonenumber` int(5) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `phones_ibfk_1` (`persons_id`),
CONSTRAINT `phones_ibfk_1` FOREIGN KEY (`persons_id`) REFERENCES `persons` (`id`) ON DELETE NO ACTION ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8`
But this doesn't check what I want. What I want to know if it is possible make a query like ALTER TABLE phones ADD CONSTRAINT chk_person CHECK (persons.allowed > 0). Is this possible? There's another alternative?
the check constraint doesn't work a solution is to create a trigger before
insert so you check the values if you find something wron do an insert into the same table that will cause a erreur in
DELIMITER ||
CREATE TRIGGER trigger_check before insert ON phones FOR EACH ROW
begin
DECLARE is_allowed boolean;
select allowed into #is_allowed from Persons where id = new.person_id;
if #is_allowed <1 then
insert into phones values ('','','',''); #make erreur doesn't metter
end if ;
end ||
DELIMITER ;
that's how we do it for now hope that check constraint work in the new versions

MYSQL: How to make NULL or empty data default to 0 during insert

So this seems like it would be pretty straight forward and I swear I've done this before, but for some reason it's just not working for me.
I am using MAMP and have a table with about 200 columns and I want about 20 of them to default to 0 if NULL or empty data is inserted into it.
Here's a small example of what my table looks like as well as what I have done for columns that I want to default to 0.
CREATE TABLE `listings` (
`ListingID` int(11) NOT NULL,
`BathsFull` int(6) NOT NULL DEFAULT '0',
PRIMARY KEY (`ListingID`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
So notice on BathsFull I have it set to NOT NULL DEFAULT '0' the problem is that when empty data is passed to it I get a SQL error of SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'BathsFull' cannot be null.
I've also tried so that BathsFull acceptsNULLandDEFAULT '0', however when empty data is passed, the table shows NULL instead of 0.
Am I missing something here? Do I need to write some sort of trigger? I don't want to scrub the data in my script before putting it into the DB if I don't have to.
If you are explicitly setting the value to NULL in your insert, but want MySQL to replace the NULL with 0, one way to do that is to define the column to allow NULL in the CREATE TABLE statement, and then replace the NULL with a TRIGGER.
Something like this:
CREATE TABLE `listings` (
`ListingID` int(11) NOT NULL,
`BathsFull` int(6) NULL DEFAULT 0,
PRIMARY KEY (`ListingID`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
delimiter $$
create trigger tr_b_ins_listings before insert on listings for each row
begin
set new.BathsFull = coalesce(new.BathsFull,0);
end $$
delimiter ;
Try it for yourself in this SQL Fiddle
You can definitely use a trigger for that
Assuming that you make the field nullable
CREATE TABLE `listings` (
`ListingID` int(11) NOT NULL,
`BathsFull` int(6), <-----
PRIMARY KEY (`ListingID`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
Trigger
DELIMITER $$
CREATE TRIGGER tg_lst_insert BEFORE INSERT ON listings
FOR EACH ROW
BEGIN
SET NEW.BathsFull = IFNULL(NEW.BathsFull, 0);
END $$
DELIMITER ;
Inserting some rows
INSERT INTO `listings` VALUES(1, '');
INSERT INTO `listings` VALUES(3, 'a');
INSERT INTO `listings` VALUES(4, NULL);
INSERT INTO `listings` (ListingID) VALUES(2);
INSERT INTO `listings` VALUES(5, 3);
Result
+-----------+-----------+
| ListingID | BathsFull |
+-----------+-----------+
| 1 | 0 |
| 2 | 0 |
| 3 | 0 |
| 4 | 0 |
| 5 | 3 |
+-----------+-----------+
You forgot to post the INSERT queries but I'm sure the problem is there. This works as expected:
insert into listings (ListingID) values(1)
... because you omit BathsFull so MySQL uses the defalt. This doesn't:
insert into listings (ListingID, BathsFull) values(1, null);
... because your are telling MySQL that you want NULL.
You can use the default keyword to get the default value of the column during insertion.
Example :
INSERT INTO listings(ListingID, BathsFull) VALUES (2,DEFAULT);
Maybe you should try this:
insert into listings (ListingID, ListingID, BathsFull)
values (#listing_id, #ListingID, isnull(BathsFull, 0)