MySQL INSERT .. UPDATE breaks AUTO_INCREMENT? - mysql

There are the following two tables:
create table lol(id int auto_increment, data int, primary key id(id));
create table lol2(id int auto_increment, data int, primary key id(id));
Insert some values:
insert into lol2 (data) values (1),(2),(3),(4);
Now insert using select:
insert into lol (data) select data from lol2;
Do it again:
insert into lol (data) select data from lol2;
Now look at the table:
select * from lol;
I receive:
+----+------+
| id | data |
+----+------+
| 1 | 1 |
| 2 | 2 |
| 3 | 3 |
| 4 | 4 |
| 8 | 1 |
| 9 | 2 |
| 10 | 3 |
| 11 | 4 |
+----+------+
I'm puzzled by the gap between 4 and 8... What caused this and how can I do it so that there isn't a gap? Thanks a lot!

auto_increment does not guarantee to have increments by 1 in the ID column. And it cannot, because as soon as you work with parallel transactions it would break anyways:
BEGIN BEGIN
INSERT INTO lol VALUES(...) INSERT INTO lol VALUES(..)
... ...
COMMIT ROLLBACK
What ids should be assigned by the database? It cannot know in advance which transaction will succeed and which will be rolled back.
If you need a sequential numbering of your records you would use a query which returns that; e.g.
SELECT COUNT(*) as position, lol.data FROM lol
INNER JOIN lol2 ON lol.id < lol2.id
GROUP BY lol.id

Related

Everytime when the rows in table are deleted, The Auto_increment will be reset to 1 at the next insert

I'm working on MySql 5.6.19-log, Windows 7 Ultimate, X64. This thing confused me a long time, here is the thing:
I always set an Auto_Increment for primary key like 100001 when a table created, and it works fine when I insert some rows, delete something....., but if I deleted all of the rows, Then next time when I'm going to insert into this table, the Auto_Increment will reset to 1.
I don't know why, searching a lot and got nothing useful.
Hope someone can help me out!!! THANKS!!!
Run alter table after a truncate.
create table t123
( id int auto_increment primary key,
colB varchar(40) not null
) AUTO_INCREMENT=10000;
insert t123(colB) values ('a'),('b');
select * from t123;
+-------+------+
| id | colB |
+-------+------+
| 10000 | a |
| 10001 | b |
+-------+------+
truncate table t123;
insert t123(colB) values ('a'),('b');
select * from t123;
+----+------+
| id | colB |
+----+------+
| 1 | a |
| 2 | b |
+----+------+
truncate table t123;
alter table t123 auto_increment=10000; -- fire this off
insert t123(colB) values ('a'),('b');
select * from t123;
+-------+------+
| id | colB |
+-------+------+
| 10000 | a |
| 10001 | b |
+-------+------+
Mysql Manual Page on Alter Table

Restrict insertion based on a count

So, I need to safely restrict the insertion of entries in a table based on the count of other entries in that same table. Say we have the following table:
resource:(id, foreign_key)
I need to create up to a number of entries based on the foreign key. So, as soon as I reach a count, let's say 100 for our example, I want to restrict creating more entries.
The obvious answer would be something like that:
count the entries with the specified foreign key.
if count < limit insert the new entry
And in fact, that's what I have been using. The thing is, this approach is not fail-proof since between 1 and 2 there might occur another insertion. I considered the possibility of using transactions but (unless I'm completely misunderstanding transactions) this has the same issue:
start transaction
insert the new entry
if entries have exceeded the limit, rollback. otherwise commit
Now, say we already have 99/100 entries and two transactions run at the same time. They both will commit since they don't see each-other's entries.
Short of actually creating the entry and then delete it if it's invalid (which feels kindof messy in my mind) I can't think of a way to solve this issue. Any ideas?
edit: upon request I'm providing sample data:
table1
+-------------+------------------+------+-----+----------------+
| Field | Type | Null | Key | Extra |
+-------------+------------------+------+-----+----------------+
| id | int(10) unsigned | NO | PRI | auto_increment |
| limit | int(10) unsigned | NO | MUL | |
+-------------+------------------+------+-----+----------------+
table2
+-------------+------------------+------+-----+----------------+
| Field | Type | Null | Key | Extra |
+-------------+------------------+------+-----+----------------+
| id | int(10) unsigned | NO | PRI | auto_increment |
| foreign_id | int(10) unsigned | NO | MUL | |
+-------------+------------------+------+-----+----------------+
and some sample data:
table1
+----+----------+
| id | limit |
+----+----------+
| 1 | 5 |
+----+----------+
table2
+----+---------------+
| id | foreign_id |
+----+---------------+
| 1 | 1 |
+----+---------------+
| 2 | 1 |
+----+---------------+
| 3 | 1 |
+----+---------------+
| 4 | 1 |
+----+---------------+
At this point, let's say that two users attempt to create table2 entries. The first one will have to be accepted and the 2nd rejected.
With the first approach, if both users go through step 1 (counting the old entries) and then through step 2 (insert the new entry) both entries will be created.
With the second approach, if both of them run at the same time, they both will count 4 slots before themselves and commit instead of one of them rollbacking.
Halo Mate, a Stored Procedure similar to this structure may help you
UPDATE
DROP PROCEDURE IF EXISTS sp_insert_record;
DELIMITER //
CREATE PROCEDURE sp_insert_record(
IN insert_value1 INT(9),
IN chosen_id INT(9)
)
BEGIN
SELECT id, `limit`
INTO #id, #limit
FROM table1
WHERE id = chosen_id;
START TRANSACTION;
INSERT INTO table2 (id, foreign_id)
VALUES (insert_value1, chosen_id);
SELECT COUNT(id)
INTO #count
FROM table2
WHERE foreign_id = #id;
IF #count <= #limit THEN
COMMIT;
ELSE
ROLLBACK;
END IF;
END//
DELIMITER ;
By using a Stored Procedure, you can also add any validation or process based on your requirements.
Hope this can be of help, cheers!

