mysql bulk update multiple records at once - mysql

I have data such as:
id table column add edit delete view
1 vendors city 0 0 0 1
1 vendors state 0 0 0 1
1 vendors zip 0 0 0 1
And i am trying to perform a bulk update with a similar query:
UPDATE user_perms SET add = ?, edit = ?, delete = ?, view = ?
WHERE id = ? AND table_name = ? AND column_name = ?
Without having to loop through a list and run this query X number of times, does MySQL support a way to bulk update in 1 go?
CREATE TABLE STMT:
CREATE TABLE `user_perms` (
`up_id` int unsigned NOT NULL AUTO_INCREMENT,
`id` int unsigned DEFAULT NULL,
`table_name` varchar(255) DEFAULT NULL,
`column_name` varchar(255) DEFAULT NULL,
`add` int unsigned DEFAULT NULL,
`edit` int unsigned DEFAULT NULL,
`delete` int unsigned DEFAULT NULL,
`view` int unsigned DEFAULT NULL,
PRIMARY KEY (`up_id`),
UNIQUE KEY `up_id_UNIQUE` (`up_id`),
KEY `fk_idx` (`id`),
CONSTRAINT `fk_id` FOREIGN KEY (`id`) REFERENCES `users` (`user_id`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=1659 DEFAULT CHARSET=utf8

In your case, the closest thing you could do to update multiple rows is to set the values using CASE expressions:
UPDATE user_perms
SET add = CASE column_name WHEN 'city' THEN ? WHEN 'state' THEN ? WHEN 'zip' THEN ? END,
edit = CASE column_name WHEN 'city' THEN ? WHEN 'state' THEN ? WHEN 'zip' THEN ? END,
delete = CASE column_name WHEN 'city' THEN ? WHEN 'state' THEN ? WHEN 'zip' THEN ? END,
view = CASE column_name WHEN 'city' THEN ? WHEN 'state' THEN ? WHEN 'zip' THEN ? END
WHERE id = ? AND table_name = ?;
It would be even more complicated if you wanted to put both table_name and column_name into the CASE expression.
Frankly, it's easier to just write the loop and do this one row at a time.
If you had a unique key for example comprised of the columns (id, table_name, column_name) so SQL could detect when it was inserting a duplicate row, you could do the following:
INSERT INTO user_perms (id, table_name, column_name, add, edit, delete, view)
VALUES (1, 'vendors', 'city', 0, 0, 0, 1),
(1, 'vendors', 'state', 0, 0, 0, 1),
(1, 'vendors', 'zip', 0, 0, 0, 1)
-- any number of additional tuples follow
ON DUPLICATE KEY UPDATE add = VALUES(add), edit = VALUES(edit),
delete = VALUES(delete), view = VALUES(view);
If this worked, you would not need to delete the rows first. INSERT .. ON DUPLICATE KEY UPDATE is intended to insert a new row if the data you insert does not conflict with an existing row, but if the values you insert do conflict with a unique key on the table, it updates only those columns you set, according to the UPDATE clause. Read the documentation for more details.

Related

MySQL Upsert with Insert Only Column Value

In MySQL, is it possible to do an upsert but only set a column value on insert (and not set the column value on update).
For example, for a createdBy column, we only want to set the value on insert, we don't want to override that value on update (because we lose who originally inserted the column).
Note that we only know the currently logged in user. So updatedBy is simple -- always use the value of the logged in user. But createdBy is hard. Use the value of the logged in user but only for an insert -- don't override this on update.
Example schema:
CREATE TABLE `movie` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`name` NVARCHAR(100) NOT NULL,
`createdBy` NVARCHAR(100) NOT NULL,
`updatedBy` NVARCHAR(100) NOT NULL,
UNIQUE INDEX (`name`)
);
Example of a standard upsert:
INSERT INTO `movie` (`name`, `createdBy`, `updatedBy`)
VALUES ('The Matrix', 'Jill', 'Jill')
ON DUPLICATE KEY UPDATE
`id` = LAST_INSERT_ID(`id`),
`name` = VALUES(`name`),
`createdBy` = VALUES(`createdBy`),
`updatedBy` = VALUES(`updatedBy`)
;
Here's my attempt to only set the createdBy column on insert using IFNULL. But this doesn't work and results in createdBy always being null.
INSERT INTO `movie` (`name`, `createdBy`, `updatedBy`)
VALUES ('The Matrix', IFNULL(`createdBy`, 'Jill'), 'Jill')
ON DUPLICATE KEY UPDATE
`id` = LAST_INSERT_ID(`id`),
`name` = VALUES(`name`),
`createdBy` = VALUES(`createdBy`),
`updatedBy` = VALUES(`updatedBy`)
;
Results wanted:
Case 1: Jill runs an upsert that inserts a row.
id = 1
name = 'The Matrix'
createdBy = 'Jill' // Created by Jill
updatedBy = 'Jill' // Last updated by Jill
Case 2: Bob runs an upsert that updates the same row.
id = 1
name = 'The Matrix Reloaded'
createdBy = 'Jill' // Created by Jill (do not change value on update)
updatedBy = 'Bob' // Last updated by Bob
I created a fiddle guessing that Name is the Key, feel free to give it a try here.
This is the basic syntax:
INSERT INTO `movie` (`name`, `UpdatedBy`,`CreatedBy`)
VALUES ('Star wars', 'NameA','NameB')
ON DUPLICATE KEY UPDATE `UpdatedBy` = VALUES(`UpdatedBy`)
;
Notice: NameA and NameB can be the same so you dont get nulls on inserts
Hope it helps :)
Try this:
INSERT INTO `movie` (`name`, `createdBy`)
VALUES ('The Matrix', 'Jill')
ON DUPLICATE KEY UPDATE `name` = VALUES(`name`)
;

