In mysql, I have a trigger which checks for collisions, and updates the hp value of rows that have a collision. I also set a boolean value of the projectile to be true to mean that it should be deleted later. But I seem to do something twice that I would rather done once.
DELIMITER //
CREATE TRIGGER checkcollision2 BEFORE UPDATE ON projectiles
FOR EACH ROW
BEGIN
SET #unit_size:=25;
IF (SELECT count(*) FROM players WHERE wolf=1 AND NEW.x>=x AND NEW.x<=x+#unit_size AND NEW.y>=y AND NEW.y<=y+#unit_size)>0 THEN
SET NEW.deleted=1;
UPDATE players SET hp=hp-NEW.dmg WHERE wolf=1 AND NEW.x>=x AND NEW.x<=x+#unit_size AND NEW.y>=y AND NEW.y<=y+#unit_size;
END IF;
END;//
DELIMITER ;
In the above, I do a select which gives me the number of rows that have a collision. Then below I do the same query because I want to update those same rows. I could have just done the update, but I only want to do the SET NEW.deleted=1; if there was at least 1 row affected. Im not sure how to do that if I only keep the update statement.
Does anyone know of a batter way of doing this?
Thanks
If you want to set the NEW.deleted value based on the number of rows affected on update then you can use ROW_COUNT() to check the number of rows affected.
Example:
DELIMITER //
CREATE TRIGGER checkcollision2 BEFORE UPDATE ON projectiles
FOR EACH ROW
BEGIN
SET custom_unit_size := 25;
UPDATE players
SET hp = hp - NEW.dmg
WHERE wolf = 1
AND NEW.x >= x
AND NEW.x <= x + custom_unit_size
AND NEW.y >= y
AND NEW.y <= y + custom_unit_size;
IF ROW_COUNT() > 0 THEN
SET NEW.deleted = 1;
END IF;
END;
//
DELIMITER ;
Refer to:
ROW_COUNT()
returns the number of rows changed, deleted, or inserted by the last statement if it was an UPDATE, DELETE, or INSERT. For other statements, the value may not be meaningful.
Related
I want to add 1000 unique random values to the column AgentID to the table agents.
I have used Upsert to ignore the duplicate values. What I have done so far:
DROP PROCEDURE IF EXISTS Generate_random;
DELIMITER $$
CREATE PROCEDURE Generate_random()
BEGIN
Drop table if exists ai_training.`Agents`;
CREATE TABLE ai_training.`Agents`(AgentID int PRIMARY KEY);
SET #first = 1;
SET #last = 1000;
WHILE(#first <= #last) Do
INSERT INTO ai_training.`Agents` VALUES(
FLOOR(RAND()*(2900000-2800000+1)+2800000)
)
ON DUPLICATE KEY UPDATE AgentID = FLOOR(RAND()*(2900000-2800000+1)+2800000);
SET #first = #first + 1;
END WHILE;
END$$
DELIMITER ;
CALL Generate_random();
Select * from ai_training.`Agents`;
The problem is I need 1000 unique agentid's and this code is generating 1000 - the repeated ones. So, if it finds 6 repeated ids it returns 994 rows and not 1000.
Is there any way I can achieve this?
Can't change the random ID generation part.
You could use information function ROW_COUNT() to check whether a row was actually inserted or not, and increment the counter accordingly.
The documentation states:
With ON DUPLICATE KEY UPDATE, the affected-rows value per row is 1 if the row is inserted as a new row, 2 if an existing row is updated, and 0 if an existing row is set to its current values.
So:
WHILE(#first <= #last) DO
INSERT INTO ai_training.`Agents`
VALUES(FLOOR(RAND()*(2900000-2800000+1)+2800000))
ON DUPLICATE KEY UPDATE AgentID = AgentID;
IF ROW_COUNT() = 1 THEN
SET #first = #first + 1;
END IF;
END WHILE;
Note that I changed the action on duplicate key to a dummy update rather than re-assigning a new random value; this makes things a little less expensive when a collision happens.
CREATE PROCEDURE Generate_random()
BEGIN
Drop table if exists ai_training.`Agents`;
CREATE TABLE ai_training.`Agents`(AgentID int PRIMARY KEY);
SET #last = 1000;
WHILE (SELECT COUNT(*) FROM ai_training.`Agents`) < #last DO
INSERT IGNORE INTO ai_training.`Agents`
VALUES
(FLOOR(RAND()*(2900000-2800000+1)+2800000));
END WHILE;
END
I have been given a use case where an user needs to update a certain TIME field if other of the same type changes.
I have been given two columns
horario_arribo (TIME)
horario_salida (TIME)
Now, what I need to do is the followin
Make a trigger so when I change for example horario_arribo, horario_salida is the same TIME as horario_arribo minus 1 hour, and the same if I update horario_salida , make horario_arribo plus 1 hour.
I have been thinking how to do it, first check if the value trying to be updated is horario_salida, then I just add 1 hour to horario_arribo, or if I'm updating horario_arribo, to the same to horario_salida but minus 1 hour.
DELIMITER //
CREATE TRIGGER modificar_horarioruta AFTER
UPDATE ON ruta
FOR EACH ROW
BEGIN
IF(new.horario_salida) THEN
SET AddTime(old.horario_arribo, '00:60:00')
ELSE
SET AddTime(old.horario_salida, Here I dont know how to minus 1 hour to that)
END IF;
END //
DELIMITER ;
This is the data I have
So , in short, if I update horario_salida, horario_arribo need to be 1 hour ahead, if I update horario_arribo, horario_salida needs to be 1 hour less.
Thanks
IIUC:
DELIMITER //
CREATE TRIGGER modificar_horarioruta AFTER
UPDATE ON ruta
FOR EACH ROW
BEGIN
IF (NEW.horario_salida) THEN
SET NEW.horario_arribo = OLD.horario_arribo + 1;
ELSE
SET NEW.horario_salida = OLD.horario_salida - 1;
END IF;
END //
DELIMITER ;
Let me know if this is not what you need.
I have something like this:
UPDATE users SET credits=credits-1000 WHERE id=1
How do I make this fail if credits goes negative? I want to avoid having to query the database twice just to check for that. I need it to FAIL/return false if it goes negative, not set the credits to zero.
What about adding the condition ?
UPDATE users SET credits=credits-1000 WHERE id=1 AND credits > 1000
I think you have to use stored procedure for this purpose. For example
CREATE procedure UpdateUserCredit
#user_id INT,
#credits INT,
#op_status BIT OUTPUT
AS
BEGIN
SET #op_status = 0
UPDATE Users
SET credits = credits - #credits
WHERE id = #user_id
AND credits - #credits > 0
IF ROW_COUNT() = 0
RETURN
ELSE
SET #op_status = 1
END
This stored procedure will return 0 in variable #op_status if current credits + #credits for #user_id would be less zero.
DELIMITER $$
CREATE TRIGGER users_bu BEFORE UPDATE ON users FOR EACH ROW
BEGIN
IF NEW.credits != OLD.credits AND NEW.credits < 0 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'negative balances are not supported';
END IF;
END $$
DELIMITER ;
Before the update is written to a row in the table, the new (replacement) value for credits is checked for negative values if that value has been changed by this update. If it changed, and is now negative, deny the update by throwing an exception. Requires MySQL Server 5.5 or later as written, but can be made to work with 5.1 or 5.0 with some modifications.
Note that FOR EACH ROW doesn't mean the whole table, it means that each row affected by the update is evaluated individually by this code, which will of course typically be just one row.
I'm am trying to create this mysql trigger:
DELIMITER //
CREATE TRIGGER checkcollision2 AFTER UPDATE ON projectiles p1
FOR EACH ROW
BEGIN
IF (SELECT count(*) FROM players p WHERE p.wolf=1 AND NEW.x>=p.x AND NEW.x<=p.x+25 AND NEW.y>=p.y AND NEW.y<=p.y+25) > 0 THEN
DELETE FROM projectiles p2 WHERE p1.id=p2.id;
END IF;
END;//
DELIMITER ;
But this isn't working, as its getting a syntax error. I'm not sure if its because I'm using an alias names for the tables. I am doing that because, if the if statement is true, then I want to delete the current updated row that the trigger started on. I think I could do that by doing a delete from the same table, where the id is the same, but if I don't use alias, then all rows from this table would be deleted.
Does anyone see whats wrong here?
Alias names on identifies at trigger signature level is not supported and hence is incorrect.
Instead you can define alias within the trigger body if required.
And as you want to perform a delete operation based on a condition, you can make use of NEW to compare with a specific row-column value. Current where clause WHERE p1.id=p2.id will delete ALL rows from the table, which is unsafe.
Change your trigger definition as below:
DELIMITER //
DROP TRIGGER IF EXISTS checkcollision2 //
CREATE TRIGGER checkcollision2 AFTER UPDATE ON projectiles
FOR EACH ROW
BEGIN
DELCARE match_count INT DEFAULT 0;
SELECT count(*) INTO match_count
FROM players p
WHERE p.wolf = 1
AND NEW.x BETWEEN p.x AND ( p.x + 25 )
AND NEW.y BETWEEN p.y AND ( p.y + 25 )
IF match_count > 0 THEN
DELETE FROM projectiles WHERE id = NEW.id;
END IF;
END;
//
DELIMITER ;
I'm trying to get this trigger to work:
CREATE TRIGGER Transaction_insert BEFORE INSERT ON Transaction
FOR EACH ROW WHERE Number = NEW.AccountNumber
IF Account.CreditBalance + NEW.Amount < Account.CreditLimit THEN
UPDATE Account SET CreditBalance = CreditBalance + NEW.Amount where Number = NEW.AccountNumber;
ELSE
SET NEW.Valid = 0
END IF;
This is the error I get from myPHPAdmin.
Your IF needs to be a full SELECT to reference another table (Account)
IF EXISTS (SELECT * FROM `Account` A
WHERE A.CreditBalance + NEW.Amount < A.CreditLimit AND
A.Number = NEW.AccountNumber) THEN
UPDATE ...
Edit: this was on your 2nd duplicate answer
In this case, remove the WHERE after FOR EACH ROW
Updated Answer
This is what I think you want, assuming that Account to Transaction is a 1:N relationship keyed on Number/AccountNumber:
DELIMITER //
-- Assumptions:
-- 1. Transaction.AccountNumber is F.K. REFERENCES Account(Number)
-- 2. Account.Number is UNIQUE
--
CREATE TRIGGER trg_bi_transaction BEFORE INSERT ON Transaction
FOR EACH ROW
BEGIN
-- Adjust account balance (if permitted)
--
UPDATE Account
SET CreditBalance = CreditBalance + NEW.Amount
WHERE Number = NEW.AccountNumber
AND
(CreditBalance + NEW.Amount) < CreditLimit;
-- Was the adjustment valid/permitted?
--
SET NEW.Valid = (ROW_COUNT() = 1);
END //
DELIMITER ;
That trigger will attempt to UPDATE the proper Account for any given Transaction if the CreditLimit permits. The Valid field will be set to 1 if the UPDATE succeeded, and 0 if it did not.
Original Answer
MySQL triggers do not support trigger-level WHERE clauses. Move the Number/NEW.AccountNumber check inside the trigger body.