Autoincrementing a field till a certain condition is met mysql - mysql

I have the following table
SNo Value Item
where Sno is a column which exists in another table also. Right now , what I need is a self incrementing field which will go on incrementing if the value of sno is a constant and then get back to 0 and start incrementing again once the value of sno changes. IS there any way to do this?
Lets say I have four columns:
SNO |Value |Item | AUtoIncrementingField
1 344 a 0
1 345 b 1
1 346 c 2
2 568 d 0
So when I say insert into this table , and the value of SNO changes from whatr it originally was the value of the auto incrementing field should go back to 0. Is there any inbuilt way of doing this, or writing some code on top of mysql to achieve this. If not what other option do I have to uniquely identify each value/item belonging to a certain value of sno?

Whilst this doesn't help you on InnoDB, it's worth pointing out that MyISAM natively supports this functionality. As documented under Using AUTO_INCREMENT:
MyISAM Notes
For MyISAM 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. This is useful when you want to put data into ordered groups.
CREATE TABLE animals (
grp ENUM('fish','mammal','bird') NOT NULL,
id MEDIUMINT NOT NULL AUTO_INCREMENT,
name CHAR(30) NOT NULL,
PRIMARY KEY (grp,id)
) ENGINE=MyISAM;
INSERT INTO animals (grp,name) VALUES
('mammal','dog'),('mammal','cat'),
('bird','penguin'),('fish','lax'),('mammal','whale'),
('bird','ostrich');
SELECT * FROM animals ORDER BY grp,id;
Which returns:
+--------+----+---------+
| grp | id | name |
+--------+----+---------+
| fish | 1 | lax |
| mammal | 1 | dog |
| mammal | 2 | cat |
| mammal | 3 | whale |
| bird | 1 | penguin |
| bird | 2 | ostrich |
+--------+----+---------+
In this case (when the AUTO_INCREMENT column is part of a multiple-column index), AUTO_INCREMENT values are reused if you delete the row with the biggest AUTO_INCREMENT value in any group. This happens even for MyISAM tables, for which AUTO_INCREMENT values normally are not reused.

Related

How to make SQL Primary key have a specific number of characters?