How to get auto-increment PK on a multi-row insert in MySql

I need to get back a list of "affected" ids when inserting multiple rows at once. Some rows might already be there, resulting in an update instead.
DDL:
CREATE TABLE `users` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY,
`email` varchar(100) NOT NULL,
`is_active` tinyint(1) NOT NULL DEFAULT '1',
`update_time` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
UNIQUE KEY `email` (`email`)
)
Query:
INSERT INTO users (id, email, is_active)
VALUES (NULL, "joe#mail.org", true),
(NULL, "jack#mail.org", false),
(NULL, "dave#mail.org", true)
ON DUPLICATE KEY UPDATE
is_active = VALUES(is_active)
There is a UNIQUE constraint on email.
From what I gathered, LAST_INSERT_ID() would only give me first generated id of the batch. But I wouldn't know how many inserts/updates really took place.
The only way I could come up with is to follow with a second SELECT statement:
SELECT id
FROM users
WHERE email IN ("joe#mail.org", "jack#mail.org", "dave#mail.org")
Is there a better way?

Update if exists, otherwise insert (without unique keys)

I have a table that is created like this:
'CREATE TABLE `boss_kills` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`characterid` int(11) DEFAULT NULL,
`mobid` int(11) DEFAULT NULL,
`amount` int(11) NOT NULL DEFAULT ''0'',
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=25 DEFAULT CHARSET=latin1'
Goal: I'm trying to create a system where a player kills a boss in the game and it records the boss's ID and the player's ID in the table. I want to be able to write one query where it updates if the player is already logged with the specific boss and inserts if he or she isn't.
Research: I did a lot of research online and people suggest the INSERT INTO ... ON DUPLICATE KEY UPDATE, but that only works if you either know your primary key or have a unique key, none of which I have or know.
Tries: I've tried
IF EXISTS (...) THEN UPDATE (...) ELSE INSERT(...)
and
UPDATE (...) IF ##ROWCOUNT = 0 INSERT INTO (...)
but they don't work. I get syntax errors.
If needed I can provide the errors thrown by the above tries. My current code for trying to update is this (but it throws a SQL syntax error):
Attempt 1:
UPDATE boss_kills
SET amount = amount + 1
WHERE characterid = ? AND mobid = ?
IF ##ROWCOUNT = 0
INSERT INTO boss_kills (characterid, mobid, amount) VALUES (?, ?, 1)
Attempt 2:
IF NOT EXISTS (SELECT id FROM boss_kills WHERE characterid = ? AND mobid = ?)
THEN
INSERT INTO boss_kills VALUES (DEFAULT, ?, ?, 1)
ELSE
UPDATE boss_kills SET amount = amount + 1 WHERE characterid = ? AND mobid = ?
It seems like (characterid, mobid) could make a unique index constraint for your table, thus allowing you to use INSERT ... ON DUPLICATE KEY UPDATE.
Use below script to create your table
CREATE TABLE boss_kills (
id int(11) NOT NULL AUTO_INCREMENT,
characterid int(11) DEFAULT NULL,
mobid int(11) DEFAULT NULL,
amount int(11) NOT NULL DEFAULT 0,
PRIMARY KEY ( id ),
UNIQUE ( characterid, mobid )
) ENGINE=MyISAM AUTO_INCREMENT=25 DEFAULT CHARSET=latin1;
Note that I've removed backticks around your table and column names - they are not necessary.
Inserting a row
INSERT INTO boss_kills (characterid, mobid, amount)
VALUES (?, ?, 1)
ON DUPLICATE KEY UPDATE amount = amount + 1;

