How to auto increment on unique value - mysql

Let's say I have this table, with both columns as primary keys, in mysql workbench and Innodb engine:
+--------+---------+
| grp | name |
+--------+---------+
| fish | lax |
| mammal | dog |
| mammal | bat |
| mammal | whale |
| bird | bat |
| bird | ostrich |
+--------+---------+
How can I add a column, behaving like this with grp column:
+--------+----+---------+
| grp | id | name |
+--------+----+---------+
| fish | 1 | lax |
| mammal | 2 | dog |
| mammal | 2 | cat |
| mammal | 2 | whale |
| bird | 3 | penguin |
| bird | 3 | ostrich |
+--------+----+---------+
Note that the actual table I want to alter is much bigger. Also note this is not what auto incrementing with MyISAM does and not what is asked here(but the answers could be helpful): How to auto increment on different foreign keys?

CREATE TABLE Groups (
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
grp VARCHAR...
PRIMARY KEY(id),
UNIQUE(grp)
) ENGINE=InnoDB
SELECT DISTINCT grp FROM YourTable;
That should give you the mapping between grp and id.
At this point, you should not generate a table with 3 columns (grp+id+name), but instead use the above Groups as a "normalization", which you seem to be doing. Then do
CREATE TABLE New LIKE YourTable;
ALTER TABLE New
ADD COLUMN id INT UNSIGNED NOT NULL,
DROP COLUMN grp;
# Note: if you have an index on grp, something else may need to be done.
INSERT INTO New
SELECT g.id, y.name
FROM Groups g
JOIN YourTable y ON y.grp = g.grp;
# At this point, verify that `New` has what you want. Then switch over:
RENAME TABLE YourTable TO Old, New TO YourTable;
DROP TABLE Old;
Edit I chose to create a new table rather than update the existing table -- because it will be (I think) considerably faster. With UPDATE, all the rows would need to be locked. With INSERT..SELECT, they are simply created.

Related

How to add a set of different values in one column(MySQL)?

Do not judge strictly, but I can not figure it out in any way
My table:
CREATE table courses (id INT PRIMARY KEY AUTO_INCREMENT,
-> faculty VARCHAR(55) NULL,
-> number INT(10) NULL,
-> diff VARCHAR(10) NULL);
mysql> select * from courses;
Target. Inject values ('ez', 'mid', 'hard') into diff column.
For exampl, im trying this:
mysql> INSERT courses (diff) VALUES ('ez');
OR
mysql> UPDATE courses SET faculty = 'chem', number = 2, diff = 'mid';
Add rows with empty id(values NULL).
PLZ help me!
I want to get this result
+----+---------+--------+------+
| id | faculty | number | diff |
+----+---------+--------+------+
| 1 | bio | 1 | ez |
| 2 | chem | 2 | mid |
| 3 | math | 3 | hard |
| 4 | geo | 4 | mid |
| 5 | gum | 5 | ez |
+----+---------+--------+------+
You can use a case expression in an UPDATE statements:
UPDATE courses
SET diff=CASE
WHEN faculty in ('bio', 'gum') THEN 'ez'
WHEN faculty in ('chem', 'geo') THEN 'mid'
WHEN faculty = 'math' THEN 'hard'
END;

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

how to omit mysql rows that match another table when neither is unique

I have two tables, one that holds potential items, the other holds completed items.
The potential item table currently contains the records that have also been added to the completed items table. I want to remove (either by deleting or selecting new results) the already completed items from the list of potential items.
In both tables, items may appear multiple times, and I only want to remove the number of items that are completed, not all that match.
The real data set is more larger of course, but here are samples.
Potential items:
mysql> select * from stack;
+----------+------+------+
| stack_id | type | name |
+----------+------+------+
| 3 | a | aa |
| 4 | b | bb |
| 5 | c | cc |
| 6 | d | dd |
| 7 | a | aa |
| 8 | b | bb |
+----------+------+------+
6 rows in set (0.00 sec)
Completed items
mysql> select * from temp;
+----------+------+------+
| item_id | type | name |
+----------+------+------+
| 1 | a | aa |
| 2 | b | bb |
| 6 | b | bb |
+----------+------+------+
3 rows in set (0.00 sec)
The IDs between tables do not correlate, so they should be ignored as far as finding matches.
I want to omit 1 instance of a/aa and 2 of b/bb since those have been completed and exist in the other table.
when I try this:
mysql> select stack.* from stack where (type,name) not in (select type,name from temp);
I get this:
+----------+------+------+
| stack_id | type | name |
+----------+------+------+
| 5 | c | cc |
| 6 | d | dd |
+----------+------+------+
2 rows in set (0.03 sec)
But this omitted both instances of type="a" and name="aa" and I want to only omit one of them (since it only exists once in the completed items table)
How do I get this?
+----------+------+------+
| stack_id | type | name |
+----------+------+------+
| 5 | c | cc |
| 6 | d | dd |
| 7 | a | aa |
+----------+------+------+
I don't care which instance of a/aa is deleted (whether id=7 or id=3)
The best I've been able to come up with is to use PHP rather than MySQL to loop through each record in temp and delete with a LIMIT 1 from stack.
But I'd rather not have to run code for this, I'd like to do it in queries, it works better that way in my workflow
Thanks!
CREATE TABLE `test`.`stack` (
`stack_id` INT UNSIGNED NOT NULL,
`type` VARCHAR(45) NULL,
`name` VARCHAR(45) NULL,
PRIMARY KEY (`stack_id`));
CREATE TABLE `test`.`temp` (
`item_id` INT UNSIGNED NOT NULL,
`type` VARCHAR(45) NULL,
`name` VARCHAR(45) NULL,
PRIMARY KEY (`item_id`));
and than something like this:
select
min(stack_id), type, name
from stack _s
inner join
(
select min(item_id) item_id, type, name
from temp
group by type, name
) _t using(type, name)
group by _s.type, _s.name
will give you only one the first item in temp:
stack_id
type
name
3
a
aa
4
b
bb

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!

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