I'm working on a MySQL database for shop items. I want these shop items to have IDs like 0001, 0002 etc. But if I use AUTO_INCREMENT (which I need) it will go as 1, 2 etc. Is there any way to make AUTO_INCREMENT for PRIMARY KEY work this way because I need IDs to have a specific number of characters?
This is the code where I'm creating the items table:
CREATE TABLE items (
item_id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(30) NOT NULL,
price FLOAT NOT NULL,
discount INT
);
This is the Python loop where I'm putting all items from .csv file into the database table:
for item in items_list:
mycursor.execute(f"INSERT INTO items(name, price, discount) VALUES ({item['name']}, {item['price']}, {item['discount']});")
Is it possible to make AUTO_INCREMENT work that way or I need to do it manually?
Primary keys need to have one job only, that of uniquely identifying a row. As soon as you start trying to make them look presentable by formatting them or make them sequential without gaps, or even when you try to use them to see if one row was created before another, you create reasons to want to change them.
Practically anything visible to users or involved in business logic is going to end up needing to change. And primary keys shouldn't change. Changing a primary key means deleting the row and making a new one with the new key value, and also fixing all the references to the old key. It's fiddly and tedious and error-prone, it is something you want to avoid.
Make a separate column for a user-visible identifier separate from the PK that you can have full control over. You can populate it with a trigger or application code based off the key if you want. Just keep it separate from the primary key.
Auto_incrememts are tricky, because they can't be used in BEFORE INSERT TRIGGER it is alays 0
so you need another table and a AFTER INSERT TRIIGGER
CREATE TABLE items (
item_id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(30) NOT NULL,
price DECIMAL(10,2) NOT NULL,
discount INT
);
CREATE TABLE t1 (
item_id_1 varchar(8) )
CREATE TRIGGER ins_sum AFTER INSERT ON items
FOR EACH ROW INSERT INTO t1 VALUES(LPAD (NEW.item_id , 8, '0'));
INSERT INTO items (name,price, discount) VALUES ('test1',1.1,1)
INSERT INTO items (name,price, discount) VALUES ('test2',1.1,1)
INSERT INTO items (name,price, disc
SELECT * FROM t1
| item_id_1 |
| :-------- |
| 00000001 |
| 00000002 |
| 00000003 |
SELECT *,(SELECT item_id_1 FROM t1 WHERE item_id_1 + 0 = i.item_id) FROM items i
item_id | name | price | discount | (SELECT item_id_1 FROM t1 WHERE item_id_1 + 0 = i.item_id)
------: | :---- | ----: | -------: | :---------------------------------------------------------
1 | test1 | 1.10 | 1 | 00000001
2 | test2 | 1.10 | 1 | 00000002
3 | test3 | 1.10 | 1 | 00000003
SELECT i.*,t1.item_id_1 FROM items i JOIN t1 ON i.item_id = t1.item_id_1 + 0
item_id | name | price | discount | item_id_1
------: | :---- | ----: | -------: | :--------
1 | test1 | 1.10 | 1 | 00000001
2 | test2 | 1.10 | 1 | 00000002
3 | test3 | 1.10 | 1 | 00000003
db<>fiddle here

Why the primary key is not the clustered index if another non clustered index is added in MariaDB

Hello I have a table created by the following query MariaDB version 10.5.9
CREATE TABLE `test` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`status` varchar(60) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `test_status_IDX` (`status`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4
I always thought that the primary key is by default the clustered index which also defines the order of the rows in the table but here it seems that the index on the status is picked as the clustered. Why is this happening and how can I change it?
MariaDB [test]> select * from test;
+----+--------+
| id | status |
+----+--------+
| 2 | cfrc |
| 5 | hjr |
| 1 | or |
| 3 | test |
| 6 | verve |
| 4 | yes |
+----+--------+
6 rows in set (0.001 sec)
It is not safe to assume that the results of SELECT will be ordered by any column across dB engines. You should always use ORDER BY col [ASC|DESC] if you expect sorting to happen. I see records being displayed in the order they were added, but that can change after deletions/insertions etc, and should not be relied on. See here for more details.
(I am going to cite MySQL docs in my answer but in the context of this question, the information applies to MariaDB as well.)
First of all, let's talk about index extensions. The InnoDB engine automatically creates an additional (composite) index behind the scenes whenever you define a secondary index (i.e. any index that is not the clustered index). That is called an index extension.
This extra index contains the columns you defined in your original secondary index (in the same order) with the columns of the primary key added after them. So, in your example, InnoDB creates an index extension for test_status_IDX (let's call it X), with columns (stauts, id).
Now let's look at the query select * from test;. There is no WHERE clause here, so all the optimizer needs to do to satisfy this query is fetch all columns for all rows of the table. This boils down to fetching status & id since there are no other columns in the table. These exact fields happen to be stored within the extended index X. This makes index X a covering index for this query. A covering index is an index that, given a query, can fully produce the results of the query without having to read any actual data rows.
Therefore, the optimizer reads & returns the values needed for the result of the query from index X, in the order that they appear there, which is by status, hence the order you observed.
To further demonstrate and extend (pun intended) this point, let's reproduce the example (tested with MariaDB 10.4):
1. First create the table & add the rows
CREATE TABLE foo (
id int(10) unsigned NOT NULL AUTO_INCREMENT,
status varchar(60) DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB;
INSERT INTO foo VALUES
(1, 'or'),
(2, 'cfrc'),
(3, 'test'),
(4, 'yes'),
(5, 'hjr'),
(6, 'verve');
SELECT * FROM foo;
+----+--------+
| id | status |
+----+--------+
| 1 | or |
| 2 | cfrc |
| 3 | test |
| 4 | yes |
| 5 | hjr |
| 6 | verve |
+----+--------+`
2. Now let's add the secondary index and check the order again
CREATE INDEX secondary_idx ON foo (status);
SELECT * FROM foo;
+----+--------+
| id | status |
+----+--------+
| 2 | cfrc |
| 5 | hjr |
| 1 | or |
| 3 | test |
| 6 | verve |
| 4 | yes |
+----+--------+
As described above, the rows are returned in the order they appear in the (extended) secondary_idx
3. Now let's drop the index and re-add it with a prefix length of 2 bytes. This means that the index will not store the full value of the column but only its first two bytes, which means the extended index is no longer a covering index because it cannot fully produce the results of the query. Thus the clustered index will be used
ALTER TABLE foo DROP INDEX secondary_idx;
CREATE INDEX secondary_idx ON foo (status(2));
SELECT * FROM foo;
+----+--------+
| id | status |
+----+--------+
| 1 | or |
| 2 | cfrc |
| 3 | test |
| 4 | yes |
| 5 | hjr |
| 6 | verve |
+----+--------+
4. Let's showcase this behaviour in another way. Here we will retain the original secondary index (without a prefix length) but we will add a 3rd column to the table. This will once again render the secondary index a non covering index (because it does not contain the 3rd column), therefore, the clustered index will be used here as well.
ALTER TABLE foo DROP INDEX secondary_idx;
CREATE INDEX secondary_idx ON foo (status);
ALTER TABLE foo ADD bar integer NOT NULL;
SELECT * FROM foo;
+----+--------+-----+
| id | status | bar |
+----+--------+-----+
| 1 | or | 0 |
| 2 | cfrc | 0 |
| 3 | test | 0 |
| 4 | yes | 0 |
| 5 | hjr | 0 |
| 6 | verve | 0 |
+----+--------+-----+
Adding bar to the index (or dropping it from the table) will again make the query use the secondary index.
ALTER TABLE foo DROP INDEX secondary_idx;
CREATE INDEX secondary_idx ON foo (status, bar);
SELECT * FROM foo;
+----+--------+-----+
| id | status | bar |
+----+--------+-----+
| 2 | cfrc | 0 |
| 5 | hjr | 0 |
| 1 | or | 0 |
| 3 | test | 0 |
| 6 | verve | 0 |
| 4 | yes | 0 |
+----+--------+-----+
You can also use EXPLAIN on all of the SELECT statements above to see which index is used at each stage.
#aprsa is right I falsely assumed that the results will be in the same order as the clustered index but in this case(using INNODB) the status index is used for the query's evaluation so that's why it appears to be 'sorted' by the status. If I select the id then the primary index is used and the results appear to be 'sorted' by the id. In another engine this might not be true.
That particular table is composed of 2 BTrees:
The data, sorted by the PRIMARY KEY. Yes, it is clustered and is ordered 1,2,3,...
The secondary index, sorted by status. Each secondary index contains a copy of the PK so that it can reach into the other BTree to get the rest of the columns (not that there are any more!). That is, the is BTree is equivalent to a 2-column table with PRIMARY KEY(status) plus an id.
Note how the output is in status order. I have to assume it decided to simply read the secondary index in its order to provide the results.
Yes, you must specify an ORDER BY if you want a particular ordering. You must not assume the details I just discussed. Who knows, tomorrow there may be something else going, such as an in-memory "hash" that has the information scrambled in some other way!
(This Answer applies to both MySQL and MariaDB. However, MariaDB is already playing a game with hashing that MySQL has not yet picked up. Be warned! Or simply add an ORDER BY.)

Insert with on duplicate key update ignores index

So I've got a table product_supplier that I need to add data to from import_tbl. Product_supplier has three columns product_id, supplier_id and price. Import_tbl has the same columns plus some extra. What's most important and what I can't get working is that when a specific combination of product_id and supplier_id exists, only the price should be updated. If that combination does not exist a new row needs to be added. I tried this query
INSERT INTO product_supplier (product_id, supplier_id, price)
SELECT i.product_id, i.supplier_id, i.price
FROM import_tbl i
ON DUPLICATE KEY UPDATE
price = i.price
This one works if I add a row with a new product_id, but it totally ignores the supplier_id. So it won't add new rows if I a row uses the same product_id but a different supplier_id.
I think this has something to do with indexes, and I tried unique indexes for both product_id, and supplier_id and a multiple-column index of both product_id and supplier_id. But when I put EXPLAIN in front of the query it never recognises any indexes. Please some help, thanks!
Table structure of product_supplier
+---------------------+---------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------------------+---------+------+-----+---------+----------------+
| product_supplier_id | int(11) | NO | PRI | NULL | auto_increment |
| product_id | int(11) | NO | UNI | 0 | |
| supplier_id | int(11) | NO | MUL | 0 | |
| price | int(11) | NO | | 0 | |
+---------------------+---------+------+-----+---------+----------------+
It looks like you have a key problem.
The "ON DUPLICATE KEY UPDATE" pays attention to the table's primary key only, in this case a combo primary of product_supplier_id plus product_id. The product_supplier_id field isn't being included in your INSERT, and is then being auto-generated.
If you really want to make this commit as a single action (instead of check for existing then choose to either insert or update) then you'll need to move the primary key to be based on a combo of product_id and supplier_id and drop the auto-increment field.
If you need to be able to have more than one price per product/supplier then you can't use ON DUPLICATE KEY UPDATE and will need to run multiple queries.

Create mysql unique key based on three columns and a specific value

I have a table with a unique index across two columns user_id and country_id
I have added a new column deleted_at so I can delete rows whilst keeping the data.
I would now like to update the unique key so that it is based on user_id, country_id and where deleted_at IS NULL. Is this possible, if so how?
+----+---------+------------+------------+
+ id | user_id | country_id | deleted_at |
+----+---------+------------+------------+
+ 2 | 3 | 1 | NULL |
+ 3 | 3 | 1 | 2012-10-16 |
| 4 | 3 | 1 | 2012-10-15 |
+----+---------+------------+------------+
Using the above as reference, rows could not be added because of id 2, however if row 2 was not set a new row could be created.
Modifying your table mytable should do the trick:
alter table mytable drop index user_country;
alter table mytable add
unique index user_country_deleted (user_id, country_id, deleted_at);
Edit: I was too quick. According to CREATE INDEX Syntax this works only for BDB storage.

How to implement sequence number database for each group of users?

It's easy to create a database table for storing sequence numbers ; but this design is suited for the event when the sequence is shared for all users. What I want is to create sequence for each group of users : this group can grow at any time because it's a database table , that is the administrator can create a group at any time and users are assigned to a specific group. So how to implement the sequence generation according to a group ?
if you are using myisam
http://dev.mysql.com/doc/refman/5.0/en/example-auto-increment.html
Below extracted from above links.
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. This is
useful when you want to put data into ordered groups.
CREATE TABLE animals (
grp ENUM('fish','mammal','bird') NOT NULL,
id MEDIUMINT NOT NULL AUTO_INCREMENT,
name CHAR(30) NOT NULL,
PRIMARY KEY (grp,id)
) ENGINE=MyISAM;
INSERT INTO animals (grp,name) VALUES
('mammal','dog'),('mammal','cat'),
('bird','penguin'),('fish','lax'),('mammal','whale'),
('bird','ostrich');
SELECT * FROM animals ORDER BY grp,id;
Which returns:
+--------+----+---------+
| grp | id | name |
+--------+----+---------+
| fish | 1 | lax |
| mammal | 1 | dog |
| mammal | 2 | cat |
| mammal | 3 | whale |
| bird | 1 | penguin |
| bird | 2 | ostrich |
+--------+----+---------+
For your case:
CREATE TABLE mytable (
user_id MEDIUMINT NOT NULL AUTO_INCREMENT,
group_id MEDIUMINT NOT NULL,
user_name CHAR(30) NOT NULL,
PRIMARY KEY (group_id,user_id)
) ENGINE=MyISAM;
INSERT INTO mytable (group_id, user_name) VALUES
(1,'alex'),(1,'jenny'),(2,'baz'),(1,'tim'),(2,'danny'),(3,'joe');
SELECT * FROM mytable ORDER BY group_id,user_id;
Returns:
user_id group_id user_name
1 1 alex
2 1 jenny
3 1 tim
1 2 baz
2 2 danny
1 3 joe