Defining Composite Key with Auto Increment in MySQL - mysql

Scenario:
I have a table which references two foreign keys, and for each unique combination of these foreign keys, has its own auto_increment column. I need to implement a Composite Key that will help identify the row as unique using combination of these three (one foreign keys and one auto_increment column, and one other column with non-unique values)
Table:
CREATE TABLE `issue_log` (
`sr_no` INT NOT NULL AUTO_INCREMENT ,
`app_id` INT NOT NULL ,
`test_id` INT NOT NULL ,
`issue_name` VARCHAR(255) NOT NULL ,
primary key (app_id, test_id,sr_no)
);
Of course, there has to be something wrong with my query, because of which the error thrown is:
ERROR 1075: Incorrect table definition; there can be only one auto
column and it must be defined as a key
What I am trying to achieve:
I have an Application Table (with app_id as its primary key), each Application has a set of Issues to be resolved, and each Application has multiple number of tests (so the test_id col)
The sr_no col should increment for unique app_id and test_id.
i.e. The data in table should look like:
The database engine is InnoDB.
I want to achieve this with as much simplicity as possible (i.e. avoid triggers/procedures if possible - which was suggested for similar cases on other Questions).

You can't have MySQL do this for you automatically for InnoDB tables - you would need to use a trigger or procedure, or user another DB engine such as MyISAM. Auto incrementing can only be done for a single primary key.
Something like the following should work
DELIMITER $$
CREATE TRIGGER xxx BEFORE INSERT ON issue_log
FOR EACH ROW BEGIN
SET NEW.sr_no = (
SELECT IFNULL(MAX(sr_no), 0) + 1
FROM issue_log
WHERE app_id = NEW.app_id
AND test_id = NEW.test_id
);
END $$
DELIMITER ;

You can do this with myISAM and BDB engines. InnoDB does not support this. Quote from MySQL 5.0 Reference Manual.
For MyISAM and BDB tables you can specify AUTO_INCREMENT on a secondary column in a multiple-column index. In this case, the generated value for the AUTO_INCREMENT column is calculated as MAX(auto_increment_column) + 1 WHERE prefix=given-prefix.
http://dev.mysql.com/doc/refman/5.0/en/example-auto-increment.html

I don't fully understand your increment requirement on the test_id column, but if you want an ~autoincrement sequence that restarts on every unique combination of (app_id, test_id), you can do an INSERT ... SELECT FROM the same table, like so:
mysql> INSERT INTO `issue_log` (`sr_no`, `app_id`, `test_id`, `issue_name`) SELECT
IFNULL(MAX(`sr_no`), 0) + 1 /* next sequence number */,
3 /* desired app_id */,
1 /* desired test_id */,
'Name of new row'
FROM `issue_log` /* specify the table name as well */
WHERE `app_id` = 3 AND `test_id` = 1 /* same values as in inserted columns */
This assumes a table definition with no declared AUTO_INCREMENT column. You're essentially emulating autoincrement behavior with the IFNULL(MAX()) + 1 clause, but the manual emulation works on arbitrary columns, unlike the built-in autoincrement.
Note that the INSERT ... SELECT being a single query ensures atomicity of the operation. InnoDB will gap-lock the appropriate index, and many concurrent processes can execute this kind of query while still producing non-conflicting sequences.

