MySQL Replace is not working - mysql

I try to use mysql replace with following table.
CREATE TABLE price (
id BIGINT UNSIGNED AUTO_INCREMENT,
ski_chalet_id BIGINT UNSIGNED NOT NULL,
month DATE NOT NULL,
d_1 DOUBLE(18, 2),
d_2 DOUBLE(18, 2),
d_3 DOUBLE(18, 2),
d_4 DOUBLE(18, 2),
d_5 DOUBLE(18, 2),
created_at DATETIME NOT NULL,
updated_at DATETIME NOT NULL,
UNIQUE INDEX fk_ski_chalet_price_ski_chalet_idx (ski_chalet_id, month),
INDEX ski_chalet_id_idx (ski_chalet_id),
PRIMARY KEY(id)
) DEFAULT CHARACTER SET utf8 ENGINE = InnoDB;
I want add a row if row is not exist and update if row is exist. So i used mysql REPLACE to do that.
REPLACE INTO `price` SET `ski_chalet_id` = 43 and `month` = '2013-04-01' and `d_1` = 23
This query is successful. but add all value as 00 and 0000-00-00 or null.
But insert query is working.
INSERT INTO `price` (`ski_chalet_id`,`month`,`d_1`) VALUES ('34', '2013-04-01', '46');
I cant find whats the issue. Please help me.

You have to use "," instead on "and" in replace query ........
REPLACE INTO price SET ski_chalet_id = 43 , month = '2013-04-01' , d_1 = 23
NOTE : REPLACE function will work want you want you don't have to look for other functions.

Don't use SET with REPLACE. The syntax for REPLACE is the same as for INSERT:
REPLACE INTO `price` (`ski_chalet_id`,`month`,`d_1`) VALUES ('43', '2013-04-01', '23');

To specify multiple columns with SET in REPLACE (and UPDATE / INSERT), you must separate them with commas. Your query amounts to:
SET `ski_chalet_id` = (some boolean expression)
Rewrite as
SET `ski_chalet_id` = 43, `month` = '2013-04-01', `d_1` = 23

Related

MySql WHERE condition based on unique index

