I'm reasonably new to writing MySQL stored procedures, and I'm trying to get my head around using loops and variables.
I have a table with a column called STAT_NAME, where several rows could have the same value for that column. I want to create a temporary column which numbers the occurrences of each STAT_NAME value, so for example first time STAT_NAME is "stat A", set STAT_COUNT to 1, 2nd time 2 etc. And then start again at 1 for "stat B" and so on.
I've got as far as creating the the temporary table with an empty column called STAT_COUNT, and sorted the table by STAT_NAME.
I've then tried to loop through all the rows and set STAT_COUNT accordingly. However, I get the following error:
Error Code: 1064. You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '#counting,1) THEN SET #STAT_NAME_CURR = (SELECT #STAT_NAME FROM tmp1 LIMIT #'
Is anyone able to explain why what I've written is not OK? I think it would be fine if I used a system variable instead of #counting, and I don't understand why. I've tried googling the problem but not getting anywhere!
cheers.
DROP PROCEDURE IF EXISTS collate_stats;
delimiter //
CREATE PROCEDURE collate_stats()
begin
create temporary table tmp1 engine=memory
SELECT STAT_NAME, STAT, '' AS STAT_COUNT
FROM performance_testing_stats.20131014
ORDER BY STAT_NAME;
-- stat name that we are currently looking at
SET #STAT_NAME_CURR := "";
-- number of times this name has been found so far
SET #STAT_NAME_COUNT := 0;
-- Use to loop through all rows
SET #n := (SELECT COUNT(*) FROM tmp1);
-- Row reached so far
SET #counting := 0;
WHILE #counting < #n DO
-- IF #STAT_NAME_CURR is not equal to the STAT_NAME for the current row,
-- THEN set #STAT_NAME_CURR to STAT_NAME value and reset #STAT_NAME_COUNT
-- ELSE just increment #STAT_NAME_COUNT
IF #STAT_NAME_CURR <> (SELECT #STAT_NAME FROM tmp1 LIMIT #counting,1) THEN
SET #STAT_NAME_CURR = (SELECT #STAT_NAME FROM tmp1 LIMIT #counting,1);
SET #STAT_NAME_COUNT = 0;
ELSE
SET #STAT_NAME_COUNT = #STAT_NAME_COUNT + 1;
END IF;
-- Set STAT_COUNT for current row to value of #STAT_NAME_COUNT
UPDATE tmp1 SET STAT_COUNT = #STAT_NAME_COUNT WHERE STAT_NAME = #STAT_NAME_CURR AND STAT_COUNT = '' LIMIT 1;
-- Move to next row
SET #counting = #counting + 1;
END WHILE;
select * from tmp1;
END //
delimiter ;
You might want to look into using cursors instead of the while loop.
However, you could probably accomplish what you are trying to do with a simple GROUP BY query:
SELECT STAT_NAME, STAT, COUNT(1) as STAT_COUNT
FROM performance_testing_stats.20131014
GROUP BY STAT_NAME, STAT
ORDER BY STAT_NAME, STAT;
Unless I'm missing something about what you are doing.
Related
In the code below, I'm trying go through the results of endDateTable row by row, comparing the current row's endDate to the previous row's endDate. If there has been any change since the previous, we increment #revisionNum. However, upon populating the new table, all of the #revisionNum entries are 0. What am I doing wrong?
NOTE: I'm using prepared statements in this manner since doing a straightforward SELECT into a variable gives a syntax error due to the LIMIT clause not allowing a variable in our version of MySQL.
BEGIN
DECLARE _currentEndDate DATETIME DEFAULT now();
DECLARE _priorEndDate DATETIME DEFAULT now();
SET #ResultsCount = (SELECT COUNT(*) FROM mainTable);
SET #j = 0;
WHILE #j < #ResultsCount DO
SET #revisionNum = 0;
/*CURRENT END DATE*/
SET #appResultQueryCurrent = CONCAT('
SELECT
end_date
INTO _currentEndDate
FROM endDateTable
LIMIT ', #j, ', 1'
);
PREPARE currentQueryStmt FROM #appResultQueryCurrent;
EXECUTE currentQueryStmt;
/*PREVIOUS END DATE*/
SET #appResultQueryPrior = CONCAT('
SELECT
end_date
INTO _priorAppEndDate
FROM endDateTable
LIMIT ', IF(#j = 0, 0, #j - 1), ', 1'
);
PREPARE priorQueryStmt FROM #appResultQueryPrior;
EXECUTE priorQueryStmt;
SET #revisionNum = IF(
#j = 0 OR (_currentEndDate = _priorEndDate),
#revisionNum,
IF(
_currentEndDate != _priorEndDate,
#revisionNum + 1,
#revisionNum
)
);
INSERT INTO finalTable (RevisionNum)
SELECT
#revisionNum AS RevisionNum
FROM endDateTable;
SET #j = #j +1;
END WHILE;
END $$
You don't need a loop, you can use INSERT INTO ... SELECT ..., incrementing the variable in the select query.
You also need an ORDER BY criteria to specify how to order the rows when comparing one row to the previous row.
INSERT INTO finalTable (RevisionNum, otherColumn)
SELECT revision, otherColumn
FROM (
SELECT IF(end_date = #prev_end_date, #revision, #revision := #revision + 1) AS revision,
#prev_end_date := end_date,
otherColumn
FROM endDateTable
CROSS JOIN (SELECT #prev_end_date := NULL, #revision := -1) AS vars
ORDER BY id) AS x
DEMO
The offset value in the LIMIT clause is tenuous without an ORDER BY.
Without an ORDER BY clause, MySQL is free to return results in any sequence.
There is no guarantee that LIMIT 41,1 will return the row before LIMIT 42,1, or that it won't return the exact same row as LIMIT 13,1 did.
(A table in a relational database represents an unordered set of tuples, there is no guaranteed "order" or rows in a table.)
But just adding ORDER BY to the queries isn't enough to fix the Rube-Goldberg-esque rigmarole.
In the code shown, it looks like each time through the loop, we're inserting a copy of endDateTable into finalTable. If that's 1,000 rows in endDateTable, we're going to get 1,000,000 rows (1,000 x 1,000) inserted into finalTable. Not at all clear why we need so many copies.
Given the code shown, it's not clear what the objective is. Looks like we are conditionally incrementing revisionNum, the end result of which is the highest revision num. Just guessing here.
If there is some kind of requirement to do this in a LOOP construct, within a procedure, I'd think we'd do a cursor loop. And we can use procedure variables vs user-defined variables.
Something along these lines:
BEGIN
DECLARE ld_current_end_date DATETIME;
DECLARE ld_prior_end_date DATETIME;
DECLARE li_done INT;
DECLARE li_revision_num INT;
DECLARE lcsr_end_date CURSOR FOR SELECT t.end_date FROM `endDateTable` t ORDER BY NULL;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET li_done = TRUE;
SET li_done = FALSE;
SET li_revision_num = 0;
OPEN lcsr_end_date;
FETCH lcsr_end_date INTO ld_current_end_date;
SET ld_prior_end_date = ld_current_end_date;
WHILE NOT li_done DO
SET li_revision_num = li_revision_num + IF( ld_current_end_date <=> ld_prior_end_date ,0,1);
SET ld_prior_end_date := ld_current_end_date;
FETCH lcsr_end_date INTO ld_current_end_date;
END WHILE;
CLOSE lcsr_end_date;
INSERT INTO `finalTable` (revisionnum) VALUES (li_revision_num);
END $$
Note the "order by" clause on the SELECT, its not clear what the rows should be ordered on, so we're using a literal as a placeholder.
As the end result, we insert a single row into finalTable.
Again, it's not clear what the code in the question is supposed to achieve, but doing a cursor loop across ordered rows would be much more efficient than a bazillion dynamic SQL executions fetching individual rows.
I am trying to write a Mysql query . I am getting some error on WITH clause of select function .
This is the sample query :
CREATE TRIGGER SAMPLE BEFORE INSERT ON SERVER
FOR EACH ROW
BEGIN
IF ((SELECT COUNT(S.ID) FROM SERVER S WITH UR) >= (SELECT L.SERVERS FROM SAMPLE L WHERE L.ID = 1 LIMIT 1)) THEN
SIGNAL SQLSTATE '73550' SET MESSAGE_TEXT='+ID';
END IF;
END;
and i am getting the error :
right syntax to use near 'UR) >= (SELECT L.SERVERS FROM SAMPLE L WHERE L.ID = 1 LIMIT 1))
Is WITH clause doesn't support in Mysql ? Or any other syntax should i use ? Any suggestion would helpful .
update :
Also i am using another query :
CREATE TRIGGER SAMPLE_TRIGGER NO CASCADE BEFORE INSERT ON TABLE_1
FOR EACH ROW
BEGIN
SET NEW.RID = (SELECT ID FROM TABLE_2 WHERE ACTIVE=0 FETCH FIRST 1 ROWS ONLY WITH RS USE AND KEEP EXCLUSIVE LOCKS);
IF (NEW.RID IS NULL) THEN
SIGNAL SQLSTATE '73550' SET MESSAGE_TEXT='+ID';
END IF;
END;
getting another error
right syntax to use near 'LIMIT 1 WITH RS USE AND KEEP EXCLUSIVE LOCKS);
IF (NEW.RID IS NULL) THEN'
In DB2, with ur means "with uncommitted read". This is a locking mechanism in the database, as explained here.
If you are porting code to MySQL, I would not worry about this. So, just remove it:
CREATE TRIGGER SAMPLE BEFORE INSERT ON SERVER
FOR EACH ROW
BEGIN
IF ((SELECT COUNT(S.ID) FROM SERVER S) >= (SELECT L.SERVERS FROM SAMPLE L WHERE L.ID = 1 LIMIT 1)) THEN
SIGNAL SQLSTATE '73550' SET MESSAGE_TEXT='+ID';
END IF;
END;
I've got this SQL code:
CREATE EVENT `update_statistics`
ON SCHEDULE EVERY 1 DAY STARTS '2015-08-24 02:00:00'
ON COMPLETION PRESERVE
DO BEGIN
UPDATE statistics
SET km_traveled = (SELECT sum(km)
FROM archived_trips),
passengers_driven = (SELECT sum(passengers)
FROM archived_trips),
trips_taken = (SELECT count(*)
FROM archived_trips)
WHERE id = 1;
UPDATE statistics
SET co_saved = ((SELECT km_traveled
FROM statistics) * 0.215 * (SELECT passengers_driven
FROM statistics))
WHERE id = 1;
END;
When I run it through the SQL console in PhpStorm - it runs fine and the scheduled task works and so on.
But if I try to run the query directly in phpmyadmin, I get the following error:
#1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '' at line 13
Line 13 is WHERE id=1;. Honestly, I don't see any syntax problem with the query. Any suggestions?
The delimiter of the query is ';' and ';' is also used in the stored procedure, causing the query to fail.
Add DELIMITER // before the statement and end with // to fix this.
DELIMITER //
CREATE EVENT `update_statistics`
ON SCHEDULE EVERY 1 DAY STARTS '2015-08-24 02:00:00'
ON COMPLETION PRESERVE
DO BEGIN
UPDATE statistics
SET km_traveled = (SELECT sum(km)
FROM archived_trips),
passengers_driven = (SELECT sum(passengers)
FROM archived_trips),
trips_taken = (SELECT count(*)
FROM archived_trips)
WHERE id = 1;
UPDATE statistics
SET co_saved = ((SELECT km_traveled
FROM statistics) * 0.215 * (SELECT passengers_driven
FROM statistics))
WHERE id = 1;
END; //
DELIMITER ;
With PHPstorm, sending multiple queries in one go is probably disabled, causing MySQL to ignore the delimiter.
You must set the DELIMITER to other char. else they cant find out the end of the EVENT.
DELIMITER //
CREATE EVENT `update_statistics`
ON SCHEDULE EVERY 1 DAY STARTS '2015-08-24 02:00:00'
ON COMPLETION PRESERVE
DO BEGIN
UPDATE statistics
SET km_traveled = (SELECT sum(km)
FROM archived_trips),
passengers_driven = (SELECT sum(passengers)
FROM archived_trips),
trips_taken = (SELECT count(*)
FROM archived_trips)
WHERE id = 1;
UPDATE statistics
SET co_saved = ((SELECT km_traveled
FROM statistics) * 0.215 * (SELECT passengers_driven
FROM statistics))
WHERE id = 1;
END//
Shot in the dark, but try removing the semicolon after END.
I have a MYSQL table and two of the fields are called Rate_per_unit and Cost. First I want the field Rate_per_unit to populate itself from another table called SHD_TEACHER then I want the field COST to populate itself also from RATE in SHD_TEACHER and multiplies by UNITS.
I have the following code which is giving me an error:
CREATE TRIGGER RATE_PER_UNIT_1
BEFORE INSERT ON SHD_SCHEDULE
FOR EACH ROW
SET NEW.RATE_PER_UNIT =
(
SELECT RATE
FROM SHD_TEACHER
WHERE TEACHERID = NEW.TEACHER_ID
LIMIT 1
)
SET NEW.COST = (
SELECT RATE
FROM SHD_TEACHER
WHERE TEACHERID = NEW.TEACHER_ID
) * UNITS
Any help please?
thanks
using your syntax, I would expect a delimiter statement and a begin/end block. So, try this:
DELIMITER $$
CREATE TRIGGER RATE_PER_UNIT_1
BEFORE INSERT ON SHD_SCHEDULE
FOR EACH ROW
BEGIN
SET NEW.RATE_PER_UNIT =
(
SELECT RATE
FROM SHD_TEACHER t
WHERE t.TEACHERID = NEW.TEACHER_ID
LIMIT 1
)
SET NEW.COST = (
SELECT t.RATE
FROM SHD_TEACHER t
WHERE t.TEACHERID = NEW.TEACHER_ID
) * NEW.UNITS
END $$
DELIMITER ;
You have a limit 1 in the first subquery, suggesting that there might be multiple matches. If so, you will get a run-time error in the second. Also, UNITS is just hanging out there, all alone. I assumed it is in the NEW record.
Here is another way to write this:
DELIMITER $$
CREATE TRIGGER RATE_PER_UNIT_1
BEFORE INSERT ON SHD_SCHEDULE
FOR EACH ROW
BEGIN
SELECT NEW.RATE_PER_UNIT := t.RATE, NEW.COST := t.RATE * NEW.UNITS
FROM (SELECT t.*
FROM SHD_TEACHER t
WHERE t.TEACHERID = NEW.TEACHER_ID
LIMIT 1
) t
END $$
DELIMITER ;
I have a table which contains relative large data,
so that it takes too long for the statements below:
SELECT MIN(column) FROM table WHERE ...
SELECT MAX(column) FROM table WHERE ...
I tried index the column, but the performance still does not suffice my need.
I also thought of caching min and max value in another table by using trigger or event.
But my MySQL version is 5.0.51a which requires SUPER privilege for trigger and does not support event.
It is IMPOSSIBLE for me to have SUPER privilege or to upgrade MySQL.
(If possible, then no need to ask!)
How to solve this problem just inside MySQL?
That is, without the help of OS.
If your column is indexed, you should find min(column) near instantly, because that is the first value MySQL will find.
Same goes for max(column) on an indexed column.
If you cannot add an index for some reason the following triggers will cache the MIN and MAX value in a separate table.
Note that TRUE = 1 and FALSE = 0.
DELIMITER $$
CREATE TRIGGER ai_table1_each AFTER INSERT ON table1 FOR EACH ROW
BEGIN
UPDATE db_info i
SET i.minimum = LEAST(i.minimum, NEW.col)
,i.maximum = GREATEST(i.maximum, NEW.col)
,i.min_count = (i.min_count * (new.col < i.minumum))
+ (i.minimum = new.col) + (i.minimum < new.col)
,i.max_count = (i.max_count * (new.col > i.maximum))
+ (i.maximum = new.col) + (new.col > i.maximum)
WHERE i.tablename = 'table1';
END $$
CREATE TRIGGER ad_table1_each AFTER DELETE ON table1 FOR EACH ROW
BEGIN
DECLARE new_min_count INTEGER;
DECLARE new_max_count INTEGER;
UPDATE db_info i
SET i.min_count = i.min_count - (i.minimum = old.col)
,i.max_count = i.max_count - (i.maximum = old.col)
WHERE i.tablename = 'table1';
SELECT i.min_count INTO new_min_count, i.max_count INTO new_max_count
FROM db_info i
WHERE i.tablename = 'table1';
IF new_max_count = 0 THEN
UPDATE db_info i
CROSS JOIN (SELECT MAX(col) as new_max FROM table1) m
SET i.max_count = 1
,i.maximum = m.new_max;
END IF;
IF new_min_count = 0 THEN
UPDATE db_info i
CROSS JOIN (SELECT MIN(col) as new_min FROM table1) m
SET i.min_count = 1
,i.minimum = m.new_min;
END IF;
END $$
DELIMITER ;
The after update trigger will be some mix of the insert and delete triggers.