You can use a unique composite key for sr_no,app_id & test_id. You cannot use incremental in sr_no as this is not unique.
CREATE TABLE IF NOT EXISTS `issue_log` (
`sr_no` int(11) NOT NULL,
`app_id` int(11) NOT NULL,
`test_id` int(11) NOT NULL,
`issue_name` varchar(255) NOT NULL,
UNIQUE KEY `app_id` (`app_id`,`test_id`,`sr_no`)
) ENGINE=InnoDB ;
I have commented out unique constraint violation in sql fiddle to demonstrate (remove # in line 22 of schema and rebuild schema )

This is what I wanted
id tenant
1 1
2 1
3 1
1 2
2 2
3 2
1 3
2 3
3 3
My current table definition is
CREATE TABLE `test_trigger` (
`id` BIGINT NOT NULL,
`tenant` varchar(255) NOT NULL,
PRIMARY KEY (`id`,`tenant`)
);
I created one table for storing the current id for each tenant.
CREATE TABLE `get_val` (
`tenant` varchar(255) NOT NULL,
`next_val` int NOT NULL,
PRIMARY KEY (`tenant`,`next_val`)
) ENGINE=InnoDB ;
Then I created this trigger which solve my problem
DELIMITER $$
CREATE TRIGGER trigger_name
BEFORE INSERT
ON test_trigger
FOR EACH ROW
BEGIN
UPDATE get_val SET next_val = next_val + 1 WHERE tenant = new.tenant;
set new.id = (select next_val from get_val where tenant=new.tenant);
END$$
DELIMITER ;
This approach will be thread safe also because any insertion for the same tenant will happen sequentially because of the update query in the trigger and for different tenants insertions will happen parallelly.

Just add key(sr_no) on auto-increment column:
CREATE TABLE `issue_log` (
`sr_no` INT NOT NULL AUTO_INCREMENT ,
`app_id` INT NOT NULL ,
`test_id` INT NOT NULL ,
`issue_name` VARCHAR(255) NOT NULL ,
primary key (app_id, test_id,sr_no),
key (`sr_no`)
);

Why don't you try to change the position of declare fields as primary key, since when you use "auto_increment" it has to be referenced as the first. Like in the following example
CREATE TABLE `issue_log` (
`sr_no` INT NOT NULL AUTO_INCREMENT ,
`app_id` INT NOT NULL ,
`test_id` INT NOT NULL ,
`issue_name` VARCHAR(255) NOT NULL ,
primary key (sr_no,app_id, test_id)
);

Related

Specific field parameter when creating a table MySQL

I'm using MySQL Workbench.
I would like to create a table named courseInfo and I want to put a column named moduleCode in it, but I want it to always be similar in format: CFSM H0000 where the four zeros are a number that increases starting with 0000.
For example:
CFSM H0001
CFSM H0002
[..]
You cannot auto-increment character type columns in MySQL, as auto-increment is only possible on integer type columns. One (alphanumeric) auto-incrementing moduleCode column would therefore not be possible. However, you could try splitting up the moduleCode into two columns, for example like so:
CREATE TABLE `courseInfo` (
`prefix` CHAR(6) NOT NULL DEFAULT 'CFSM H',
`id` SMALLINT(4) UNSIGNED ZEROFILL NOT NULL AUTO_INCREMENT,
KEY (`id`)
) AUTO_INCREMENT = 0;
Where prefix could for example be "CFSM H" and id could be 0001
Then, upon executing SELECT statements, you could merge the prefix column with the id column into a moduleCode column with CONCAT, e.g.:
SELECT CONCAT(`prefix`, `id`) as `moduleCode` FROM `courseInfo`;
An alternative approach (from MySQL version 5.7 and up) seems to be the use of a generated column, for example:
CREATE TABLE `courseInfo` (
`prefix` CHAR(6) NOT NULL DEFAULT 'CFSM H',
`id` SMALLINT(4) UNSIGNED ZEROFILL NOT NULL AUTO_INCREMENT,
`moduleCode` CHAR(10) AS (CONCAT(`prefix`, `id`)),
KEY (`id`)
) AUTO_INCREMENT = 0;
However, the above example of a generated column would not work, because moduleCode is dependent on an auto-increment column, and the auto-increment is not executed yet at the time the generated column is computed. See also: https://dev.mysql.com/doc/refman/5.7/en/create-table-generated-columns.html. It would throw an ER_GENERATED_COLUMN_REF_AUTO_INC error.
You could therefore use the first solution, or try to add moduleCode as a column and use an AFTER INSERT trigger to update its value:
CREATE TABLE `courseInfo` (
`prefix` CHAR(6) NOT NULL DEFAULT 'CFSM H',
`id` SMALLINT(4) UNSIGNED ZEROFILL NOT NULL AUTO_INCREMENT,
`moduleCode` CHAR(10),
KEY (`id`),
UNIQUE KEY `unique_index` (`prefix`,`id`)
) AUTO_INCREMENT = 0;
DELIMITER //
CREATE TRIGGER `addModuleCode` AFTER INSERT ON `courseInfo`
FOR EACH ROW
BEGIN
UPDATE `courseInfo` SET `moduleCode` = CONCAT(NEW.`prefix`, NEW.`id`) WHERE `prefix` = NEW.`prefix` AND `id` = NEW.`id`;
END;//
DELIMITER ;

mysql select for update concurency issues

We have an table bellow:
CREATE TABLE usable (
id bigint(20) NOT NULL AUTO_INCREMENT primary key ,
device VARCHAR(64) NOT NULL,
key1 VARCHAR(128) NOT NULL,
key2 VARCHAR(128) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ;
create unique index uidx_usable on usable (key1);
CREATE TABLE used
(
usedid bigint(20) PRIMARY KEY NOT NULL AUTO_INCREMENT primary key,
devid VARCHAR(64) NOT NULL,
key1 VARCHAR(128) NOT NULL ,
key2 VARCHAR(64) NOT NULL '
created_time timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ;
create unique index uidx_used on used (key1);
create unique index uidx_used_deviceid on used (devid);
select 1 record from usable table wih muti-thread.
insert into record to used table.
delete this record from usable table.
my work goes here:
START TRANSACTION
SELECT * FROM usable WHERE device = 555 order by rand() limit 1 FOR UPDATE;
insert into used (key1, key2) values (key1, key2 from step 1 select result);
delete from usable where id = (id from step 1 select result);
COMMIT
The tables are in InnoDB engine. AutoCommit is set to ON in the global variables.
There is no problem for just one thread, about 100ms with these three sql cmds. Is there solution to improve it?
but, thread begin to hang up when 100 QPS.
How to congfirm transaction worked?
How to improve QPS
(0.7~5ms) SELECT ; (47ms~250ms) insert ; (48ms~250ms) delete
why insert and delete need large time? how to tune insert and delete sql?

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 |
+-------+-----------+

MYSQL: Partitioning Table keeping id unique

We are using a table which has schema like following:-
CREATE TABLE `user_subscription` (
`ID` varchar(40) NOT NULL,
`COL1` varchar(40) NOT NULL,
`COL2` varchar(30) NOT NULL,
`COL3` datetime NOT NULL,
`COL4` datetime NOT NULL,
`ARCHIVE` tinyint(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`ID`)
)
Now we wanted to do partition on column ARCHIVE. ARCHIVE can have only 2 values 0 or 1 and so 2 partitions.
Actually in our case, we are using partitioning as a Archival process. To do partition, we need to make ARCHIVE column as a part of primary key. But the problem here is that 2 rows can have same ID with different ARCHIVE column value. Actually thats not the main problem for us as 2 rows will be in different partitions. Problem is when we will update the archive column value of one of them to other to move one of the row to archive partition, then it will not allow us to update the entry giving "Duplicate Error".
Can somebody help in this regard?
Unfortunately,
A UNIQUE INDEX (or a PRIMARY KEY) must include all columns in the table's partitioning function
and since MySQL does not support check constraints either, the only ugly workaround I can think of is enforcing the uniqueness manually though triggers:
CREATE TABLE t (
id INT NOT NULL,
archived TINYINT(1) NOT NULL DEFAULT 0,
PRIMARY KEY (id, archived), -- required by MySQL limitation on partitioning
)
PARTITION BY LIST(archived) (
PARTITION pActive VALUES IN (0),
PARTITION pArchived VALUES IN (1)
);
CREATE TRIGGER tInsert
BEFORE INSERT ON t FOR EACH ROW
CALL checkUnique(NEW.id);
CREATE TRIGGER tUpdate
BEFORE UPDATE ON t FOR EACH ROW
CALL checkUnique(NEW.id);
DELIMITER //
CREATE PROCEDURE checkUnique(pId INT)
BEGIN
DECLARE flag INT;
DECLARE message VARCHAR(50);
SELECT id INTO flag FROM t WHERE id = pId;
IF flag IS NOT NULL THEN
-- the below tries to mimic the error raised
-- by a regular UNIQUE constraint violation
SET message = CONCAT("Duplicate entry '", pId, "'");
SIGNAL SQLSTATE "23000" SET
MYSQL_ERRNO = 1062,
MESSAGE_TEXT = message,
COLUMN_NAME = "id";
END IF;
END //
(fiddle)
MySQL's limitations on partitioning being such a downer (in particular its lack of support for foreign keys), I would advise against using it altogether until the table grows so large that it becomes an actual concern.

MySQL is not failing when deliberately inserting `NULL` in Primary Key AUTO_INCREMENT column

I have created a table empInfo as follow
CREATE TABLE empInfo (
empid INT(11) PRIMARY KEY AUTO_INCREMENT ,
firstname VARCHAR(255) DEFAULT NULL,
lastname VARCHAR(255) DEFAULT NULL
)
Then I run below Insert statements :-
INSERT INTO empInfo VALUES(NULL , 'SHREE','PATIL');
INSERT INTO empInfo(firstname,lastname) VALUES( 'VIKAS','PATIL');
INSERT INTO empInfo VALUES(NULL , 'SHREEKANT','JOHN');
I thought first or Third SQL will fail as empid is PRIMARY KEY and We are trying to insert NULL for empid .
But MYSQL proved me wrong and all 3 queries ran successfully .
I wanted to know Why it is not failing when trying to insert NULL in empid column ?
Final Data available in table is as below
empid firstname lastname
1 SHREE PATIL
2 VIKAS PATIL
3 SHREEKANT JOHN
I can figure out that it has something releted to AUTO_INCREMENT But I am not able to figure out reason for it . Any pointers on this .
This behaviour is by design, viz inserting 0, NULL, or DEFAULT into an AUTO_INCREMENT column will all trigger the AUTO_INCREMENT behaviour.
INSERT INTO empInfo VALUES(DEFAULT, 'SHREEKANT','JOHN');
INSERT INTO empInfo VALUES(NULL, 'SHREEKANT','JOHN');
INSERT INTO empInfo VALUES(0, 'SHREEKANT','JOHN');
and is commonplace practice
Note however that this wasn't however always the case in versions prior to 4.1.6
Edit
Does that mean AUTO_INCREMENT is taking precedance over PRIMARY KEY?
Yes, since the primary key is dependent on the AUTO_INCREMENT delivering a new sequence prior to constraint checking and record insertion, the AUTO_INCREMENT process (including the above re-purposing of NULL / 0 / DEFAULT) would need to be resolved prior to checking PRIMARY KEY constraint in any case.
If you remove the AUTO_INCREMENT and define the emp_id PK as INT(11) NULL (which is nonsensical, but MySql will create the column this way), as soon as you insert a NULL into the PK you will get the familiar
Error Code: 1048. Column 'emp_id' cannot be null
So it is clear that the AUTO_INCREMENT resolution precedes the primary key constraint checks.
It is exactly because of the auto increment. As you can see, no empid values are null in the db. That is the purpose of auto increment. Usually you would just not include that column in the insert, which is same as assigning null
As per the documentation page:
No value was specified for the AUTO_INCREMENT column, so MySQL assigned sequence numbers automatically. You can also explicitly assign 0 to the column to generate sequence numbers. If the column is declared NOT NULL, it is also possible to assign NULL to the column to generate sequence numbers.
So, because you have an auto increment null-allowed field, it ignores the fact that you're trying to place a NULL in there, and instead gives you a sequenced number.
You could just leave it as is since, even without the not null constraint, you can't get a NULL in there, because it will auto-magically convert that to a sequenced number.
Or you can change the column to be empid INT(11) PRIMARY KEY AUTO_INCREMENT NOT NULL if you wish, but I still think the insert will allow you to specify NULLs, converting them into sequenced numbers in spite of what the documentation states (tested on sqlfiddle in MySQL 5.6.6 m9 and 5.5.32).
In both cases, you can still force the column to a specific (non-zero) number, constraints permitting of course.
CREATE TABLE empInfo (
empid INT(11) PRIMARY KEY AUTO_INCREMENT NOT NULL,
firstname VARCHAR(255) DEFAULT NULL,
lastname VARCHAR(255) DEFAULT NULL
)
Not sure but i think it will work :)