MySQL-Update inserted auto increment primary key to custom value - mysql

When I insert a new row, an auto increment ID will be saved like 1,2,3..
I need to custom it using a Trigger so it will be saved like 20171,20172,20173..

This trigger would do this.
CREATE DEFINER=`root`#`%` TRIGGER `test_before_insert` BEFORE INSERT ON `test` FOR EACH ROW BEGIN
SET NEW.id = (
SELECT CONCAT(YEAR(CURDATE()),IFNULL(MAX(CAST(ids.id AS UNSIGNED))+1,1))
FROM (
SELECT RIGHT(t.id,LENGTH(t.id)-4) AS id
FROM test t
WHERE LEFT(t.id,4) = YEAR(CURDATE())
) ids
);
END
But there are many reason you would not want to. This will get exponentially more expensive as rows are inserted, and provides no viable sorting etc.

Related

How to make unique incrementing counter in MySQL

How to assign unique auto incrementing values to a certain column? Kind of like AUTO_INCREMENT does but it should be NULL at the time of insertion and assigned at some later point.
I have a table that gets regular data inserts and a few workers that process that data and set processed_at datetime field when they're done. Now I want incrementally select new processed rows since the last call. If I naively use where processed_at > #last_update_time I'm afraid there might be a situation where some records are processed at the same second and I miss some rows.
update: Can I just do
begin;
select #max := max(foo) from table1;
update table1 set foo = #max + 1 where id = 'bar' limit 1;
commit;
if foo column is indexed?
You can use a trigger to implement that.
CREATE TABLE my_increment (value INT, table_name TEXT);
INSERT INTO my_increment VALUES (0, 'your_table_name');
CREATE TRIGGER pk AFTER UPDATE ON your_table_name
BEGIN
UPDATE my_increment
SET value = value + 1
WHERE table_name = 'your_table_name';
UPDATE your_table_name
SET ID2 = (
SELECT value
FROM my_increment
WHERE table_name = 'your_table_name')
WHERE ROWID = new.ROWID;
END;
But bear in mind that this trigger will work on every execution of the Update query.
You can also do it manually:
Create the table to store increment value:
CREATE TABLE my_increment (value INT, table_name TEXT);
INSERT INTO my_increment VALUES (0, 'your_table_name');
Then when you want to update the table, get the last value from this table and insert value+1 to your column needed to be incremented.

Script/workflow insert not exist and auto increment

