MySQL - trigger on before insert + if data already exists + enum column - mysql

I am working on a trigger that I though is quite easy and should work but it is not working.
Here is the (abstract) table structure:
PK_id | FK1_id | FK2_id | status
1 | 12 | 15 | 'ok'
status column is defined as enum('ok', 'ok_2', 'not_ok') NUT NULL with no default value.
The trigger should verify that a combination of both FKx_id values already exists and if yes it should set the status to 'ok_2', otherwise to 'ok' and if the status is set in the INSERT INTO it is not touched.
The trigger I have right now (only body!):
BEGIN
DECLARE cnt INT;
SET cnt = (SELECT COUNT(*) FROM `table` WHERE `FK1_id` = NEW.FK1_id AND `FK2_id` = NEW.FK2_id);
IF cnt > 0 AND NEW.status IS NULL THEN
SET NEW.status = 'ok_2';
ELSEIF NEW.status IS NULL THEN
SET NEW.status = 'ok';
END IF;
END
Unfortunately this trigger sets the status always to 'ok' - please notice that the status is not part of the INSERT query (thus considered as NULL). I have previously tried this trigger body with the same result:
BEGIN
IF (SELECT COUNT(*) FROM `table` WHERE `FK1_id` = NEW.FK1_id AND `FK2_id` = NEW.FK2_id) > 0 AND NEW.status IS NULL THEN
SET NEW.status = 'ok_2';
ELSEIF NEW.status IS NULL THEN
SET NEW.status = 'ok';
END IF;
END
and also this (with the very same result):
BEGIN
IF EXISTS(SELECT * FROM `table` WHERE `FK1_id` = NEW.FK1_id AND `FK2_id` = NEW.FK2_id LIMIT 1) AND NEW.status IS NULL THEN
SET NEW.status = 'ok_2';
ELSEIF NEW.status IS NULL THEN
SET NEW.status = 'ok';
END IF;
END
Can anyone tell me why the first condition is never met even if I am inserting the same FKx_id combination that is already present in the table?
EDIT: I switched the condition and the result is also the same - no 'ok_2' status set:
BEGIN
DECLARE cnt INT;
SET cnt = (SELECT COUNT(*) FROM `table` WHERE `FK1_id` = NEW.FK1_id AND `FK2_id` = NEW.FK2_id);
IF cnt = 0 AND NEW.status IS NULL THEN
SET NEW.status = 'ok';
ELSEIF NEW.status IS NULL THEN
SET NEW.status = 'ok_2';
END IF;
END

Got it.
The problem was this declaration of the status column:
status enum('ok', 'ok_2', 'not_ok') NOT NULL
which leads into status being pre-filled with the first enum's value if the status is not set in the INSERT statement. So the solution is next trigger body:
BEGIN
DECLARE cnt INT;
SET cnt = (SELECT COUNT(*) FROM `table` WHERE `FK1_id` = NEW.FK1_id AND `FK2_id` = NEW.FK2_id);
IF cnt = 0 THEN
SET NEW.status = 'ok';
ELSEIF NEW.status = 'ok' THEN
SET NEW.status = 'ok_2';
END IF;
END
Now if I do this insert for the first time
INSERT INTO table (FK_1, FK_2) VALUES (100, 150)
the status is 'ok', if I insert this for the second time
INSERT INTO table (FK_1, FK_2) VALUES (100, 150)
the status is 'ok_2' and if I set the status explicitly like this:
INSERT INTO table (FK_1, FK_2, status) VALUES (100, 150, 'not_ok')
the status is 'not_ok'.
so, when working with enums that have no default value while are set as NOT NULL - do not expect them to be NULL on insert when omitted. The will be pre-filled probably with the first enums's value.

Related

I want to throw an exception when my mysql stored procedure doesn't find any results