MySQL database relationship without an ID

Hi StackOverflow community,
I have these two tables:
tbl_users
ID_user (PRIMARY KEY)
Username (UNIQUE)
Password
...
tbl_posts
ID_post (PRIMARY KEY)
Owner (UNIQUE)
Description
...
Why always everybody make database relationships with foreign keys? What about if I want to relate Username with Owner instead of doing ID_user with ID_user in both tables?
Username is UNIQUE and the Owner is the username of the creator of the post.
Can it be done like that? There is something to correct or make better? Maybe I have a misconception.
I would appreciate detailed and understandable answers.
Thank you in advance.
The reason is primarily for data integrity. The argument concerning performance is a little misleading. While neither exhaustive, nor definitive, I hope this little example will shed some light on that fact:
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(i INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,s CHAR(12) NOT NULL UNIQUE
);
STEP1:
INSERT IGNORE INTO my_table (s)
SELECT CONCAT(CHAR((RAND()*26)+97),CHAR((RAND()*26)+97),CHAR((RAND()*26)+97),CHAR((RAND()*26)+97),CHAR((RAND()*26)+97),CHAR((RAND()*26)+97)
,CHAR((RAND()*26)+97),CHAR((RAND()*26)+97),CHAR((RAND()*26)+97),CHAR((RAND()*26)+97),CHAR((RAND()*26)+97),CHAR((RAND()*26)+97)
);
STEP2:
INSERT IGNORE INTO my_table (s)
SELECT CONCAT(CHAR((RAND()*26)+97),CHAR((RAND()*26)+97),CHAR((RAND()*26)+97),CHAR((RAND()*26)+97),CHAR((RAND()*26)+97),CHAR((RAND()*26)+97)
,CHAR((RAND()*26)+97),CHAR((RAND()*26)+97),CHAR((RAND()*26)+97),CHAR((RAND()*26)+97),CHAR((RAND()*26)+97),CHAR((RAND()*26)+97)
)
FROM my_table;
[REPEAT STEP 2 SEVERAL TIMES]
SELECT COUNT(*) FROM my_table;
+----------+
| COUNT(*) |
+----------+
| 16384 |
+----------+
1 row in set (0.01 sec)
SELECT * FROM my_table ORDER BY i LIMIT 12;;
+----+------------+
| i | s |
+----+------------+
| 1 | kkxeehxsvy |
| 2 | iuyhrk{vaq |
| 3 | ngpedelooc |
| 4 | irkbyqgkhc |
| 6 | yqkcifcxdz |
| 7 | sgezlgvjjq |
| 8 | blavbvxbnl |
| 9 | wdbtqvgvgt |
| 13 | pakzpbnhxr |
| 14 | vpoy{gdwyd |
| 15 | ezlhz{drwg |
| 16 | ncwcwbpudh |
+----+------------+
SELECT * FROM my_table x JOIN my_table y ON y.i < x.i ORDER BY x.i,y.i LIMIT 1;
+---+------------+---+------------+
| i | s | i | s |
+---+------------+---+------------+
| 2 | iuyhrk{vaq | 1 | kkxeehxsvy |
+---+------------+---+------------+
1 row in set (1 min 22.60 sec)
SELECT * FROM my_table x JOIN my_table y ON y.s < x.s ORDER BY x.s,y.s LIMIT 1;
+-------+------------+------+------------+
| i | s | i | s |
+-------+------------+------+------------+
| 21452 | aabetdlvum | 6072 | aabdnegtav |
+-------+------------+------+------------+
1 row in set (1 min 13.59 sec)
So, we have two queries doing essentially the same thing (a comparison of 270 million values). The first joins the table to itself on an integer value. The second joins the table to itself on a string value. Both columns are indexed. As you can see, in this example, the string join actually performs better than the integer join - even though the hit on the CPU may actually be greater!

Adding auto increment column other than primary key

I have table in which has key is primary key. I want to add seqNo key which should be auto incremented but it does not allow to make it as auto increment.
Because there is already one primary key,
Is it possible to make seqNo auto increment? currently seqNo is not present. I want to add it
You can't have two identity columns in a SQL table but you can still create sequences. Here's the link http://technet.microsoft.com/en-us/library/ff878091.aspx
You have the following options.
Make a trigger that increments your column value on every insert statement
Use a sequence, but once a sequence value is generated it will never be generated again (meaning, you would get a gap in your values if your insert fails for some reason)
Vignesh, consider the following...
DROP TABLE IF EXISTS test;
CREATE TABLE test
( testID int(11) NOT NULL
, string varchar(45) DEFAULT NULL
, testInc int(11) NOT NULL AUTO_INCREMENT
, PRIMARY KEY (testID)
, KEY testInc (testInc)
);
INSERT INTO test
(testID
, string
) values
(1
,'Hello'
);
INSERT INTO test (testid,string) SELECT x.testid + y.max_test,string FROM test x JOIN (SELECT MAX(testid) max_test FROM test)y;
INSERT INTO test (testid,string) SELECT x.testid + y.max_test,string FROM test x JOIN (SELECT MAX(testid) max_test FROM test)y;
INSERT INTO test (testid,string) SELECT x.testid + y.max_test,string FROM test x JOIN (SELECT MAX(testid) max_test FROM test)y;
Query OK, 4 rows affected (0.03 sec)
SELECT * FROM test;
+--------+--------+---------+
| testID | string | testInc |
+--------+--------+---------+
| 1 | Hello | 1 |
| 2 | Hello | 2 |
| 3 | Hello | 3 |
| 4 | Hello | 4 |
| 5 | Hello | 6 |
| 6 | Hello | 7 |
| 7 | Hello | 8 |
| 8 | Hello | 9 |
+--------+--------+---------+
Note that the number of rows (8), and the value of testinc (9) are different. This is not what the OP wants. The MAX() trick I've used for generating the PK is also no good, because it's subject to runtime errors.
fiddle of same http://www.sqlfiddle.com/#!2/d29a5b/1
The point is... storing a sequential id is pointless.

Automating table normalization

I have a table with this structure (simplified):
artID: 1
artName: TNT
ArtBrand: ACME
...
And I want to normalize it making a separate table for the brand (it will have additional data about every brand)
So I want to end up with this
article table:
artID: 1
artName: TNT
brandID: 1
...
brand table
brandID: 1
brandName: ACME
brandInfo: xyz
....
This table have way too many brands to do this manually.
Any easy way to automate this?
I'm using MySQL
As the other answers suggested, you can use the INSERT ... SELECT syntax to do something like this:
INSERT INTO brands (brandName)
SELECT artBrand
FROM original
GROUP BY artBrand;
INSERT INTO articles (artName, brandID)
SELECT o.artName, b.brandID
FROM original o
JOIN brands b ON (b.brandName = o.artBrand);
Test case:
CREATE TABLE original (artID int, artName varchar(10), artBrand varchar(10));
CREATE TABLE articles (artID int auto_increment primary key, artName varchar(10), brandID int);
CREATE TABLE brands (brandID int auto_increment primary key, brandName varchar(10));
INSERT INTO original VALUES (1, 'TNT1', 'ACME1');
INSERT INTO original VALUES (2, 'TNT2', 'ACME1');
INSERT INTO original VALUES (3, 'TNT3', 'ACME1');
INSERT INTO original VALUES (4, 'TNT4', 'ACME2');
INSERT INTO original VALUES (5, 'TNT5', 'ACME2');
INSERT INTO original VALUES (6, 'TNT6', 'ACME3');
INSERT INTO original VALUES (7, 'TNT7', 'ACME3');
INSERT INTO original VALUES (8, 'TNT8', 'ACME3');
INSERT INTO original VALUES (9, 'TNT9', 'ACME4');
Result:
SELECT * FROM brands;
+---------+-----------+
| brandID | brandName |
+---------+-----------+
| 1 | ACME1 |
| 2 | ACME2 |
| 3 | ACME3 |
| 4 | ACME4 |
+---------+-----------+
4 rows in set (0.00 sec)
ELECT * FROM articles;
+-------+---------+---------+
| artID | artName | brandID |
+-------+---------+---------+
| 1 | TNT1 | 1 |
| 2 | TNT2 | 1 |
| 3 | TNT3 | 1 |
| 4 | TNT4 | 2 |
| 5 | TNT5 | 2 |
| 6 | TNT6 | 3 |
| 7 | TNT7 | 3 |
| 8 | TNT8 | 3 |
| 9 | TNT9 | 4 |
+-------+---------+---------+
9 rows in set (0.00 sec)
I would use create table as select
... syntax to create the brands
table with generated id-s
create the brand_id column, and fill it up with the generated id-s from the brands table, using the existing brand columns in article table.
remove the brand columns from article table except of course brand_id
create the foreign key...
Generating brands table should be fairly simple:
CREATE TABLE brands (
id INT PRIMARY KEY AUTO_INCREMENT,
brand_name VARCHAR(50),
brand_info VARCHAR(200)
);
INSERT INTO brands VALUES (brand_name)
SELECT ArtBrand FROM Table
GROUP BY ArtBrand;
Similar story with creating relations between your original table and new brands table, just that select statement in your insert will look like this:
SELECT t.artId, b.id
FROM table t JOIN brands b ON (t.ArtBrand = b.brand_name)