157

I have a MySQL table with an auto increment primary key. I deleted some rows in the middle of the table. Now I have, for example, something like this in the ID column: 12, 13, 14, 19, 20. I deleted the 15, 16, 17 and 18 rows.

I want to reassign / reset / reorder the primary key so that I have continuity, i.e. make the 19 a 15, the 20 a 16, and so on.

How can I do it?

Pang
  • 9,564
  • 146
  • 81
  • 122
Jonathan
  • 8,676
  • 20
  • 71
  • 101

17 Answers17

409

Even though this question seems to be quite old, will post an answer for someone who reaches in here searching.

SET @count = 0;
UPDATE `users` SET `users`.`id` = @count:= @count + 1;

If the column is used as a foreign key in other tables, make sure you use ON UPDATE CASCADE instead of the default ON UPDATE NO ACTION for the foreign key relationship in those tables.

Further, in order to reset the AUTO_INCREMENT count, you can immediately issue the following statement.

ALTER TABLE `users` AUTO_INCREMENT = 1;

For MySQLs it will reset the value to MAX(id) + 1.

Anshul
  • 5,378
  • 2
  • 19
  • 18
  • 1
    This with foreign keys is a very cool solution for my table where lots of junk gets inserted and deleted and I want to save index space. – mukunda Sep 23 '14 at 05:50
  • 2
    mySQL Doc advises against this: " As a general rule, other than in SET statements, you should never assign a value to a user variable and read the value within the same statement. For example, to increment a variable, this is okay: SET @a = @a + 1; For other statements, such as SELECT, you might get the results you expect, but this is not guaranteed. In the following statement, you might think that MySQL will evaluate @a first and then do an assignment second: SELECT @a, @a:=@a+1, ...; However, the order of evaluation for expressions involving user variables is undefined. " – ReverseEMF Apr 08 '15 at 17:27
  • 3
    @ReverseEMF: No. The order of assignment is fixed in MySQL expressions. From what you quoted, MySQL documentation advices against multiple independent use of the variable. In the case above, the evaluation of the expression is bound to happen in a predefined order because of a single assignment expression ``users`.`id` = @count:= @count + 1`. From the documentation: "The value on the right hand side may be a literal value, another variable storing a value, or any legal expression that yields a scalar value" – Anshul Apr 13 '15 at 19:20
  • 1
    Is this a very expensive statement? How would it perform in a multi gigabyte table? I'm afraid of blowing up my ibdata1 (long transaction) and blocking the table for too long. – Stefan Jan 21 '17 at 18:05
  • Hey, I tried this and I receive an error: "You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'UPDATE products SET id = @count:= @count+1' at line 1". Any idea how to fix it or what I am doing wrong. It works perfectly in phpMyAdmin – John Smith Jul 29 '17 at 16:01
  • 1
    @Stefan On a table (5MB) with foreign keys that references another with +2GB of data, this script didnt took more than five minutes. The system has a ssd, so i suppose that it helped a lot. The fk had the ON UPDATE CASCADE – fernandezr Mar 15 '19 at 13:52
  • @Vadim Tatarnikov ... your error it sem that you try to execute multiple query in a prepared statement PDO or Stored procedure. In mi case until today every time that I have tried this solution has working good but today I have one error that can't explain: `Duplicate entry 7859 for key PRIMARY` and no way to solve this. Any help are welcome – MTK May 19 '19 at 23:11
  • How can you do it in JPA query? – Yaron Dec 30 '20 at 07:41
106

You could drop the primary key column and re-create it. All the ids should then be reassigned in order.

However this is probably a bad idea in most situations. If you have other tables that have foreign keys to this table then it will definitely not work.

Tom Haigh
  • 57,217
  • 21
  • 114
  • 142
  • I have other tables that hold a foreign key to this table, but I'm just starting the project so it's OK for me.. Thanks! – Jonathan Apr 11 '09 at 16:24
  • 71
    It might be best long term if you try and start accepting that your IDs won't always be sequential, otherwise when you start working on bigger projects it'll really drive out crazy! – Ciaran McNulty Apr 11 '09 at 20:07
  • 9
    ALTER TABLE your_table AUTO_INCREMENT =1 – Sinac May 20 '16 at 10:25
81

To reset the IDs of my User table, I use the following SQL query. It's been said above that this will ruin any relationships you may have with any other tables.