I create a script/workflow exportation/importation from 2 system.
I have Table1 {id, name, description}
I want to create a script (not a procedure). I could (I didnt succed) adding procedure into my workflow. (create and delete at the end)
id is auto increment
I cant change the table
I can be sure that between the time I start execution of my script and the end, there will not be an insertion of one of my items into the database.
The script insert {name,description} but I want to NOT insert if the element (name or name and description) is there.
BASE QUERY :
INSERT INTO TABLE1 (name,description) VALUES ('itemX','this is item X')
BASE Script :
Use database1;
BEGIN;
SELECT * FROM TABLE1 ;
SELECT * FROM TABLE3 ;
INSERT INTO TABLE1 (name,description) VALUES ('itemX','this is item X');
set #idTable1 = LAST_INSERT_ID();
INSERT INTO TABLE3 (idTable1,idTable2) VALUES (#idTable1,1);
INSERT INTO TABLE3 (idTable1,idTable2) VALUES (#idTable1,2);
SELECT * FROM TABLE1 ;
SELECT * FROM TABLE3 ;
ROLLBACK;
I want to protect the multiple insertion on TABLE1. But without changing the table.
Maybe I did it wrong
I tried IF but not working outside procedure.
I tried IGNORE (valid only if id is the same, but never the same, its
auto increment)
I tried WHEN
I tried ON DUPLICATE KEY
Because of #idTable1, I will need change the " set #idTable1 = LAST_INSERT_ID();" if I doesnt have if else. But if my item is the only one with the same "name", I can get this instead of last_insert_id.
I opted for creating procedure before my "BEGIN" and removed them at the end of the script.
Just create the table with name as primary key, then be sure that you take care of the key capitalization (uppercase or lowercase) to avoid duplicates.
CREATE TABLE TABLE1(
id INT AUTO_INCREMENT,
name CHAR(30),
description CHAR(100),
PRIMARY KEY (name)
)
create unique constraint on name field if possible.
Otherwise, create trigger before insert in order to ignore duplicate insertion.
Trigger for checking duplicate on two fields a and b:
delimiter //
drop trigger if exists aborting_trigger //
create trigger aborting_trigger before insert on t
for each row
begin
set #found := false;
select true into #found from t where a=new.a and b=new.b;
if #found then
signal sqlstate '45000' set message_text = 'duplicate insert';
end if;
end //
delimiter ;
The trigger here provides feature similar to unique constraint. After creation you should use INSERT IGNORE or INSERT ...ON DUPLICATE KEY UPDATE

Insert only when auto-increment id is not equal 6(for example)?

I have a table with 3 fields: Id(PK,AI), Name(varchar(36)), LName(varchar(36)).
I have to insert name and last name, Id inserts automatically because of it's constraints,
Is There a way to Jump id auto increment value when it reaches 6?
for instance do this 7 times:
Insert Into table(Name, LName) Values ('name1', 'lname1') "And jump id to 7 if it is going to be 6"
It may sound stupid to do this but I have the doubt.
Also Jump and do not record id 6.
record only, 1-5, 7,8,9 and so on
What I want to achieve starts from a Union:
Select * From TableNames
Union All
Select * From TableNames_general
In the TableNames_general I assign it's first value so that when the user sees the table for the first time it will be displayed the record I inserted.
The problem comes when the user inserts a new record, if the Id of the inserted record is the same as the one I have inserted it will be duplicated, that is why I want to achieve when the users inserts one record and if the last insert id already exists just jump that record. this is because I must have different ids due to its relationship among child tables.
Identity column generate values for you, And its best left this way, You have the ability to insert specific values in Identity column but its best left alone and let it generate values for you.
Imagine you have inserted a value explicitly in an identity column and then later on Identity column generates the same value for you, you will end up with duplicates.
If you want to have your input in that column then why bother with identity column anyway ??
Well this is not the best practice but you can jump to a specific number by doing as follows:
MS SQL SERVER 2005 and Later
-- Create test table
CREATE TABLE ID_TEST(ID INT IDENTITY(1,1), VALUE INT)
GO
-- Insert values
INSERT INTO ID_TEST (VALUE) VALUES
(1),(2),(3)
GO
-- Set idnentity insert on to insert values explicitly in identity column
SET IDENTITY_INSERT ID_TEST ON;
INSERT INTO ID_TEST (ID, VALUE) VALUES
(6, 6),(8,8),(9,9)
GO
-- Set identity insert off
SET IDENTITY_INSERT ID_TEST OFF;
GO
-- 1st reseed the value of identity column to any smallest value in your table
-- below I reseeded it to 0
DBCC CHECKIDENT ('ID_TEST', RESEED, 0);
-- execute the same commad without any seed value it will reset it to the
-- next highest idnetity value
DBCC CHECKIDENT ('ID_TEST', RESEED);
GO
-- final insert
INSERT INTO ID_TEST (VALUE) VALUES
(10)
GO
-- now select data from table and see the gap
SELECT * FROM ID_TEST
If you query the database to get the last inserted ID, then you can check if you need to increment it, by using a parameter in the query to set the correct ID.
If you use MSSQL, you can do the following:
Before you insert check for the current ID, if it's 5, then do the following:
Set IDENTITY_INSERT to ON
Insert your data with ID = 7
Set IDENTITY_INSERT to OFF
Also you might get away with the following scenario:
check for current ID
if it's 5, run DBCC CHECKIDENT (Table, reseed, 6), it will reseed the table and in this case your next identity will be 7
If you're checking for current identity just after INSERT, you can use SELECT ##IDENTITY or SELECT SCOPE_IDENTITY() for better results (as rcdmk pointed out in comments)
Otherwise you can just use select: SELECT MAX(Id) FROM Table
There's no direct way to influence the AUTO_INCREMENT to "skip" a particular value, or values on a particular condition.
I think you'd have to handle this in an AFTER INSERT trigger. An AFTER INSERT trigger can't update the values of the row that was just inserted, and I don't think it can make any modifications to the table affected by the statement that fired the trigger.
A BEFORE INSERT trigger won't work either, because the value assigned to an AUTO_INCREMENT column is not available in a BEFORE INSERT trigger.
I don't believe there's a way to get SQL Server IDENTITY to "skip" a particular value either.
UPDATE
If you need "unique" id values between two tables, there's a rather ugly workaround with MySQL: roll your own auto_increment behavior using triggers and a separate table. Rather than defining your tables with AUTO_INCREMENT attribute, use a BEFORE INSERT trigger to obtain a value.
If an id value is supplied, and it's larger than the current maximum value from the auto_increment column in the dummy auto_increment_seq table, we'd need to either update that row, or insert a new one.
As a rough outline:
CREATE TABLE auto_increment_seq
(id INT NOT NULL PRIMARY KEY AUTO_INCREMENT) ENGINE=MyISAM;
DELIMITER $$
CREATE TRIGGER TableNames_bi
BEFORE INSERT ON TableNames
FOR EACH ROW
BEGIN
DECLARE li_new_id INT UNSIGNED;
IF ( NEW.id = 0 OR NEW.id IS NULL ) THEN
INSERT INTO auto_increment_seq (id) VALUES (NULL);
SELECT LAST_INSERT_ID() INTO li_new_id;
SET NEW.id = li_new_id;
ELSE
SELECT MAX(id) INTO li_max_seq FROM auto_increment_seq;
IF ( NEW.id > li_max_seq ) THEN
INSERT INTO auto_increment_seq (id) VALUES (NEW.id);
END IF;
END IF;
END$$
CREATE TRIGGER TableNames_ai
AFTER INSERT ON TableNames
FOR EACH ROW BEGIN
DECLARE li_max_seq INT UNSIGNED;
SELECT MAX(id) INTO li_max_seq FROM auto_increment_seq;
IF ( NEW.id > li_max_seq ) THEN
INSERT INTO auto_increment_seq (id) VALUES (NEW.id);
END IF;
END;
DELIMITER ;
The id column in the table could be defined something like this:
TableNames
( id INT UNSIGNED NOT NULL DEFAULT 0 PRIMARY KEY
COMMENT 'populated from auto_increment_seq.id'
, ...
You could create an identical trigger for the other table as well, so the two tables are effectively sharing the same auto_increment sequence. (With less efficiency and concurrency than an Oracle SEQUENCE object would provide.)
IMPORTANT NOTES
This doesn't really insure that the id values between the tables are actually kept unique. That would really require a query of the other table to see if the id value exists or not; and if running with InnoDB engine, in the context of some transaction isolation levels, we might be querying a stale (as in, consistent from the point in time at the start of the transaction) version of the other table.
And absent some additional (concurrency killing) locking, the approach outline above is subject to a small window of opportunity for a "race" condition with concurrent inserts... the SELECT MAX() from the dummy seq table, followed by the INSERT, allows a small window for another transaction to also run a SELECT MAX(), and return the same value. The best we can hope for (I think) is for an error to be thrown due to a duplicate key exception.
This approach requires the dummy "seq" table to use the MyISAM engine, so we can get an Oracle-like AUTONOMOUS TRANSACTION behavior; if inserts to the real tables are performed in the context of a REPEATABLE READ or SERIALIZABLE transaction isolation level, reads of the MAX(id) from the seq table would be consistent from the snapshot at the beginning of the transaction, we wouldn't get the newly inserted (or updated) values.
We'd also really need to consider the edge case of an UPDATE of row changing the id value; to handle that case, we'd need BEFORE/AFTER UPDATE triggers as well.

Using and UPDATE Trigger to Modify the Row Being Updated

I have an employee table (em) that contains, among other things, a floor Id (fl_id) and room Id (rm_id). In certain circumstances (when em_loc_cnt = 0) I want to set the em record's fl_id and rm_id to null. Below is my code so far.
I am not sure how to refer to the fl_id & rm_id in the commented line. Will I run into issues because this trigger is being called as a result of the em record being updated and I am updating that same record in the trigger?
Suggestions?
IF EXISTS (SELECT * FROM [sysobjects] WHERE [name] = 'em_upd_self_serv_t' AND [type] = 'TR')
BEGIN
DROP TRIGGER [dbo].[em_upd_self_serv_t]
END
GO
CREATE TRIGGER [dbo].[em_upd_self_serv_t]
ON [dbo].[em]
AFTER UPDATE
AS
BEGIN
DECLARE #em_id VARCHAR(35),
#em_loc_cnt INT;
SET #em_id = (SELECT em_id FROM inserted);
SET #em_loc_cnt = (SELECT COUNT(*) FROM emlocs WHERE em_id = #em_id);
IF (#em_loc_cnt = 0)
BEGIN
-- I want to set the fl_id and the rm_id to NULL
END
END;
Your fundamental flaw is that you seem to expect the trigger to be fired once per row - this is NOT the case in SQL Server. Instead, the trigger fires once per statement, and the pseudo table Inserted might contain multiple rows.
Given that that table might contain multiple rows - which one do you expect will be selected here??
SET #em_id = (SELECT em_id FROM inserted);
It's undefined - you might get the values from arbitrary rows in Inserted.
You need to rewrite your entire trigger with the knowledge the Inserted WILL contain multiple rows! You need to work with set-based operations - don't expect just a single row in Inserted !
You need to change your code to something like this:
IF EXISTS (SELECT * FROM sys.triggers WHERE [name] = 'em_upd_self_serv_t')
DROP TRIGGER [dbo].[em_upd_self_serv_t]
GO
CREATE TRIGGER [dbo].[em_upd_self_serv_t]
ON [dbo].[em]
AFTER UPDATE
AS
UPDATE dbo.em
SET fl_id = NULL, rm_id = NULL
FROM Inserted i
WHERE em_id = i.em_id
AND NOT EXISTS (SELECT * FROM dbo.emlocs WHERE em_id = i.em_id)

remove gaps in auto increment

Say I have a MySQL table with an auto incrementing id field, then I insert 3 rows. Then, I delete the second row. Now the id's of the table go 1,3. Can I get MySQL to correct that and make it 1,2 without having to write a program to do so?
MySQL won't let you change the indexing of an Auto-Index column once it's created. What I do is delete the Auto-Index column and then add a new one with the same name, mysql will index the newly generated column with no gaps. Only do this on tables where the Auto-Index is not relevant to the rest of the data but merely used as a reference for updates and deletes.
For example I recently did just that for a table containing proverbs where the Auto-Index column was only used when I updated or deleted a proverb but I needed the Auto-Index to be sequential as the proverbs are pulled out via a random number between 1 and the count of the proverbs, having gaps in the sequence could have led to the random number pointing to a non-existant index.
HTH
Quoting from The Access Ten Commandments (and it can be extensible to other RDBMS: "Thou shalt not use Autonumber (or Auto Incremental) if the field is meant to have meaning for thy users".
The only alternative I can think of (using only MySQL) is to:
Create a trigger that adds the row number to a column (not the primary key)
Create a procedure to delete rows and update the row number (I couldn't make this work with triggers, sorry)
Example:
create table tbl_dummy(
id int unsigned not null auto_increment primary key,
row_number int unsigned not null default 0,
some_value varchar(100)
);
delimiter $$
-- This trigger will add the correct row number for each record inserted
-- to the table, regardless of the value of the primary key
create trigger add_row_number before insert on tbl_dummy
for each row
begin
declare n int unsigned default 0;
set n = (select count(*) from tbl_dummy);
set NEW.row_number = n+1;
end $$
-- This procedure will update the row numbers for the records stored
-- after the id of the soon-to-be-deleted record, and then deletes it.
create procedure delete_row_from_dummy(row_id int unsigned)
begin
if (select exists (select * from tbl_dummy where id = row_id)) then
update tbl_dummy set row_number = row_number - 1 where id > row_id;
delete from tbl_dummy where id = row_id;
end if;
end $$
delimiter ;
Notice that you'll be forced to delete the records one by one, and you'll be forced to get the correct primary key value of the record you want to delete.
Hope this helps