How to reduce the auto increment number in SQL database? - mysql

Currently the table structure is like this:
user_preference
---------------
id
user_id
pref_id
this table store all the user options, and id is auto -inc
the problems are:
1) is it necessary to keep an ID for every table ? It seems the common practice to keep a system generated id for every table
2) whenever the user update their perference, I will clear all related record for him and insert the update one, the auto-inc number will become very large later. How can I prevent that?
Thanks for helping.

You can periodically reset the auto increment counter back to 1, to ensure that the id does not become arbitrarily large (and sparse) over the course of frequent deletion of records.
In MySQL:
ALTER TABLE table_name AUTO_INCREMENT = 1
In SQL Server:
DBCC CHECKIDENT (table_name, RESEED, 0)
Each of these commands will reset the auto increment counter to 1, or to the value closest to 1 if 1 be already in use by another record.

You do not need to have an AUTO_INCREMENT PRIMARY KEY for every table. Sometimes there is a 'natural' key that works quite well for the PK.
Do not manipulate AUTO_INCREMENT values. Do not depend on any property other than uniqueness.
Your user_preference table smells like a many-to-many mapping? If so, this is optimal:
CREATE TABLE user_preference (
user_id ...,
pref_id ...,
PRIMARY KEY(user_id, pref_id),
INDEX (pref_id, user_id)
) ENGINE=InnoDB;
For discussion of "why", see http://mysql.rjweb.org/doc.php/index_cookbook_mysql#many_to_many_mapping_table

Related

Remove duplicate values without ID

I have a table like this:
uuid | username | first_seen | last_seen | score
Before, the table used the primary key of a "player_id" column that ascended. I removed this player_id as I no longer needed it. I want to make the 'uuid' the primary key, but there's a lot of duplicates. I want to remove all these duplicates from the table, but keep the first one (based off the row number, the first row stays).
How can I do this? I've searched up everywhere, but they all show how to do it if you have a row ID column...
I highly advocate having auto-incremented integer primary keys. So, I would encourage you to go back. These are useful for several reasons, such as:
They tell you the insert order of rows.
They are more efficient for primary keys.
Because primary keys are clustered in MySQL, they always go at the end.
But, you don't have to follow that advice. My recommendation would be to insert the data into a new table and reload into your desired table:
create temporary table tt as
select t.*
from tt
group by tt.uuid;
truncate table t;
alter table t add constraint pk_uuid primary key (uuid);
insert into t
select * from tt;
Note: I am using a (mis)feature of MySQL that allows you to group by one column while pulling columns not in the group by. I don't like this extension, but you do not specify how to choose the particular row you want. This will give values for the other columns from matching rows. There are other ways to get one row per uuid.

How to re-arrange database primary key

