Increment MySQL uniq row by only one request - mysql

I need to increment value unique row, use only one request
Not use select and after condition INSERT OR UPDATE
Table columns (date, servie) are used as unique values
Only one INSERT
For example
CREATE TABLE `log` (
`date` date NOT NULL,
`service` varchar(255) NOT NULL,
`count` int(11) NOT NULL,
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
I found a single solution, but it not work for my
INSERT INTO log (`date`,`service`,`count`) VALUES ('2020-12-1','amazon',count+1)
ON DUPLICATE KEY UPDATE `date`='2020-12-01',`service`='amazon',`count`=`count`+1;
Are there any other solutions?

First, define id_key as a unique key in the table (or as the primary key):
create table log (
id_key int primary key
dt date not null,
service varchar(255) not null,
cnt int not null,
) engine=innodb default charset=utf8;
Then you can do:
insert into t1 (id_key, dt, service, cnt)
values (1, '2020-12-1', 'amazon', 1)
on duplicate key update
dt = values(dt),
service = values(service),
cnt = cnt + 1;
This attempts to insert a new row in the table, with a cnt of of 1. If id_key exists already, then it is updated instead: the date and service are updated, and the count is incremented.
If you want to use the date and service as unique keys, then do reflect that in the create table statement:
create table log (
id_key int primary key
dt date not null,
service varchar(255) not null,
cnt int not null,
unique (dt, service)
) engine = innodb default charset = utf8;
And then, it does not really make sense to update these columns on duplicate keys, so:
insert into t1 (id_key, dt, service, cnt)
values (1, '2020-12-1', 'amazon', 1)
on duplicate key update cnt = cnt + 1;

Related

How to copy a very large table into another table in MYSQL?

I have a large table with 110M rows. I would like to copy some of the fields into a new table and here is a rough idea of how I am trying to do:
DECLARE l_seenChangesTo DATETIME DEFAULT '1970-01-01 01:01:01';
DECLARE l_migrationStartTime DATETIME;
SELECT NOW() into l_migrationStartTime;
-- See if we've run this migration before and if so, pick up from where we left off...
IF EXISTS(SELECT seenChangesTo FROM migration_status WHERE client_user = CONCAT('this-migration-script-', user())) THEN
SELECT seenChangesTo FROM migration_status WHERE client_user = CONCAT('this-migration-script-', user()) INTO l_seenChangesTo;
SELECT NOW() as LogTime, CONCAT('Picking up from where we left off: ', l_seenChangesTo) as MigrationStatus;
END IF;
INSERT IGNORE INTO newTable
(field1, field2, lastModified)
SELECT o.column1 AS field1,
o.column2 AS field2,
o.lastModified
FROM oldTable o
WHERE
o.lastModified >= l_seenChangesTo AND
o.lastModified <= l_migrationStartTime;
INSERT INTO migration_status (client_user,seenChangesTo)
VALUES (CONCAT('this-migration-script-', user()), l_migrationStartTime)
ON DUPLICATE KEY UPDATE seenChangesTo=l_migrationStartTime;
Context:
CREATE TABLE IF NOT EXISTS `newTable` (
`field1` varchar(255) NOT NULL,
`field2` tinyint unsigned NOT NULL,
`lastModified` datetime NOT NULL,
PRIMARY KEY (`field1`, `field2`),
KEY `ix_field1` (`field1`),
KEY `ix_lastModified` (`lastModified`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `oldTable` (
`column1` varchar(255) NOT NULL,
`column2` tinyint unsigned NOT NULL,
`lastModified` datetime NOT NULL,
PRIMARY KEY (`column1`, `column2`),
KEY `ix_column1` (`column1`),
KEY `ix_lastModified` (`lastModified`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `migration_status` (
`client_user` char(64) NOT NULL,
`seenChangesTo` char(128) NOT NULL,
PRIMARY KEY (`client_user`)
);
Note: I have a few more columns in oldTable. Both oldTable and newTable are in same DB schema using mysql.
What's the general strategy when copying a very table? Should I perform this migration in an iterative manner by copy say 50,000 rows at time.
The insert speed doing a migration like this iteratively is going to be dreadfully slow. Why not SELECT oldTable INTO OUTFILE, then LOAD DATA INFILE ?

Update row in first table and insert a row into a second table in a single query

I'm trying to figure it out how to insert a row into a table on updating a particular field in the second table.
Let's say I have table 1 (dif):
CREATE TABLE dif
(
Position INT(10) UNSIGNED PRIMARY KEY NOT NULL AUTO_INCREMENT,
pKey SMALLINT(3) UNSIGNED NOT NULL,
Number SMALLINT(3) UNSIGNED DEFAULT 0 NOT NULL
);
ALTER TABLE dif
ADD CONSTRAINT dif_article_pKey_fk
FOREIGN KEY (pKey) REFERENCES article (pKey) ON UPDATE CASCADE;
and table 2 (article):
CREATE TABLE IF NOT EXISTS article (
pKey smallint(3) unsigned NOT NULL AUTO_INCREMENT,
Name varchar(80) COLLATE utf8_roman_ci NOT NULL,
Number SMALLINT NOT NULL DEFAULT 0
PRIMARY KEY (pKey)
);
The table article is populated with some data and should be only updated. Table "dif" is empty at the beginning. So, let's say I'm updating the fields on "article" like this:
UPDATE article SET pKey = 15, Name = SomeName, Number = 22 WHERE pKey=15;
Can I somehow combine the UPDATE query with this?
INSERT INTO dif (pKey, Number) VALUES (15, 12);
The "12" is the difference between the "article.Number" before and after UPDATE.
No, but you can make a stored procedure that does both of those things and then execute it in a single statement.
create procedure GiveThisABetterName
(
in pKey int,
in newNumber int,
in currentNumber int,
in newName varchar(100)
)
begin
update
article
set
Name = newName, Number = newNumber
where
pKey = pKey;
insert into dif (pKey, Number) values (pKey, newNumber);
end
My mysql syntax is rusty, but that should be close. Then when you want to execute it:
call GiveThisABetterName(12, 15, 22, 'Some Name');
EDIT: After reading your question again, it seems to me that you're trying to make your data model track audit information that it's just not set up to accommodate naturally. Do you have control over the model? If so, consider something like this (see here for a working example of what's below):
CREATE TABLE IF NOT EXISTS article (
pKey smallint(3) unsigned NOT NULL AUTO_INCREMENT,
Name varchar(80) COLLATE utf8_roman_ci NOT NULL,
PRIMARY KEY (pKey)
);
CREATE TABLE ArticleNumbers
(
Counter int UNSIGNED PRIMARY KEY AUTO_INCREMENT,
pKey SMALLINT(3) UNSIGNED NOT NULL,
Number SMALLINT(3) DEFAULT 0 NOT NULL,
Difference SMALLINT(3)
);
ALTER TABLE ArticleNumbers
ADD CONSTRAINT ArticleNumbers_article_pKey_fk
FOREIGN KEY (pKey) REFERENCES article (pKey) ON UPDATE CASCADE;
Maybe add a few views to make things easier:
CREATE VIEW GroupedArticleNumbers
as
select pKey, max(Counter) as Counter
from ArticleNumbers
group by pKey;
CREATE VIEW CurrentArticles
as
select article.pKey, article.Name, numbers.Number, numbers.Difference
from article
left outer join GroupedArticleNumbers filter on article.pKey = filter.pKey
left outer join ArticleNumbers numbers on filter.Counter = numbers.Counter;
Since you can track the number separately from the base record now but still easily determine what the current number is, you can now combine your update and insert statement functionality. See below.
First, some test data:
insert into article (Name) values ('Test');
insert into ArticleNumbers (pKey, Number, Difference) values (1, 10, null);
insert into ArticleNumbers (pKey, Number, Difference) select 1, 20, 20 - Number from CurrentArticles where pKey = 1;
insert into ArticleNumbers (pKey, Number, Difference) select 1, 50, 50 - Number from CurrentArticles where pKey = 1;
insert into ArticleNumbers (pKey, Number, Difference) select 1, 15, 15 - Number from CurrentArticles where pKey = 1;
See how nicely that works out once the overhead of setting up the schema has been done?
To get the current number for the article we created:
select * from currentarticles where pKey = 1
To get the number history for that article:
select * from article
left outer join articlenumbers on article.pkey = articlenumbers.pkey
order by counter asc
If you're willing to mess with your data model, you can have an alternative to stored procedures.
Alternatively, if you want to use triggers as #Jonathan Leffler suggested, something like this should work:
CREATE TABLE article (
pKey smallint(3) unsigned NOT NULL AUTO_INCREMENT,
Name varchar(80) COLLATE utf8_roman_ci NOT NULL,
Number SMALLINT(3) DEFAULT 0 NOT NULL,
PRIMARY KEY (pKey)
);
CREATE TABLE ArticleNumbers
(
Counter int UNSIGNED PRIMARY KEY AUTO_INCREMENT,
pKey SMALLINT(3) UNSIGNED NOT NULL,
Number SMALLINT(3) DEFAULT 0 NOT NULL,
Difference SMALLINT(3)
);
delimiter $
create trigger tr_u_article
before update on article
for each row
begin
insert into ArticleNumbers (pKey, Number, Difference) select old.pKey, new.Number, new.Number - old.Number
end;
delimiter ;

How to use INSERT ... SELECT with a particular column auto-incrementing, starting at 1?

I am using INSERT ... SELECT to insert a data from specific columns from specific rows from a view into a table. Here's the target table:
CREATE TABLE IF NOT EXISTS `queue` (
`ID` int(11) NOT NULL AUTO_INCREMENT,
`customerId` int(11) NOT NULL,
`productId` int(11) NOT NULL,
`priority` int(11) NOT NULL,
PRIMARY KEY (`ID`),
KEY `customerId` (`customerId`),
KEY `productId` (`productId`),
KEY `priority` (`priority`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ;
The INSERT ... SELECT SQL I have works, but I would like to improve it if possible, as follows: I would like the inserted rows to start with 1 in the priority column, and each subsequent row to increment the priority value by 1. So, if three rows were inserted, the first would be priority 1, the second 2, and the third 3.
A exception to the "start at 1" rule: if there are existing rows in the target table for the specified customer, I would like the inserted rows to start with MAX(priority)+1 for that customer.
I thought I could use a subquery, but here's the problem: sometimes the subquery returns NULL (when there are no records in the queue table for the specified customer), which breaks the insert, as the priority column does not allow nulls.
I tried to CAST the column to an integer, but that still gave me NULL back when there are no records with that customer ID in the table.
I've hardcoded the customer ID in this example, but naturally in my application that would be an input parameter.
INSERT INTO `queue`
(
`customerId`,
`productId`,
`priority`,
`status`,
`orderId`)
SELECT
123, -- This is the customer ID
`PRODUCT_NO`,
(SELECT (MAX(`priority`)+1) FROM `queue` WHERE `customerId` = 123),
'queued',
null
FROM
`queue_eligible_products_view`
Is there a way to do this in one SQL statement, or a small number of SQL statements, i.e., less than SQL statement per row?
I do not think I can set the priority column to auto_increment, as this column is not necessarily unique, and the auto_increment attribute is used to generate a unique identity for new rows.
As Barmar mentions in the comments : use IFNULL to handle your sub query returning null. Hence:
INSERT INTO `queue`
(
`customerId`,
`productId`,
`priority`,
`status`,
`orderId`)
SELECT
123, -- This is the customer ID
`PRODUCT_NO`,
IFNULL((SELECT (MAX(`priority`)+1) FROM `queue` WHERE `customerId` = 123),1),
'queued',
null
FROM
`queue_eligible_products_view`
Here's how to do the incrementing:
INSERT INTO queue (customerId, productId, priority, status, orderId)
SELECT 123, product_no, #priority := #priority + 1, 'queued', null
FROM queue_eligible_products_view
JOIN (SELECT #priority := IFNULL(MAX(priority), 0)
FROM queue
WHERE customerId = 123) var

How to Add integer column to an String column in MySQl 5.0

I Want to add an Integer Column to a String that's because i need to generate a varchar variable with a numeric part that automatically increments. For example, P000001,P000002...
In order to do that what i am doing while creation of table i have taken an int field ID which auto_increments and i am Concatenating P with 00000 and the ID value
The Table i have created is :
CREATE TABLE tblAcceptTest(
ID int AUTO_INCREMENT NOT NULL primary key,
PatientID as CONCAT('P' , CONCAT('000000',CAST(ID as char)))
);
It Shows me the error from as keyword.
Please help
MySQL's documentation (http://dev.mysql.com/doc/refman/5.1/en/create-table.html) says, "the default value must be a constant; it cannot be a function or an expression." Why don't you just get the PatientID value afterward as part of the SELECT:
SELECT CONCAT('P', LPAD(ID, 6, 0)) AS PatientID FROM tblAcceptTest;
It looks like you want six digits after the "P", so try this for your expression:
CONCAT('P', LPAD(ID, 6, '0'))
Mysql has little support for computed columns.
Patient ID from your specification could be a char(7)
CREATE TABLE tblAcceptTest(
ID int AUTO_INCREMENT NOT NULL primary key,
PatientID char(7)
);
Then create some triggers. Note that the following insert trigger will cause issues with high concurrency servers.
DELIMITER |
CREATE TRIGGER tblAcceptTest_insert BEFORE INSERT ON tblAcceptTest
FOR EACH ROW BEGIN
DECLARE next_id INT;
SET next_id = (SELECT AUTO_INCREMENT FROM information_schema.TABLES WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='tblAcceptTest');
SET NEW.PatientID = CONCAT('P' , RIGHT(CONCAT('000000',next_id),6)) ;
END;
|
CREATE TRIGGER tblAcceptTest_update BEFORE UPDATE ON tblAcceptTest
FOR EACH ROW BEGIN
SET NEW.PatientID = CONCAT('P' , RIGHT(CONCAT('000000',NEW.ID),6)) ;
END;
|
DELIMITER ;
You use relationships and views to achieve the same result.
CREATE TABLE `patient` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`patient` varchar(60) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `accepted_test` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`patient_id` int(11) NOT NULL,
`accepted` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `patient_id` (`patient_id`),
CONSTRAINT `accepted_test_ibfk_1` FOREIGN KEY (`patient_id`) REFERENCES `patient` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
create or replace view accepted_test_veiw as
select CONCAT('P' , RIGHT(CONCAT('000000',patient_id),6)) patient_key
, accepted
, id accepted_test_id
, patient_id
from accepted_test ;
select * from `accepted_test_veiw`

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)