I have the following table groupToScore:
CREATE TABLE `groupToScore` (
`groupId` int NOT NULL,
`scoreId` varchar(255) NOT NULL,
`scoreName` varchar(255) DEFAULT NULL,
UNIQUE KEY `gToS` (`groupId`,`scoreId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
The pair (groupId, scoreId) form a unique key gTos for this table.
My question is how do I perform UPDATE using gTos in the WHERE clause?
It would look something like, UPDATE groupToScore SET scoreName = #{scoreName} WHERE gToS is equal to (groupId, scoreId).
UPDATE groupToScore SET scoreName = #{scoreName} WHERE groupId = 1 and scoreId = 1;
I believe mysql server will choose a proper index for you and you can get the one sql indexing information by put a explain at the top of the sql
explain UPDATE groupToScore SET scoreName = #{scoreName} WHERE groupId = 1 and scoreId = 1;
You can't use indices directly. The SQL engine uses the index automatically if it is applicable. Therefore, you simply query for WHERE groupId = ? AND scoreId = ?.
You cannot use the key name directly but you can use its expression. Try this:
UPDATE groupToScore SET scoreName = #{scoreName} WHERE (`groupId`,`scoreId`) = (groupId, scoreId)
--e.g for a single match
insert groupToScore values(1,3,'dd'),(1,2,'xx');
UPDATE groupToScore SET scoreName = 'aa' WHERE (`groupId`,`scoreId`) = (1, 2);
--e.g for multiple matches
UPDATE groupToScore SET scoreName = 'kk' WHERE (`groupId`,`scoreId`) in (
(1,2),
(1,3)
);
groupId = 1 and scoreId = 1
will fail to fully use your
UNIQUE KEY `gToS` (`groupId`,`scoreId`)
because of a type conflict.
When comparing a varchar column to an integer literal, the column is converted to an integer before performing the test. Do, instead
groupId = 1 and scoreId = "1"
It does not matter if you have groupId = "1"; the string will be converted to a number and the index can still be used.
The update is simply
UPDATE groupToScore SET
scoreName = #{scoreName}
WHERE groupId = 123, scoreId = "987"
And/or, change the datatype of `scoreId to be something numeric. (Of course, this assumes its values are really numbers.

MySQL stored function to read column from row in table

We need a function / stored procedure to return a column in a row in a table, where we pass the table, id field name, id and column to return.
We have a table which is an activity log with columns such as
log_datetime - the date/time the user did something
log_table - the table affected
log_idcol - the column in log_table that holds the id
log_id - the id of the row in log_table
log_titlecol - the column in log_table which holds the 'title' of the
item
So in a report/ MySql statement I need to be able to list the title which is held in the column name held in log_titlecol, read from the table with the name in the log_table column, and id of whatever is in log_idcol.
so like
SELECT log_titlecol FROM log_table where log_idcol = log_id
where all the 'log_; parts are replaceable. I can't see a way to do this in MySQL, so is there a way to do this in a stored procedure/function?
For example:
CREATE TABLE jokes (
JokeID smallint(4) NOT NULL, **(autoincrement, unique)**
Title varchar(50) CHARACTER SET utf8 DEFAULT NULL,
Note text CHARACTER SET utf8
)
CREATE TABLE idioms (
IdiomID smallint(4) NOT NULL, **(autoincrement, unique)**
IdiomTitle varchar(50) CHARACTER SET utf8 DEFAULT NULL,
Note text CHARACTER SET utf8
)
CREATE TABLE `log` (
id smallint(4) NOT NULL, **(autoincrement, unique)**
log_datetime datetime,
log_table varchar(50) CHARACTER SET utf8 DEFAULT NULL,
log_idcol varchar(50) CHARACTER SET utf8 DEFAULT NULL,
log_id smallint(4) NOT NULL,
log_titlecol varchar(50) CHARACTER SET utf8 DEFAULT NULL,
)
INSERT INTO jokes (Title, Notes)
VALUES ('Funny joke','This is note1')
INSERT INTO jokes (Title, Notes)
VALUES ('Another Funny joke','This is another note')
INSERT INTO idioms (IdiomTitle, Notes)
VALUES ('Bird in the hand','What this means..')
INSERT INTO jokes (Title, Notes)
VALUES ('Another Funny joke','This is another note')
INSERT INTO log (log_datetime, log_table, log_idcol, log_id , log_titlecol)
VALUES (now(), 'jokes','JokeID',1,'Title')
INSERT INTO log (log_datetime, log_table, log_idcol, log_id , log_titlecol)
VALUES (now(), 'jokes','JokeID',2,'Title')
INSERT INTO log (log_datetime, log_table, log_idcol, log_id , log_titlecol)
VALUES (now(), 'idioms','IdiomID',2,'IdiomTitle')
So now how can I have a report from log showing the date time and the Title column from the Jokes table, row id as in the log_id column? I need a function.
eg SELECT log_datetime, log_table, log_id, GetTitle(log_table, log_idcol, log_id, log_titlecol) from log
Where GetTitle is a function which will return the column held in 'log_titlecol' form the table passed as log_table, from the row with an id (held in the log_idcol column) of log_id
So for example the output would show:
2018-01-01 12:00 jokes 1 Funny Joke
2018-01-01 12:10 jokes 2 Another Funny joke
2018-01-01 12:11 idioms 1 Bird in the hand
I have tried
CREATE PROCEDURE Getcol(IN tab TEXT CHARSET utf8mb4, IN col TEXT CHARSET utf8mb4, IN idcol TEXT CHARSET utf8mb4, IN id INT(15), OUT outcol TEXT CHARSET utf8mb4)
DETERMINISTIC
COMMENT 'Return a column from any table'
BEGIN
SET #Expression = CONCAT('SELECT ', col,' INTO #outcol FROM ', tab, ' where ', idcol, ' = ', id);
PREPARE myquery FROM #Expression;
EXECUTE myquery;
SELECT #outcol;
END
I can call this like
CALL GetCol('Jokes','Title','JokeID',1)
And this works to return the Title column for ID 1, but I am cant seem to then put this call into a function
CREATE FUNCTION getrowcol(tab TEXT, col TEXT, idcol TEXT, id INT) RETURNS text CHARSET utf8mb4
NO SQL
COMMENT 'Return a column from any table'
BEGIN
DECLARE outvar TEXT;
CALL GetCol(tab, col, idcol, id, #out1);
SELECT #out1 INTO outvar;
RETURN outvar;
END
This returns blank. How can I return #out1?
This is essentially the same question (with no answer)
https://dba.stackexchange.com/questions/151328/dynamic-sql-stored-procedure-called-by-a-function
Try something like,
DELIMITER //
CREATE PROCEDURE demo(tab VARCHAR(50), tit VARCHAR(50))
BEGIN
SET #Expression = CONCAT('SELECT l.log_datetime, j.',tit,' FROM `log` l INNER JOIN ',tab,' j;');
PREPARE myquery FROM #Expression;
EXECUTE myquery;
END
//
DELIMITER ;
Call it like
CALL demo('jokes', 'title')
from Reference

Why does MySQL not always use index merge here?

Consider this table:
CREATE TABLE `Alarms` (
`AlarmId` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`DeviceId` BINARY(16) NOT NULL,
`Code` BIGINT(20) UNSIGNED NOT NULL,
`Ended` TINYINT(1) NOT NULL DEFAULT '0',
`NaturalEnd` TINYINT(1) NOT NULL DEFAULT '0',
`Pinned` TINYINT(1) NOT NULL DEFAULT '0',
`Acknowledged` TINYINT(1) NOT NULL DEFAULT '0',
`StartedAt` TIMESTAMP NOT NULL DEFAULT '0000-00-00 00:00:00',
`EndedAt` TIMESTAMP NULL DEFAULT NULL,
`MarkedForDeletion` TINYINT(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`AlarmId`),
KEY `Key1` (`Ended`,`Acknowledged`),
KEY `Key2` (`Pinned`),
KEY `Key3` (`DeviceId`,`Pinned`),
KEY `Key4` (`DeviceId`,`StartedAt`,`EndedAt`),
KEY `Key5` (`DeviceId`,`Ended`,`EndedAt`),
KEY `Key6` (`MarkedForDeletion`)
) ENGINE=INNODB;
And, for this test, populate it like so:
-- Populate some dummy data; 500 alarms for each
-- of 1000 one-second periods
SET #testDevice = UNHEX('00030000000000000000000000000000');
DROP PROCEDURE IF EXISTS `injectAlarms`;
DELIMITER ;;
CREATE PROCEDURE injectAlarms()
BEGIN
SET #fromdate = '2018-02-18 00:00:00';
SET #numdates = 1000;
SET #todate = DATE_ADD(#fromdate, INTERVAL #numdates SECOND);
-- Create table of alarm codes to join on
DROP TABLE IF EXISTS `__codes`;
CREATE TEMPORARY TABLE `__codes` (
`Code` BIGINT NOT NULL PRIMARY KEY
);
SET #startcode = 0;
SET #endcode = 499;
REPEAT
INSERT INTO `__codes` VALUES(#startcode);
SET #startcode = #startcode + 1;
UNTIL #startcode > #endcode END REPEAT;
-- Add an alarm for each code, for each second in range
REPEAT
INSERT INTO `Alarms`
(`DeviceId`, `Code`, `Ended`, `NaturalEnd`, `Pinned`, `Acknowledged`, `StartedAt`, `EndedAt`)
SELECT
#testDevice,
`Code`,
TRUE, FALSE, FALSE, FALSE,
#fromdate, #fromdate
FROM `__codes`;
SET #fromdate = DATE_ADD(#fromdate, INTERVAL 1 SECOND);
UNTIL #fromdate > #todate END REPEAT;
END;;
DELIMITER ;
CALL injectAlarms();
Now, for some datasets the following query works quite well:
SELECT * FROM `Alarms`
WHERE
((`Alarms`.`Ended` = FALSE AND `Alarms`.`Acknowledged` = FALSE) OR `Alarms`.`Pinned` = TRUE) AND
`MarkedForDeletion` = FALSE AND
`DeviceId` = #testDevice
;
This is because MariaDB is clever enough to use index merges, e.g.:
id select_type table type possible_keys
1 SIMPLE Alarms index_merge Key1,Key2,Key3,Key4,Key5,Key6
key key_len ref rows Extra
Key1,Key2,Key3 2,1,17 (NULL) 2 Using union(Key1,intersect(Key2,Key3)); Using where
However if I use the dataset as populated by the procedure above, and flip the query around a bit (which is another view I need, but in this case will return many more rows):
SELECT * FROM `Alarms`
WHERE
((`Alarms`.`Ended` = TRUE OR `Alarms`.`Acknowledged` = TRUE) AND `Alarms`.`Pinned` = FALSE) AND
`MarkedForDeletion` = FALSE AND
`DeviceId` = #testDevice
;
… it doesn't:
id select_type table type possible_keys
1 SIMPLE Alarms ref Key1,Key2,Key3,Key4,Key5,Key6
key key_len ref rows Extra
Key2 1 const 144706 Using where
I would rather like the index merges to happen more often. As it is, given the ref=const, this query plan doesn't look too scary … however, the query takes almost a second to run. That in itself isn't the end of the world, but the poorly-scaling nature of my design shows when trying a more exotic query, which takes a very long time:
-- Create a temporary table that we'll join against in a mo
DROP TABLE IF EXISTS `_ranges`;
CREATE TEMPORARY TABLE `_ranges` (
`Start` TIMESTAMP NOT NULL DEFAULT 0,
`End` TIMESTAMP NOT NULL DEFAULT 0,
PRIMARY KEY(`Start`, `End`)
);
-- Populate it (in reality this is performed by my application layer)
SET #endtime = 1518992216;
SET #starttime = #endtime - 86400;
SET #inter = 900;
DROP PROCEDURE IF EXISTS `populateRanges`;
DELIMITER ;;
CREATE PROCEDURE populateRanges()
BEGIN
REPEAT
INSERT IGNORE INTO `_ranges` VALUES(FROM_UNIXTIME(#starttime),FROM_UNIXTIME(#starttime + #inter));
SET #starttime = #starttime + #inter;
UNTIL #starttime > #endtime END REPEAT;
END;;
DELIMITER ;
CALL populateRanges();
-- Actual query
SELECT UNIX_TIMESTAMP(`_ranges`.`Start`) AS `Start_TS`,
COUNT(`Alarms`.`AlarmId`) AS `n`
FROM `_ranges`
LEFT JOIN `Alarms`
ON `Alarms`.`StartedAt` < `_ranges`.`End`
AND (`Alarms`.`EndedAt` IS NULL OR `Alarms`.`EndedAt` >= `_ranges`.`Start`)
AND ((`Alarms`.`EndedAt` IS NULL AND `Alarms`.`Acknowledged` = FALSE) OR `Alarms`.`Pinned` = TRUE)
-- Again, the above condition is sometimes replaced by:
-- AND ((`Alarms`.`EndedAt` IS NOT NULL OR `Alarms`.`Acknowledged` = TRUE) AND `Alarms`.`Pinned` = FALSE)
AND `DeviceId` = #testDevice
AND `MarkedForDeletion` = FALSE
GROUP BY `_ranges`.`Start`
(This query is supposed to gather a list of counts per time slice, each count indicating how many alarms' [StartedAt,EndedAt] range intersects that time slice. The result populates a line graph.)
Again, when I designed these tables and there weren't many rows in them, index merges seemed to make everything whiz along. But now not so: with the dataset as given in injectAlarms(), this takes 40 seconds to complete!
I noticed this when adding the MarkedForDeletion column and performing some of my first large-dataset scale tests. This is why my choice of indexes doesn't make a big deal out of the presence of MarkedForDeletion, though the results described above are the same if I remove AND MarkedForDeletion = FALSE from my queries; however, I've kept the condition in, as ultimately I will need it to be there.
I've tried a few USE INDEX/FORCE INDEX combinations, but it never seems to use index merge as a result.
What indexes can I define to make this table behave quickly in the given cases? Or how can I restructure my queries to achieve the same goal?
(Above query plans obtained on MariaDB 5.5.56/CentOS 7, but solution must also work on MySQL 5.1.73/CentOS 6.)
Wow! That's the most complicated "index merge" I have seen.
Usually (perhaps always), you can make a 'composite' index to replace an index-merge-intersect, and perform better. Change key2 from just (pinned) to (pinned, DeviceId). This may get rid of the 'intersect' and speed it up.
In general, the Optimizer uses index merge only in desperation. (I think this is the answer to the title question.) Any slight changes to the query or the values involved, and the Optimizer will perform the query without index merge.
An improvement on the temp table __codes is to build a permanent table with a large range of values, then use a range of values from that table inside your Proc. If you are using MariaDB, then use the dynamically built "sequence" table. For example the 'table' seq_1_to_100 is effectively a table of one column with numbers 1..100. No need to declare it or populate it.
You can get rid of the other REPEAT loop by computing the time from Code.
Avoiding LOOPs will be the biggest performance benefit.
Get all that done, then I may have other tips.

Column cannot be null - procedure

I am trying to create a procedure in MySQL that insert weeks (for current year) to my week table. But there is a problem because after first row is added for the next one I get an error: number column cannot be null. I am new to MySQL so I will appreciate any help.
CREATE PROCEDURE generateWeeks()
BEGIN
SET #currentYear = YEAR(CURDATE());
SET #nextYear = #currentYear + 1;
SET #startOfCurrentWeek = CURDATE();
WHILE(#currentYear < #nextYear) DO
SET #endOfCurrentWeek = DATE_ADD(#startOfCurrentWeek , INTERVAL 7 DAY);
SET #weekNumber = WEEK(#startOfCurrentWeek, 3) -
WEEK(#startOfCurrentWeek - INTERVAL DAY(#startOfCurrentWeek)-1 DAY, 3) + 1;
INSERT INTO `week` (`number`, `start_date`, `end_date`)
VALUES (#weekNumber, #startOfCurrentWeek, #endOfCurrentWeek);
SET #startOfCurrentWeek = #endOfCurrentWeek + 1;
SET #currentYear = YEAR(#endOfCurrentWeek);
END WHILE;
END //
DELIMITER ;
EDITED:
Table Creation:
CREATE TABLE `week` (
`id` INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
`number` INT(11) NOT NULL,
`start_date` DATE NOT NULL,
`end_date` DATE NOT NULL
)
Why for first while iteration everything is ok (rows is added), but in the next one I get null value in #weekNumber variable ?
The line:
SET #startOfCurrentWeek = #endOfCurrentWeek + 1;
will convert the variable into a integer. Use date_add instead.
Also, instead of using user-defined variables (#endOfCurrentWeek) you better use local variabled (declare v_endOfCurrentWeek date).

Update query to reset table field per month basis

I have a table named service_tracker. The table contains 6 fields but the most relevant to my question below is cycle_start and last_used. Currently I am able to determine the following things about a service: the times it has been used (usage_count) by user within 1 month (last_used,cycle_start). First query adds 1 to the usage_count as long as it is not older then 1 month. Second query clears and sets the usage_count = 1 if older than 1 month. Queries work great, however, would it possible to perform this on the basis of the twelve months of the year? Clear and set to 1 the usage_count once April, May, June etc. is over?
Queries:
UPDATE service_tracker
SET usage_count = usage_count + 1,
last_used = CURDATE()
WHERE service_tracker = 'cloudstorage' AND `user` = 'test2' AND last_used >= date_sub(CURDATE(), INTERVAL 1 month);
UPDATE service_tracker
SET usage_count = 1,
last_used = '0000-00-00',
cycle_start = CURDATE()
WHERE service_tracker = 'cloudstorage' AND `user` = 'test2' AND cycle_start < date_sub(CURDATE(), interval 1 month);
Table Schema
CREATE TABLE IF NOT EXISTS `service_tracker` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`service` varchar(50) NOT NULL,
`cycle_start` date NOT NULL,
`user` varchar(200) NOT NULL,
`usage_count` int(6) NOT NULL,
`last_used` date NOT NULL,
PRIMARY KEY (`id`)
);
INSERT INTO `service_tracker` (`id`, `service`, `cycle_start`, `user`, `usage_count`, `last_used`) VALUES
(1, 'webserver', '2015-04-24', 'test1', 13, '2015-04-24'),
(2, 'cloudstorage', '2015-04-16', 'test2', 390, '2015-04-30'),
(3, 'web-traffic-tracker', '2015-04-16', 'test3', 1916, '2015-04-30'),
(4, 'remote-it-help', '2015-04-16', 'test4', 91, '2015-04-16');
You can use the mysql scheduler to run queries at specific (and repeating) times.
DELIMITER $$
CREATE EVENT reset_usage_count
ON SCHEDULE EVERY '1' MONTH
STARTS '2015-05-01 00:00:00'
DO
BEGIN
-- your query to reset the usage_count
END$$
DELIMITER ;
This one would start the query at midnight, on the first of every month. You will need to make sure the global variable event_scheduler is set to on. You can check that it is running by executing show proceslist, and looking for User: event_scheduler