How to get auto-increment PK on a multi-row insert in MySql - 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?

Related

MySQL update unique value returns Error Code: 1062. Duplicate entry

I have a problem with updating the unique column value in the table.
I have a customer table and I have a separate customer_address table where the customer can have one primary address and others. The primary address is the main address to which the goods are delivered. Customers can have many addresses but one must be primary.
When I try to update I get the message: Error Code: 1062. Duplicate entry '0-3' for key 'primary_UNIQUE'
CREATE TABLE `customer_address` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(45) DEFAULT NULL,
`address` varchar(525) NOT NULL,
`customer_id` int(11) NOT NULL,
`primary` tinyint(4) DEFAULT '0',
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `primary_UNIQUE` (`primary`,`customer_id`),
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8
My plan was that all non-primary addresses have a value of 0, and only the primary address has a value of 1.
id | name | address | customer_id | primary
------------------------------------------------------------------------------
'2', 'Store 1', 'Address name 24', '3', '0'
'3', 'Wholesale', 'Address name 24', '3', '1'
'4', 'Store 2', 'Address name 24', '1', '0'
'5', 'Wholesale 3', 'Address name 24', '1', '1'
How I try to change primary address:
First, I need to update id = 3 and set primary to 0. This will cause Duplicate entry
UPDATE customer_address SET primary = 1 WHERE id=2;
UPDATE customer_address SET primary = 0 WHERE id=3;
Is there another way to solve this?
First of all I think your table structure is contradicting the design you posted,
I have a customer table and I have a separate customer_address table
where the customer can have one primary address and others. The
primary address is the main address to which the goods are delivered.
Customers can have many addresses but one must be
With the above table design you can have only two entries per customer_id so you will never be able to store more then 2 addresses per customer, and one will be primary and the other being the non-primary.
For this you might need to redesign your table first to fulfill the requirements you have.
So, a customer's primary address is to become the secondary address and secondary address is to become primary address. The problem is that if you try to modify either of them, then the operation will fail because of the other.
However, we know that NULL = NULL is a false criteria, so, you need to set one of them to NULL, update the other and then update the nulled one:
UPDATE customer_address SET primary = NULL WHERE id=3;
UPDATE customer_address SET primary = 1 WHERE id=2;
UPDATE customer_address SET primary = 0 WHERE id=3;

multicolumn unique key mysql insert