ALTER TABLE `users` DROP `id`;
ALTER TABLE `users` AUTO_INCREMENT = 1;
ALTER TABLE `users` ADD `id` int UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY FIRST;
rxgx
  • 5,089
  • 2
  • 35
  • 43
33

You may simply use this query

alter table abc auto_increment = 1;
Aaron W.
  • 9,254
  • 2
  • 34
  • 45
  • 2
    This won't work in this case. For ISAM tables, it will set the autoinc value to max(id) + 1. For InnoDB it will do nothing. See alter table docs for changing AUTOINCREMENT http://dev.mysql.com/doc/refman/5.0/en/alter-table.html – lreeder Dec 09 '13 at 19:10
  • 3
    @Ireeder from 5.6 onwards behavior for innodb is similar to that of myisam – Anshul Jun 30 '14 at 10:03
  • If you have other tables that have foreign keys to this table then will this break them? – Kyle Vassella Oct 31 '18 at 00:41
20
SET  @num := 0;

UPDATE your_table SET id = @num := (@num+1);

ALTER TABLE your_table AUTO_INCREMENT =1;

I think this will do it

BANK
  • 221
  • 2
  • 10
12

Or, from PhpMyAdmin, remove "AutoIncrement" flag, save, set it again and save.this resets it.

lbrutti
  • 1,181
  • 13
  • 19
  • 1
    Sorry, I can't test with current phpmyadmin versions. My answer Is quite old... If you downvoted me, can you please amend It? – lbrutti Nov 26 '19 at 14:12
7
SELECT * from `user` ORDER BY `user_id`; 

SET @count = 0;

UPDATE `user`  SET `user_id` = @count:= @count + 1;

ALTER TABLE `user_id` AUTO_INCREMENT = 1;

if you want to order by

Derrick
  • 3,669
  • 5
  • 35
  • 50
Shubham Malik
  • 105
  • 1
  • 2
  • 9
2

in phpmyadmin

note: this will work if you delete last rows not middle rows.

goto your table-> click on operations menu-> goto table options->change AUTO_INCREMENT to that no from where you want to start.

your table autoincrement start from that no.

try it. enter image description here

Anjani Barnwal
  • 1,362
  • 1
  • 17
  • 23
2

This works - https://stackoverflow.com/a/5437720/10219008.....but if you run into an issue 'Error Code: 1265. Data truncated for column 'id' at row 1'...Then run the following. Adding ignore on the update query.

SET @count = 0;
set sql_mode = 'STRICT_ALL_TABLES';
UPDATE IGNORE web_keyword SET id = @count := (@count+1);
Anush B M
  • 91
  • 1
  • 6
1

I had the same doubts, but could not make any changes on the table, I decided doing the following having seen my ID did not exceed the maximum number setted in the variable @count:

SET @count = 40000000;
UPDATE `users` SET `users`.`id` = @count:= @count + 1;

SET @count = 0;
UPDATE `users` SET `users`.`id` = @count:= @count + 1;

ALTER TABLE `users` AUTO_INCREMENT = 1;

The solution takes, but it's safe and it was necessary because my table owned foreign keys with data in another table.

Rodrigo Prazim
  • 788
  • 7
  • 14
0

You can remove the primary key auto increment functionality of that column, then every time you update that column run a query before hand that will count all the rows in the table, then run a loop that iterates through that row count inserting each value into the respective row, and finally run a query inserting a new row with the value of that column being the total row count plus one. This will work flawlessly and is the most absolute solution to someone trying to accomplish what you are. Here is an example of code you may use for the function:

$table_row_count = mysql_result(mysql_query("SELECT COUNT(`field_1`) FROM `table`"), 0);
$viewsrowsdata = mysql_query("
    SELECT `rank`, `field1`, `field2`, `field3`, `field4`
        FROM (SELECT (@rank:=@rank+1) as `rank`, `field1`, `field2`, `field3`, `field4`
            FROM (SELECT * FROM `views`) a
            CROSS JOIN (SELECT @rank:=0) b
            ORDER BY rank ASC) c
");
while ($row = mysql_fetch_assoc($viewsrowsdata)) {
    $data[] = $row;
}
foreach ($data as $row) {
    $new_field_1 = (int)$row['rank'];
    $old_field_1 = (int)$row['field1'];
    mysql_query("UPDATE `table` SET `field_1` = $new_field_1 WHERE `field_1` = $old_field_1");
}
mysql_query("INSERT INTO `table` (`field1`, `field2`, `field3`, `field4`) VALUES ('$table_row_count' + 1, '$field_2_value', 'field_3_value', 'field_4_value')");

Here I created an associative array which I had appended on a rank column with the query within a select query, which gave each row a rank value starting with 1. I then iterated through the associative array.

Another option would have been to get the row count, run a basic select query, get the associative array and iterate it through the same way but with an added variable that updates through each iteration. This is less flexible but will accomplish the same thing.

$table_row_count = mysql_result(mysql_query("SELECT COUNT(`field_1`) FROM `table`"), 0);
$viewsrowsdata = mysql_query("SELECT * FROM `table`");
$updated_key = 0;
while ($row = mysql_fetch_assoc($viewsrowsdata)) {
    $data[] = $row;
}
foreach ($data as $row) {
    $updated_key = $updated_key + 1;
    mysql_query("UPDATE `table` SET `field_1` = '$updated_key' WHERE `field_1` = '$row['field_1']'");
}
mysql_query("INSERT INTO `table` (`field1`, `field2`, `field3`, `field4`) VALUES ('$table_row_count' + 1, '$field_2_value', 'field_3_value', 'field_4_value')");
willy
  • 33
  • 10
0

for InnoDB, do this (this will remove all records from a table, make a bakcup first):

SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS ;
SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION ;
SET NAMES utf8 ;
SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 ;
SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 ;
SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' ;
SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 ;
/* ================================================= */

drop table tablename;
CREATE TABLE `tablename` (
   table structure here!

) ENGINE=InnoDB AUTO_INCREMENT=  ai number to reset  DEFAULT CHARSET= char set here;



/* ================================================= */
SET SQL_MODE=@OLD_SQL_MODE ;
SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS ;
SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS ;
SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT ;
SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS ;
SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION ;
SET SQL_NOTES=@OLD_SQL_NOTES ;
Luis
  • 9
  • 1
0

The best choice is to alter the column and remove the auto_increment attribute. Then issue another alter statement and put auto_increment back onto the column. This will reset the count to the max+1 of the current rows and thus preserve foreign key references back to this table, from other tables in your database, or any other key usage for that column.

Grwww
  • 51
  • 4
0

SELECT * from user ORDER BY user; SET @count = 0; UPDATE user SET uid = @count:= @count + 1; ALTER TABLE user AUTO_INCREMENT = 1;

if you want to order by

Shubham Malik
  • 105
  • 1
  • 2
  • 9
0

My opinion is to create a new column called row_order. then reorder that column. I'm not accepting the changes to the primary key. As an example, if the order column is banner_position, I have done something like this, This is for deleting, updating, creating of banner position column. Call this function reorder them respectively.

public function updatePositions(){
    $offers = Offer::select('banner_position')->orderBy('banner_position')->get();
    $offersCount = Offer::max('banner_position');
    $range = range(1, $offersCount);

    $existingBannerPositions = [];
    foreach($offers as $offer){
        $existingBannerPositions[] = $offer->banner_position;
    }
    sort($existingBannerPositions);
    foreach($existingBannerPositions as $key => $position){
        $numbersLessThanPosition = range(1,$position);
        $freshNumbersLessThanPosition = array_diff($numbersLessThanPosition, $existingBannerPositions);
        if(count($freshNumbersLessThanPosition)>0) {
            $existingBannerPositions[$key] = current($freshNumbersLessThanPosition);
            Offer::where('banner_position',$position)->update(array('banner_position'=> current($freshNumbersLessThanPosition)));
        }
    }
}
Viraj Amarasinghe
  • 911
  • 10
  • 20
0

Delete your id column and execute ALTER TABLE table_name ADD COLUMN id INT NOT NULL AUTO_INCREMENT UNIQUE;

-1

You can also simply avoid using numeric IDs as Primary Key. You could use Country codes as primary id if the table holds countries information, or you could use permalinks, if it hold articles for example.

You could also simply use a random, or an MD5 value. All this options have it's own benefits, specially on IT sec. numeric IDs are easy to enumerate.

Chris Russo
  • 450
  • 1
  • 7
  • 21
  • 1
    ... What do you base this on? Maybe just dont make pages like "complete_user_info_export.php?userid=34"? Internally using a string or other random value as index / identifier is a _really_ bad idea. It creates more problems than it solves (if it even solves any problems) – Rob Feb 01 '17 at 19:03
  • MD5 value is the absolute worst possibility because its possible that two different values or datasets produce the same MD5 value. So I wouldn't call it a solution. – David May 18 '19 at 01:19