I'm having trouble finding a way to throw an exception from a stored procedure if no data is found.
I need a way to check the query results in the if statement.
If the count is 0 then throw exception. If there's data then return the data.
CREATE DEFINER=`adminHC`#`%` PROCEDURE `find_user_with_credentials`(
IN `usernameIN` VARCHAR(45)
, IN `encryptedpwIN` VARCHAR(45)
)
BEGIN
SELECT *
FROM (SELECT distinct
userid
,firstname
,lastname
,publicname
,email
,addressid
,create_date
,update_date
,active
FROM dev_users
WHERE (email = usernameIN OR (
publicname IS NOT null AND
publicname = usernameIN
))
AND encryptedpw = encryptedpwIN
AND active = 1) user;
IF count(user) > 0 THEN
SELECT user;
ELSE
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = "Invalid username or password",
MYSQL_ERRNO = 403;
END IF;
END
Start with a SELECT COUNT(*) query and check the result of that.
CREATE DEFINER=`adminHC`#`%` PROCEDURE `find_user_with_credentials`(
IN `usernameIN` VARCHAR(45)
, IN `encryptedpwIN` VARCHAR(45)
)
BEGIN
IF (SELECT COUNT(*)
FROM dev_users
WHERE (email = usernameIN OR (
publicname IS NOT null AND
publicname = usernameIN
))
AND encryptedpw = encryptedpwIN
AND active = 1) > 0
THEN SELECT
userid
,firstname
,lastname
,publicname
,email
,addressid
,create_date
,update_date
,active
FROM dev_users
WHERE (email = usernameIN OR (
publicname IS NOT null AND
publicname = usernameIN
))
AND encryptedpw = encryptedpwIN
AND active = 1;
ELSE
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = "Invalid username or password",
MYSQL_ERRNO = 403;
END IF;
END
There's also no need for the SELECT * wrapper. And I doubt you need SELECT DISTINCT, since I assume you can't have multiple rows with the same userid, and there's nothing in the query that will duplicate rows.
If the query were too expensive to do twice, you could do it once and save the results in a temporary table. Then you can use SELECT COUNT(*) FROM temp_table to check if there are any results, and finally SELECT * FROM temp_table to return the data.

How to use IF condition in storedProcedure?