Good day
I create database at localhost for website. and put some info, than i delete and re-enter info from database. and now for 'id' primary key i have more than 200 rows. I want to re-arrange primary key.
for example
id |name
1 |Samuel
2 |Smith
4 |Gorge
15 |Adam
19 |David
i want to have
id |name
1 |Samuel
2 |Smith
3 |Gorge
4 |Adam
5 |David
Is it possible to do with any command?
You could drop the primary key column and re-create it. All the ids will then be reassigned, I assume in the order in which the rows were inserted.
alter table your_table drop column id;
then to create it
ALTER TABLE `your_table_name` ADD `id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY FIRST;
The purpose of a primary key is to uniquely identify each row, so rows in one table can be related to rows in another table. Remember, this is a relational database and part of the meaning of "relational" is that entities are related to each other.
In other words, you don't want to change the primary key of rows, because that will break links from other tables. MySQL does not guarantee that auto incremented values are inserted without holes. In fact, as you have discovered, deletions and re-inserts cause problems.
Your interpretation of the "primary key" as a sequential number with no gaps assigned to each row maintained by the database is simply not correct.
Even though you don't want to do this, you can. I advise against it, but you can:
declare #rn := 0;
update t
set id = (#rn := #rn + 1)
order by id;
If you want to enforce this over time, you will need to learn about triggers.
Consider this scenario: Gorge sends some offensive emails, and people complain and his account (#4) is denylisted.
Then you reorder your primary key values, and Adam is now assigned id 4. Suddenly, he finds himself banned! And lots of people mistrust him without cause.
Primary keys are not required to be consecutive -- they're only required to be unique. It's normal for there to be gaps, if you sometimes ROLLBACK transactions, or DELETE rows.
Most likely the primary key is being auto generated from some sort of auto increment sequence. In that case you can take the following steps:
1) update all the primary keys to the next value of the sequence: this will collapse all of the values into a contiguous range. In your case those ids will be 20, 21, 22, 23, 24. Postgres example:
UPDATE my_table SET id = nextval(my_table_id_sequence)
2) reset the sequence to start at 1: In Postgres this would look like the following:
ALTER SEQUENCE my_table_id_sequence RESTART WITH 1
3) update the values to the next value of the sequence again: Now can move all the rows back "down" to start at 1, and in your case they will be 1, 2, 3, 4, 5. It is important to first consolidate all the values at the "top" of the sequence before resetting, because that way we guarantee that there wont be any primary key collisions at the "bottom"
UPDATE my_table SET id = nextval(my_table_id_sequence)
NOTE: this approach only works if there are no foriegn keys which are referring to the primary key of the table. If there are foreign keys you can still take the same approach, but first do these 3 steps:
1) find all of the related tables/columns that are referencing this primary key column
2) create a function that will cascade updates to the pk out to all fks
3) create a trigger that will execute the above function whenever the pk is updated: at this point, when we update the primary key column, all of the related foreign keys will also be updated. Depending on the database, you might need to explicitly defer constraint validation, or do the whole thing in one transaction.
For an example of what the above might look like in Postgres you can take a look at my answer here How Do I Deep Copy a Set of Data, and Change FK References to Point to All the Copies?

How to have Unique IDs across two or more tables in MySQL?

I have a table called events where all new information goes. This table works as a reference for all queries for news feed(s) so event items are selected from there and information corresponding to that event is retrieved from the correct tables.
Now, here's my problem. I have E_ID's in the events table which correspond to the ID of an event in a different table, be it T_ID for tracks, S_ID for status and so on... These ID's could be the same so for the time being I just used a different auto_increment value for each table so status started on 500 tracks on 0 etc. Obviously, I don't want to do that as I have no idea yet of which table is going to have the most data in it. I would assume status would quickly exceed tracks.
The information is inserted into the event table with triggers. Here's an example of one;
BEGIN
INSERT INTO events (action, E_ID, ID)
VALUES ('has some news.', NEW.S_ID, NEW.ID);
END
That ones for he status table.
Is there an addition to that trigger I can make to ensure the NEW.S_ID != an E_ID currently in events and if it does change the S_ID accordingly.
Alternatively, is there some kind of key I can use to reference events when auto incrementing the S_ID so that the S_ID is not incremented to a value of E_ID.
Those are my thoughts, I think the latter solution would be better but I doubt it is possible or it is but would require another reference table and would be too complex.
It's really uncommon to require a unique id across tables, but here's a solution that will do it.
/* Create a single table to store unique IDs */
CREATE TABLE object_ids (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
object_type ENUM('event', ...) NOT NULL
) ENGINE=InnoDB;
/* Independent object tables do not auto-increment, and have a FK to the object_ids table */
CREATE TABLE events (
id INT UNSIGNED NOT NULL PRIMARY KEY,
...
CONSTRAINT FOREIGN KEY (id) REFERENCES object_ids (id)
) ENGINE=InnoDB;
/* When creating a new record, first insert your object type into the object_ids table */
INSERT INTO object_ids(object_type) VALUES ('event');
/* Then, get the auto-increment id. */
SET #id = LAST_INSERT_ID();
/* And finally, create your object record. */
INSERT INTO events (id, ...) VALUES (#id, ...);
Obviously, you would duplicate the structure of the events table for your other tables.
You could also just use a Universal Unique Identifier (UUID).
A UUID is designed as a number that is globally unique in space and time. Two calls to UUID() are expected to generate two different values, even if these calls are performed on two separate computers that are not connected to each other.
Please read more about it in the manual.
There's also a shorter version.
UUID_SHORT() should do the trick.
It will generate 64-bit unsigned integers for you.
According to the doc the generator logic is:
(server_id & 255) << 56
+ (server_startup_time_in_seconds << 24)
+ incremented_variable++;
The value of UUID_SHORT() is guaranteed to be unique if the following conditions hold:
The server_id value of the current server is between 0 and 255 and is unique among your set of master and slave servers
You do not set back the system time for your server host between mysqld restarts
You invoke UUID_SHORT() on average fewer than 16 million times per second between mysqld restarts
mysql> SELECT UUID_SHORT();
-> 92395783831158784
If you curious what is your server id you can use either of these:
SELECT ##server_id
SHOW VARIABLES LIKE 'server_id';

Database design: auto-increment key & update inconsistencies

Two tables share a unique identifier 'id'. Both tables are meant to be joined by using 'id'.
Defining 'id' as an auto incrementing primary key in both tables may risk update inconsistencies.
Is there some general pattern to avoid such a situation or do I have to deal with updating table1 first and table2 by utilizing the last inserted id after (therefore not declaring id as auto inc in table2)?
First, if you use InnoDB table engine in MySQL you could use both transactions and foreign keys for data consistency.
Second, after the insert in the first table, you could get the last insert id (depending on the way you access the db) and use it as foreign key.
Eg
Table 1: Users: user_id, username
Table 2: User_Profiles: user_id, name, phone
In User_Profiles you don't need to define user_id as auto increment, but first insert a record in Users table and use the user_id for the User_Profiles record. If you do this in transaction, the Users record won't be seen outside of the transaction connection until it's completed, this way you guarantee that even if something bad happens after you insert the user, but before you have inserted the profile - there won't be messed up data.
You could also define that the user_id column in User_Profiles table is foreign key of Users table thus if someone deletes a record from the Users table, the database would automatically delete the one in User_Profiles. There are many other options - read more about that.
There is no problem with same column name 'id' in any number of tables.
Several persistence layer frameworks do it same way.
Just use aliases in your SQL to distinct your tables accordingly.
do I have to deal with updating table1 first and table2 by utilizing the last inserted id after (therefore not declaring id as auto inc in table2)?
Yes. And make id a foreign key so it can only exist in table2 if it already exists in table1.
Yes you do, and remember to wrap the operation in a transaction.

How to fill in the "holes" in auto-increment fields?

I've read some posts about this but none cover this issue.
I guess its not possible, but I'll ask anyway.
I have a table with more than 50.000 registers. It's an old table where various insert/delete operations have taken place.
That said, there are various 'holes' some of about 300 registers. I.e.: ..., 1340, 1341, 1660, 1661, 1662,...
The question is. Is there a simple/easy way to make new inserts fill these 'holes'?
I agree with #Aaron Digulla and #Shane N. The gaps are meaningless. If they DO mean something, that is a flawed database design. Period.
That being said, if you absolutely NEED to fill these holes, AND you are running at least MySQL 3.23, you can utilize a TEMPORARY TABLE to create a new set of IDs. The idea here being that you are going to select all of your current IDs, in order, into a temporary table as such:
CREATE TEMPORARY TABLE NewIDs
(
NewID INT UNSIGNED AUTO INCREMENT,
OldID INT UNSIGNED
)
INSERT INTO NewIDs (OldId)
SELECT
Id
FROM
OldTable
ORDER BY
Id ASC
This will give you a table mapping your old Id to a brand new Id that is going to be sequential in nature, due to the AUTO INCREMENT property of the NewId column.
Once this is done, you need to update any other reference to the Id in "OldTable" and any foreign key it utilizes. To do this, you will probably need to DROP any foreign key constraints you have, update any reference in tables from the OldId to the NewId, and then re-institute your foreign key constraints.
However, I would argue that you should not do ANY of this, and just understand that your Id field exists for the sole purpose of referencing a record, and should NOT have any specific relevance.
UPDATE: Adding an example of updating the Ids
For example:
Let's say you have the following 2 table schemas:
CREATE TABLE Parent
(
ParentId INT UNSIGNED AUTO INCREMENT,
Value INT UNSIGNED,
PRIMARY KEY (ParentId)
)
CREATE TABLE Child
(
ChildId INT UNSIGNED AUTO INCREMENT,
ParentId INT UNSIGNED,
PRIMARY KEY(ChildId),
FOREIGN KEY(ParentId) REFERENCES Parent(ParentId)
)
Now, the gaps are appearing in your Parent table.
In order to update your values in Parent and Child, you first create a temporary table with the mappings:
CREATE TEMPORARY TABLE NewIDs
(
Id INT UNSIGNED AUTO INCREMENT,
ParentID INT UNSIGNED
)
INSERT INTO NewIDs (ParentId)
SELECT
ParentId
FROM
Parent
ORDER BY
ParentId ASC
Next, we need to tell MySQL to ignore the foreign key constraint so we can correctly UPDATE our values. We will use this syntax:
SET foreign_key_checks = 0;
This causes MySQL to ignore foreign key checks when updating the values, but it will still enforce the correct value type is used (see MySQL reference for details).
Next, we need to update our Parent and Child tables with the new values. We will use the following UPDATE statement for this:
UPDATE
Parent,
Child,
NewIds
SET
Parent.ParentId = NewIds.Id,
Child.ParentId = NewIds.Id
WHERE
Parent.ParentId = NewIds.ParentId AND
Child.ParentId = NewIds.ParentId
We now have updated all of our ParentId values correctly to the new, ordered Ids from our temporary table. Once this is complete, we can re-institute our foreign key checks to maintain referential integrity:
SET foreign_key_checks = 1;
Finally, we will drop our temporary table to clean up resources:
DROP TABLE NewIds
And that is that.
What is the reason you need this functionality? Your db should be fine with the gaps, and if you're approaching the max size of your key, just make it unsigned or change the field type.
You generally don't need to care about gaps. If you're getting to the end of the datatype for the ID it should be relatively easy to ALTER the table to upgrade to the next biggest int type.
If you absolutely must start filling gaps, here's a query to return the lowest available ID (hopefully not too slowly):
SELECT MIN(table0.id)+1 AS newid
FROM table AS table0
LEFT JOIN table AS table1 ON table1.id=table0.id+1
WHERE table1.id IS NULL
(remember to use a transaction and/or catch duplicate key inserts if you need concurrent inserts to work.)
INSERT INTO prueba(id)
VALUES (
(SELECT IFNULL( MAX( id ) , 0 )+1 FROM prueba target))
IFNULL for skip null on zero rows count
add target for skip error mysql "error clause FROM)
There is a simple way but it doesn't perform well: Just try to insert with an id and when that fails, try the next one.
Alternatively, select an ID and when you don't get a result, use it.
If you're looking for a way to tell the DB to automatically fill the gaps, then that's not possible. Moreover, it should never be necessary. If you feel you need it, then you're abusing an internal technical key for something but the single purpose it has: To allow you to join tables.
[EDIT] If this is not a primary key, then you can use this update statement:
update (
select *
from table
order by reg_id -- this makes sure that the order stays the same
)
set reg_id = x.nextval
where x is a new sequence which you must create. This will renumber all existing elements preserving the order. This will fail if you have foreign key constraints. And it will corrupt your database if you reference these IDs anywhere without foreign key constraints.
Note that during the next insert, the database will create a huge gap unless you reset the identity column.
As others have said, it doesn't matter, and if it does then something is wrong in your database design. But personally I just like them to be in order anyway!
Here is some SQL that will recreate your IDs in the same order, but without the gaps.
It is done first in a temp_id field (which you will need to create), so you can see that it is all good before overwriting your old IDs. Replace Tbl and id as appropriate.
SELECT #i:=0;
UPDATE Tbl
JOIN
(
SELECT id
FROM Tbl
ORDER BY id
) t2
ON Tbl.id = t2.id
SET temp_id = #i:=#i+1;
You will now have a temp_id field with all of your shiny new IDs. You can make them live by simply:
UPDATE Tbl SET id = temp_id;
And then dropping your temp_id column.
I must admit I'm not quite sure why it works, since I would have expected the engine to complain about duplicate IDs, but it didn't when I ran it.
You might wanna clean up gaps in a priority column.
The way below will give an auto increment field for the priority.
The extra left join on the same tabel will make sure it is added in the same order as (in this case) the priority
SET #a:=0;
REPLACE INTO footable
(id,priority)
(
SELECT tbl2.id, #a
FROM footable as tbl
LEFT JOIN footable as tbl2 ON tbl2.id = tbl.id
WHERE (select #a:=#a+1)
ORDER BY tbl.priority
)