MySQL: update a record, on duplicate key leave NEWER one, and delete OLDER one? See inside

There is a table:
CREATE TABLE `mytable` (
`user_id` INT(10) UNSIGNED NOT NULL,
`thing_id` VARCHAR(100) NOT NULL DEFAULT '',
`lock_date` DATETIME NOT NULL,
`lock_id` VARCHAR(36) NOT NULL,
PRIMARY KEY (`user_id`,`thing_id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
and some values there:
INSERT INTO mytable(user_id,thing_id,lock_date,lock_id)
VALUES
(51082,'299ac9ff-2b2b-102d-8ff6-f64c971398c3','2012-03-16 00:39:12','ec7b2008-6ede-11e1-aac2-5924aae99221'),
(108325,'299ac9ff-2b2b-102d-8ff6-f64c971398c3','2013-02-05 19:30:03','7c6de986-6edd-11e1-aac2-5924aae99221'),
(108325,'d90b354d-4b5f-11e0-9959-47117d41cf4b','2012-03-16 00:47:41','1c243032-6ee0-11e1-aac2-5924aae99221');
I want to delegate all records of user_id = 108325 to user_id = 51082, and if both users have an equal thing_id field, leave the newer one only (lock_date1 > lock_date2), so that I have following result:
51082,'299ac9ff-2b2b-102d-8ff6-f64c971398c3','2013-02-05 19:30:03','7c6de986-6edd-11e1-aac2-5924aae99221'
108325,'d90b354d-4b5f-11e0-9959-47117d41cf4b','2012-03-16 00:47:41','1c243032-6ee0-11e1-aac2-5924aae99221'
Note that 51082 now has a newer record: lock_date = '2013-02-05 19:30:03' instead of '2012-03-16 00:39:12'.
So, how can I update a row, and on duplicate key leave the newer one (by some particular field)?
Thanks!
INSERT INTO
mytable(user_id,thing_id,lock_date,lock_id)
VALUES
(51082,'299ac9ff-2b2b-102d-8ff6-f64c971398c3','2012-03-16 00:39:12','ec7b2008-6ede-11e1-aac2-5924aae99221'),
(108325,'299ac9ff-2b2b-102d-8ff6-f64c971398c3','2013-02-05 19:30:03','7c6de986-6edd-11e1-aac2-5924aae99221'),
(108325,'d90b354d-4b5f-11e0-9959-47117d41cf4b','2012-03-16 00:47:41','1c243032-6ee0-11e1-aac2-5924aae99221')
ON DUPLICATE KEY UPDATE SET
user_id = VALUES(user_id),
lock_date = VALUES(lock_date),
lock_id = VALUES(lock_id)

Keep from creating duplicate entry above zero

Would it be possible to create a database-level restriction to prevent creating a row that has a column x INT with a value that already exists and is above 0?
Is there a way to use CONSTRAINT for this purpose?
A possible solution is to do the following:
CREATE TABLE test
(
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
myfield INT,
CONSTRAINT check_myfield UNIQUE ( myfield )
);
Now, the column myfield might be NULL. So when we do the following, there will be a total of 0 errors.
INSERT INTO `test` VALUES ( '', '1' );
INSERT INTO `test` VALUES ( '', '0' );
INSERT INTO `test` VALUES ( '', '5' );
INSERT INTO `test` VALUES ( '', '7' );
etc, you get the point...
Each and every row has a unique value in the column myfield, but there is still the possibility to create rows where the value in this particular column is NULL which is almost exactly what I wanted. I wanted all values above 0 unique, this is all above NULL. The beauty of this solution is that it feels more 'professional', no unnecessary logic.