I am using a condition to lock the login account after a fixed number of attempts with the wrong password. The update portion is as follows :
loginAttempts (INT(1)) is read from login account first
DECLARE LoginAttempts INT(1);
UPDATE login SET
LOGIN_ACCOUNT_STATUS = (SELECT CASE (LoginAttempts > MaxLoginAttempts) WHEN 1 THEN 'LOCKED' ELSE 'ACTIVE' END),
LOGIN_LOGIN_ATTEMPTS = (SELECT CASE (#USER_FOUND AND #PASSWORD_CORRECT) WHEN 1 THEN 0 ELSE LOGIN_LOGIN_ATTEMPTS + 1 END),
LOGIN_LAST_LOGIN_DATE = (SELECT CASE (#USER_FOUND AND #PASSWORD_CORRECT) WHEN 1 THEN TransactionDateTime ELSE LOGIN_LAST_LOGIN_DATE END),
LOGIN_LAST_LOGIN_LOCATION = null
WHERE LOGIN_EMAIL = UserEmail;
When I set MaxLoginAttmpts at 5, the account gets locked at 11 (Greater than twice maxLoginAttempts).
If I set MaxLoginAttmpts at 2, the account gets locked at 5 (Greater than twice maxLoginAttempts).
Why is this ? Any help is appreciated.
Here I am adding the full stored procedure.
CREATE DEFINER=`pubuducg`#`%` PROCEDURE `CustomerAuthenticate`(IN UserEmail VARCHAR(100), IN PassWD VARCHAR(40), IN AccStatus VARCHAR(100),IN TransactionDateTime DATETIME, IN MaxLoginAttempts INT(1))
BEGIN
DECLARE LoginUserID INT(11);
DECLARE LoginEmail VARCHAR(50);
DECLARE LoginPassword TINYTEXT;
DECLARE LoginAttempts INT(1);
DECLARE AccountStatus VARCHAR(45);
DECLARE UserRoles VARCHAR(80);
SELECT
login.LOGIN_USER_ID,
login.LOGIN_EMAIL,
login.LOGIN_PASSWORD,
login.LOGIN_ACCOUNT_STATUS,
login.LOGIN_LOGIN_ATTEMPTS,
GROUP_CONCAT(user_role.USER_ROLE_ROLE SEPARATOR ',') AS ROLES
INTO
LoginUserID,
LoginEmail,
LoginPassword,
AccountStatus,
LoginAttempts,
UserRoles
FROM login
INNER JOIN user_role ON
user_role.USER_ROLE_USER_ID = login.LOGIN_USER_ID AND user_role.USER_ROLE_STATUS = AccStatus
WHERE login.LOGIN_EMAIL = UserEmail;
SET #USER_FOUND = found_rows();
SET #PASSWORD_CORRECT = IF((LoginPassword = PassWD AND AccountStatus = AccStatus), true, false);
UPDATE login SET
LOGIN_ACCOUNT_STATUS = (SELECT CASE (LoginAttempts > MaxLoginAttempts) WHEN 1 THEN 'LOCKED' ELSE 'ACTIVE' END),
LOGIN_LOGIN_ATTEMPTS = (SELECT CASE (#USER_FOUND AND #PASSWORD_CORRECT) WHEN 1 THEN 0 ELSE LOGIN_LOGIN_ATTEMPTS + 1 END),
LOGIN_LAST_LOGIN_DATE = (SELECT CASE (#USER_FOUND AND #PASSWORD_CORRECT) WHEN 1 THEN TransactionDateTime ELSE LOGIN_LAST_LOGIN_DATE END),
LOGIN_LAST_LOGIN_LOCATION = null
WHERE LOGIN_EMAIL = UserEmail;
SELECT
IF(#USER_FOUND AND #PASSWORD_CORRECT, LoginUserID,0) AS USER_ID,
#PASSWORD_CORRECT AS AUTHENTICATED,
#USER_FOUND AS USER_EXISTS,
AccountStatus AS ACCOUNT_STATUS,
IF(#USER_FOUND AND #PASSWORD_CORRECT, 0, LoginAttempts + 1) AS LOGIN_ATTEMPTS,
IF(#USER_FOUND AND #PASSWORD_CORRECT, UserRoles,null) AS USER_ROLES;
END

Add two timestamp variable in mysql trigger

I have written my trigger like this
CREATE TRIGGER `update_total_duration` AFTER UPDATE ON `sales_activity`
FOR EACH ROW thisTrigger:BEGIN
IF(NEW.activity_id=4)
THEN LEAVE thisTrigger;
ELSEIF NOT EXISTS
(SELECT 1 FROM sales_duration_update WHERE user_id = NEW.user_id AND date = CURDATE())
THEN INSERT INTO sales_duration_update (user_id,date,total_duration) VALUES (NEW.user_id,CURDATE(),NEW.duration);
ELSE
//problem is here when total_duration = total_duration + NEW.duration
UPDATE sales_duration_update SET total_duration = total_duration + NEW.duration WHERE user_id = NEW.user_id AND date = CURDATE();
END IF;
END
Problem is here in code
total_duration = total_duration + NEW.duration
where both variable are timestamp variable.How can I add two timestamp variables in trigger?
I found the answer.May be will help someone.
CREATE TRIGGER `update_total_duration` AFTER UPDATE ON `sales_activity`
FOR EACH ROW thisTrigger:BEGIN
IF(NEW.activity_id=4)
THEN LEAVE thisTrigger;
ELSEIF NOT EXISTS
(SELECT 1 FROM sales_duration_update WHERE user_id = NEW.user_id AND date = CURDATE())
THEN
INSERT INTO sales_duration_update (user_id,date,total_duration) VALUES (NEW.user_id,CURDATE(),NEW.duration);
ELSE
UPDATE sales_duration_update SET total_duration = ADDTIME(total_duration, NEW.duration) WHERE user_id = NEW.user_id AND date = CURDATE();
END IF;

Rewrite MySQL Trigger to Postgres

guys!
I have a trigger in MySQL database:
CREATE DEFINER="root"#"127.0.0.1" TRIGGER `tai_actions_for_active_count` AFTER INSERT ON `actions` FOR EACH ROW BEGIN
DECLARE l_isnn TINYTEXT;
IF NEW.action_type IN ('CREATION', 'VERIFICATION', 'CLOSE') THEN
SET l_isnn = IF(NEW.isnn is NULL, '*', NEW.isnn);
IF NOT NEW.action_type = 'CLOSE' THEN
INSERT INTO subscriptions.`statistics_active_count`(`service_id`, `operator_id`, `isnn`, `active_count`)
VALUES (NEW.service_id, NEW.operator_id, l_isnn, 1)
ON DUPLICATE KEY UPDATE active_count = active_count + 1;
ELSE
SET #tai_actions_for_active_count = -1;
UPDATE subscriptions.`statistics_active_count` SET active_count = #tai_actions_for_active_count := active_count - 1
WHERE `service_id` = NEW.service_id AND `operator_id` = NEW.operator_id AND `isnn` = l_isnn;
IF #tai_actions_for_active_count = 0 THEN DELETE FROM subscriptions.`statistics_active_count` WHERE `active_count` = 0; END IF;
END IF;
END IF;
END
So I need to rewrite it to make it works in Postgres database. As there's ON DUPLICATE KEY UPDATE I'm using Postgres version 9.5 with UPSERT (ON CONFLICT (KEY) DO UPDATE).
So I poorly know SQL language can you tell me what I'm doing wrong? There's the Postgres PL code:
DECLARE
l_isnn TEXT;
tai_actions_for_active_count INTEGER;
BEGIN
IF NEW.action_type IN ('CREATION', 'VERIFICATION', 'CLOSE') THEN
IF NEW.isnn is NULL THEN
l_isnn := '*';
ELSE
l_isnn := NEW.isnn;
END IF;
IF NOT NEW.action_type = 'CLOSE' THEN
INSERT INTO "subscriptions.statistics_active_count"(service_id, operator_id, isnn, active_count)
VALUES (NEW.service_id, NEW.operator_id, l_isnn, 1)
ON CONFLICT(active_count) DO UPDATE SET active_count = active_count + 1;
ELSE
tai_actions_for_active_count := -1;
UPDATE "subscriptions.statistics_active_count" SET active_count = active_count - 1
-- (tai_actions_for_active_count := active_count - 1)
WHERE service_id = NEW.service_id AND operator_id = NEW.operator_id AND isnn = l_isnn;
UPDATE "subscriptions.statistics_active_count" SET tai_actions_for_active_count = active_count
WHERE service_id = NEW.service_id AND operator_id = NEW.operator_id AND isnn = l_isnn;
IF tai_actions_for_active_count = 0 THEN DELETE FROM "subscriptions.statistics_active_count" WHERE active_count = 0; END IF;
END IF;
END IF;
RETURN NULL;
END;
As I want to test this trigger I'm getting an error -- relation "subscriptions.statistics_active_count" does not exist
Can you help me with that code?
Finally I've got the solution. I guess :)
BEGIN
IF NEW.action_type IN ('CREATION', 'VERIFICATION', 'CLOSE') THEN
IF NEW.isnn IS NULL THEN
l_isnn := '*';
ELSE
l_isnn := NEW.isnn;
END IF;
IF NOT NEW.action_type = 'CLOSE' THEN
BEGIN
INSERT INTO subscriptions.statistics_active_count (service_id, operator_id, isnn, active_count)
VALUES (NEW.service_id, NEW.operator_id, l_isnn, 1);
EXCEPTION WHEN unique_violation THEN
UPDATE subscriptions.statistics_active_count SET active_count = active_count + 1
WHERE service_id = NEW.service_id and operator_id=NEW.operator_id;
END;
ELSE
tai_actions_for_active_count := -1;
WITH upd AS
(UPDATE subscriptions.statistics_active_count
SET active_count = active_count - 1
WHERE service_id = NEW.service_id AND operator_id = NEW.operator_id AND isnn = l_isnn;
RETURNING active_count)
SELECT *
FROM upd INTO tai_actions_for_active_count;
IF tai_actions_for_active_count = 0 THEN
DELETE FROM public.statistics_active_count
WHERE active_count = 0;
END IF;
END IF;
END IF;
END;

SELECT statement from table inside cursor loop force cursor to EXIT

New to MySQL Stored Procedures.
If I uncomment any of the 4 SELECT lines (Below) then the routine EXITS out of the FETCH loop. Do not understand why
-- --------------------------------------------------------------------------------
-- Routine DDL
-- Note: comments before and after the routine body will not be stored by the server
-- --------------------------------------------------------------------------------
DELIMITER $$
CREATE DEFINER=`root`#`localhost` PROCEDURE `UpdateStatusAudit`()
BEGIN
-- Create loop for all $Service records
DECLARE svc_id INT;
DECLARE test INT;
DECLARE svc_name VARCHAR(100);
DECLARE no_more_rows BOOLEAN;
DECLARE up_duration DECIMAL(11,2);
DECLARE down_duration DECIMAL(11,2);
DECLARE maint_duration DECIMAL(11,2);
DECLARE degr_duration DECIMAL(11,2);
DECLARE services_cur CURSOR FOR SELECT service_id,service_name FROM services ORDER BY service_id;
-- Declare 'handlers' for exceptions
DECLARE CONTINUE HANDLER FOR NOT FOUND
SET no_more_rows = TRUE;
OPEN services_cur;
the_loop: LOOP
FETCH services_cur INTO svc_id,svc_name;
IF no_more_rows THEN
CLOSE services_cur;
LEAVE the_loop;
END IF;
SET up_duration = 0;
SET down_duration = 0;
SET maint_duration = 0;
SET degr_duration = 0;
SELECT svc_id;
BEGIN
-- SELECT IFNULL(sum(duration),0) INTO up_duration FROM daily_audit_summary where service_id = svc_id AND status = 'UP' AND Date = current_date - 1 group by date,service_id,status;
-- SELECT IFNULL(sum(duration),0) INTO down_duration FROM daily_audit_summary where service_id = svc_id AND status = 'DOWN' AND Date = current_date - 1 group by date,service_id,status;
-- SELECT IFNULL(sum(duration),0) INTO maint_duration FROM daily_audit_summary where service_id = svc_id AND status = 'MAINT' AND Date = current_date - 1 group by date,service_id,status;
-- SELECT IFNULL(sum(duration),0) INTO degr_duration FROM daily_audit_summary where service_id = svc_id AND status = 'DEGR' AND Date = current_date - 1 group by date,service_id,status;
END;
-- insert into daily_status
INSERT INTO daily_status (date,service_id,time_up,time_down,time_maint,time_degraded) values (current_date-1,svc_id,up_duration,down_duration,maint_duration,degr_duration);
END LOOP the_loop;
END
Did you try assigning the variables like this:
SELECT
up_duration := IFNULL(SUM(duration), 0)
FROM daily_audit_summary
WHERE service_id = svc_id
AND status = 'UP'
AND Date = current_date - 1
GROUP BY
date,
service_id,
status;
?
You could also combine all the assignments into a single SELECT:
SELECT
up_duration := SUM(CASE status WHEN 'UP' THEN duration ELSE 0 END)
down_duration := SUM(CASE status WHEN 'DOWN' THEN duration ELSE 0 END)
maint_duration := SUM(CASE status WHEN 'MAINT' THEN duration ELSE 0 END)
degr_duration := SUM(CASE status WHEN 'DEGR' THEN duration ELSE 0 END)
FROM daily_audit_summary
WHERE service_id = svc_id
AND status = 'UP'
AND Date = current_date - 1
GROUP BY
date,
service_id,
status;
But maybe you could avoid the cursor (and thus the loop) by using a single statement to do all the job:
INSERT INTO daily_status (
date,
service_id,
time_up,
time_down,
time_maint,
time_degraded
)
SELECT
d.Date,
s.service_id,
SUM(CASE das.status WHEN 'UP' THEN das.duration ELSE 0 END),
SUM(CASE das.status WHEN 'DOWN' THEN das.duration ELSE 0 END),
SUM(CASE das.status WHEN 'MAINT' THEN das.duration ELSE 0 END),
SUM(CASE das.status WHEN 'DEGR' THEN das.duration ELSE 0 END)
FROM services s
CROSS JOIN (SELECT CURRENT_DATE - 1 AS Date) AS d
LEFT JOIN daily_audit_summary AS das
ON s.service_id = das.service_id
AND das.Date = d.Date;
I guess that I needed to give a better explanation...
The Code is a Work In Progress and due to requirements, I cannot get rid of the "Services_cur" Cursor.
What I am finding is that when the query for the "Services_Cur" returns say 10 records and within the "the Loop" If I use a SELECT INTO statement from a TABLE such as
"SELECT F1 INTO MyVar from Atable where Afld = somevalue" the LOOP exits as if the "Services Cur" cursor was out of data???
If I issue a "SELECT 1234 INTO MyVar" the Loop works and I get 10 results (As Expected).
I am new to Stored Procedures for MySql and could not find an example of someone doing a series of "SELECT value for table" while within a Loop of FETCHES.
I hope this helps explain the issue better
Thanks for any help.