I have a mysql database, and a table structure like this:
CREATE TABLE `user_session_log` (
`stat_id` int(8) NOT NULL AUTO_INCREMENT,
`metric` tinyint(1) NOT NULL DEFAULT '0',
`platform` tinyint(1) NOT NULL DEFAULT '0',
`page_id` varchar(128) DEFAULT '_empty_',
`target_date` date DEFAULT NULL,
`country` varchar(2) DEFAULT NULL COMMENT 'ISO 3166 country code (2 symbols)',
`amount` int(100) NOT NULL DEFAULT '0.000000' COMMENT 'counter or amount',
`unique_key` varchar(180) DEFAULT NULL COMMENT 'Optional unique identifier',
`created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`modified` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`stat_id`),
UNIQUE KEY `unique_key` (`unique_key`) USING BTREE,
KEY `target_date` (`target_date`)
) ENGINE=InnoDB AUTO_INCREMENT=21657473 DEFAULT CHARSET=utf8
What I'm trying to achieve is to log the active sessions / unique users based on date, and page_id, and country. Currently I'm able to achieve this by generating multiple insert statements with unique_key, buy adding a page_id and date in the unique key but I want something a little bit different.
The logic should be: insert new row of unique_key (semi-unique user id), where country = this, date = this, page_id = this. If there is already a row with such information (same page_id, unique_key, and date + country) - update the amount = (amount) + 1; (session).
So I could do lookups like :
SELECT sum(amount) WHERE page_id = "something" AND target_date = "2018-12-21"
This would give me a number of sessions. OR:
SELECT COUNT(*) WHERE page_id = "something" AND target_date = "2018-12-21"
This would give me a number of active users on that pagee_id on that day
OR:
SELECT COUNT(*) WHERE target_date = "2018-12-21"
Which would give me a result of total users on that day.
I know about unique index, but would it give me a result I'm looking for?
Edit, a sample insert:
INSERT INTO `user_session_log` (`platform`,`page_id`,`target_date`,`country`,`amount`,`unique_key`,`created`,`modified`) VALUES ('1','page_id_54','2018-10-08','US',1,'ea3d0ce0406a838d9fd31df2e2ec8085',NOW(),NOW()) ON DUPLICATE KEY UPDATE `amount` = (amount) +1, `modified` = NOW();
and the table should know if theres a duplicate based on if theres a same unique_key + date + country + platform + page_id, otherwise just insert a new row.
Right now I'm doing this differently by having different metrics and a unique_key generated already containing the date + page_id and then hashed. that way it's unique by means i can filter the different unique users on a day basis, but I can't filter the amount of sessions that unique user has had, or how long he uses the software and similar.
Firstly create unique index on all the columns that needs to be unique as follows:
ALTER TABLE user_session_log ADD UNIQUE INDEX idx_multi_column ON user_session_log (unique_key, date, country, platform, page_id);
then you can use INSERT ... ON DUPLICATE KEY UPDATE query to insert/update.

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)

MYSQL - One Column Referenced to Multiple Table

Can a single column in a table can be referenced to multiple tables?
A very late answer, but for whoever is wondering & googeling.
YES this can be done, but it is NOT good practice and even though it is quite simple, it will probably blow up in your face if you're not very aware of what you are doing. Not recommended.
However, I can see uses. For instance, you have a large table of millions of records, and you want in exceptional cases link to unknown or multiple tables (in which case it better be many). With multiple tables, if you would make a foreign key for all of them, that would be a huge bloat in your database size. An unknown table would be possible for instance in a technical support system, where you want to link to record in a table where there might be a problem, and this could be (almost) all tables in the database, including future ones.
Of course you will need two fields to link with: a foreign key field and the name of the table it is linking to. Lets call them foreignId and linkedTable
linkedTable could be an enum or a string, preferrably enum (less space), but that's only possible if the different tables you want to link to, are fixed.
Let's give an extremely silly example. You have an enormous user table users of which some user can add exactly one personal set of data to their profile. This can be about a hobby, a pet, a sport they practice or their profession. Now this info is different in all four cases. (4 possible tables is in reality not enough to justify this structure)
Now let's say linkedTable is an enum with possible values pets, hobbies, sports and professions, which are the names of four differently structured tables. Let's say id is the pkey in all four of them.
You join for instance as follows:
SELECT * FROM users
LEFT JOIN pets ON linkedTable = 'pets' AND foreignId = pets.id
LEFT JOIN hobbies ON linkedTable = 'hobbies' AND foreignId = hobbies.id
LEFT JOIN sports ON linkedTable = 'sports' AND foreignId = sports.id
LEFT JOIN professions ON linkedTable = 'professions' AND foreignId = professions.id
This is just to give a basic jest. Since you probably only need the link in rare cases, you will more likely do the lookup in your programming language, like PHP, when you loop through the users (without join).
Want to try out? You can try it yourself with building this test database (make sure you use a test database):
CREATE TABLE IF NOT EXISTS `users` (
`id` INT NOT NULL AUTO_INCREMENT ,
`name` VARCHAR(100) NOT NULL ,
`linkedTable` ENUM('pets','hobbies','sports','professions') NULL DEFAULT NULL ,
`foreignId` INT NULL DEFAULT NULL ,
PRIMARY KEY (`id`), INDEX (`linkedTable`)
) ;
CREATE TABLE IF NOT EXISTS `pets` (
`id` INT NOT NULL AUTO_INCREMENT ,
`animalTypeId` INT NOT NULL ,
`name` VARCHAR(100) NOT NULL ,
`colorId` INT NOT NULL ,
PRIMARY KEY (`id`), INDEX (`animalTypeId`), INDEX (`colorId`)
) ;
CREATE TABLE IF NOT EXISTS `hobbies` (
`id` INT NOT NULL AUTO_INCREMENT ,
`hobbyTypeId` INT NOT NULL ,
`hoursPerWeekSpend` INT NOT NULL ,
`websiteUrl` VARCHAR(300) NULL ,
PRIMARY KEY (`id`), INDEX (`hobbyTypeId`)
) ;
CREATE TABLE IF NOT EXISTS `sports` (
`id` INT NOT NULL AUTO_INCREMENT ,
`sportTypeId` INT NOT NULL ,
`hoursPerWeekSpend` INT NOT NULL ,
`nameClub` VARCHAR(100) NULL ,
`professional` TINYINT NOT NULL DEFAULT 0,
PRIMARY KEY (`id`), INDEX (`sportTypeId`)
) ;
CREATE TABLE IF NOT EXISTS `professions` (
`id` INT NOT NULL AUTO_INCREMENT ,
`professionId` INT NOT NULL ,
`hoursPerWeek` INT NOT NULL ,
`nameCompany` VARCHAR(100) NULL ,
`jobDescription` VARCHAR(400) NULL,
PRIMARY KEY (`id`), INDEX (`professionId`)
) ;
INSERT INTO `users` (`id`, `name`, `linkedTable`, `foreignId`)
VALUES
(NULL, 'Hank', 'pets', '1'),
(NULL, 'Peter', 'hobbies', '2'),
(NULL, 'Muhammed', 'professions', '1'),
(NULL, 'Clarice', NULL, NULL),
(NULL, 'Miryam', 'professions', '2'),
(NULL, 'Ming-Lee', 'hobbies', '1'),
(NULL, 'Drakan', NULL, NULL),
(NULL, 'Gertrude', 'sports', '2'),
(NULL, 'Mbase', NULL, NULL);
INSERT INTO `pets` (`id`, `animalTypeId`, `name`, `colorId`)
VALUES (NULL, '1', 'Mimi', '3'), (NULL, '2', 'Tiger', '8');
INSERT INTO `hobbies` (`id`, `hobbyTypeId`, `hoursPerWeekSpend`, `websiteUrl`)
VALUES (NULL, '123', '21', NULL), (NULL, '2', '1', 'http://www.freesoup.org');
INSERT INTO `sports` (`id`, `sportTypeId`, `hoursPerWeekSpend`, `nameClub`, `professional`)
VALUES (NULL, '2', '3', 'Racket to Racket', '0'), (NULL, '12', '34', NULL, '1');
INSERT INTO `professions` (`id`, `professionId`, `hoursPerWeek`, `nameCompany`, `jobDescription`)
VALUES (NULL, '275', '40', 'Ben & Jerry\'s', 'Ice cream designer'), (NULL, '21', '24', 'City of Dublin', 'Garbage collector');
Then run the first query.
Fun note for discussion: How would you index this?
If you mean "can a column in one table be used as a foreign key in multiple tables", then the answer is YES. This is the whole point of a relational database
Yes, you can do that so. here is a sample on how to do it:
Here is the table that has a column(CountryID) that will be referenced by multiple tables:
CREATE TABLE DLAccountingSystem.tblCountry
(
CountryID INT AUTO_INCREMENT NOT NULL PRIMARY KEY,
CountryName VARCHAR(128) NOT NULL,
LastEditUser VARCHAR(128) NOT NULL,
LastEditDate DATETIME NOT NULL
) ENGINE=INNODB;
Here are the tables that is going to reference the column(CountryID):
CREATE TABLE DLAccountingSystem.tblCity
(
CityID INT AUTO_INCREMENT NOT NULL PRIMARY KEY,
CountryID INT NOT NULL,
CityName VARCHAR(128) NOT NULL,
LastEditUser VARCHAR(128) NOT NULL,
LastEditDate DATETIME NOT NULL
) ENGINE=INNODB;
CREATE TABLE DLAccountingSystem.tblProvince
(
ProvinceID INT AUTO_INCREMENT NOT NULL PRIMARY KEY,
CountryID INT NOT NULL,
ProvinceName VARCHAR(128) NOT NULL,
LastEditUser VARCHAR(128) NOT NULL,
LastEditDate DATETIME NOT NULL
) ENGINE=INNODB;
Here is how you create a reference to the column:
ALTER TABLE DLAccountingSystem.tblCity
ADD CONSTRAINT fk_tblcitycountryid FOREIGN KEY CountryID (CountryID)
REFERENCES DLAccountingSystem.tblCountry (CountryID)
ON DELETE NO ACTION
ON UPDATE NO ACTION
ALTER TABLE DLAccountingSystem.tblProvince
ADD CONSTRAINT fk_tblprovincecountryid FOREIGN KEY CountryID (CountryID)
REFERENCES DLAccountingSystem.tblCountry (CountryID)
ON DELETE NO ACTION
ON UPDATE NO ACTION
here is a table that has column that references different columns from (CountryID, ProvinceID, CityID) multiple tables(I Don't personally advice this way of table structuring. Just my opinion no offense ;) )
CREATE TABLE DLAccountingSystem.tblPersons
(
PersonID INT AUTO_INCREMENT NOT NULL PRIMARY KEY,
PlaceID INT NOT NULL,
PlaceTypeID INT NOT NULL, -- this property refers to what table are you referencing.
//Other properties here.....
) ENGINE=INNODB;
you should also have a lookup table that would contain the PlaceType:
CREATE TABLE DLAccountingSystem.tblPlaceType
(
PlaceTypeID INT AUTO_INCREMENT NOT NULL PRIMARY KEY,
PlaceTypeName INT NOT NULL
//Other properties here.....
) ENGINE=INNODB;
here is how you fetch it:
SELECT p1.PersonID,
tcity.CityName,
tprov.ProvinceName,
tcoun.CountryName
FROM DLAccountingSystem.tblPersons p1
LEFT JOIN (SELECT p2.PersonID, p2.PlaceTypeID, c.CityName FROM DLAccountingSystem.tblPersons p2 INNER JOIN DLAccountingSystem.tblCity c ON p2.ObjectID = c.CityID WHERE PlaceTypeID = #CityTypeID) tcity ON p1.PersonID = tcity.PersonID
LEFT JOIN (SELECT p2.PersonID, p2.PlaceTypeID, c.ProvinceName FROM DLAccountingSystem.tblPersons p2 INNER JOIN DLAccountingSystem.tblProvince c ON p2.ObjectID = c.ProvinceID WHERE PlaceTypeID = #ProvinceTypeID) tprov ON p1.PersonID = tprov.PersonID
LEFT JOIN (SELECT p2.PersonID, p2.PlaceTypeID, c.CountryName FROM DLAccountingSystem.tblPersons p2 INNER JOIN DLAccountingSystem.tblCountry c ON p2.ObjectID = c.CountryID WHERE PlaceTypeID = #CountryTypeID) tcoun ON p1.PersonID = tcoun.PersonID
you can select from other tables like
A same column or set of columns can act as a parent and/or as a child endpoint of a foreign key or foreign keys.

Social Networking Friends table - Explain Plan - Error Failed to enable constraints error

I have a below table structure for friends table
TABLE STRUCTURE
CREATE TABLE `my_friends` (
`first_user_id` int(10) unsigned NOT NULL,
`second_user_id` int(10) unsigned NOT NULL,
`approved` tinyint(1) NOT NULL DEFAULT '0',
`request_sent` int(10) unsigned DEFAULT NULL,
`request_approved` int(10) unsigned DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
and
DATA
insert into `my_friends` values (4,1,1,null,null);
insert into `my_friends` values (7,8,1,null,null);
insert into `my_friends` values (1,7,1,null,null);
insert into `my_friends` values (2,7,1,null,null);
insert into `my_friends` values (4,2,1,null,null);
To get all the friends for user id "1" , I am running the below query
QUERY TO GET FRIENDS
SELECT first_user_id user_id, second_user_id friend_id from my_friends where first_user_id=1 and approved=1
union
SELECT second_user_id user_id ,first_user_id friend_id from my_friends where second_user_id=1 and approved=1
and when I check explain plan for this , this throws
EXPLAIN PLAN ERROR
Error
Failed to enable constraints. One or more rows contain values violating non-null, unique, or foreign-key constraints.
I am using this query in most of the queries as a subquery and this one causing the issue to check explain plan for all other queries
This is an issue with Mysql and raised with mysql