mysql stored function simple login, extract int from set - mysql

this is my first mySQL stored-function 'cause i've always managed such things with php.
I want a function that checks if a user can log in my client-area.
I wrote the code above:
DELIMITER $$
CREATE FUNCTION `A05`.`login` (user VARCHAR(64),pass VARCHAR(64)) RETURNS INT
BEGIN
declare num_rows int;
declare id int;
SELECT (#num_rows:=COUNT(*)),(#id:=`Credential_id`) FROM `A05`.`Credentials` where `Credential_UserName` = user;
if num_rows = 0 then
-- user not present
return (-1);
end if;
-- user present, checking password
SELECT (#num_rows:=COUNT(*)),(#id:=`Credential_id`) FROM `A05`.`Credentials` where `Credential_id` = id AND `Credential_PASSWORD` = SHA1(pass);
if num_rows = 0 then
-- user presente, password incorrect
INSERT INTO `a05`.`Events` (
`Event_id` ,
`Event_RegistrationDate` ,
`Event_VariationDate` ,
`Event_State`,
`Event_Notes`,
`Event_SenderId`,
`Event_Type`
)
VALUES (
NULL , NOW(), NOW(), 'wp', NULL, id, 1
);
return (-2);
end if;
-- user present, password correct
UPDATE `A05`.`Credentials` SET `Credential_LastAccess`=NOW() where `Credential_id` = id;
INSERT INTO `a05`.`Events` (
`Event_id` ,
`Event_RegistrationDate` ,
`Event_VariationDate` ,
`Event_State`,
`Event_Notes`,
`Event_SenderId`,
`Event_Type`
)
VALUES (
NULL , NOW(), NOW(), 'ok', NULL, id, 0
);
return id;
END
I think that the syntax should be right except for the last statement return id cause i return a set instead of an integer.
The problem is that when i try to store this function on mysql i get this error:
Error 1415: Not allowed to return a result set from a function
Then i changed the last statement from 'return id' to 'return 0' for testing purpose but i keep getting the same error.
Probably is a newbie error cause it's the very first time for me on sql "advanced" scripting.
Thank you very much in advance!

Related

Update a value and set a local variable in a case statement

I am trying to update a value in the database but also want to set a local variable VAR_IS_RATE_LIMITED. The reason for this is because I do not want to use a select statement and want to execute it within one. How can I set the VAR_IS_RATE_LIMITED? I looked at other questions but their CASE statements weren't embedded in an update statement.
DELIMITER //
CREATE FUNCTION F_RATE_LIMITED(P_IP varchar(45),
P_MAX_RATE int unsigned
)
RETURNS INT UNSIGNED
BEGIN
DECLARE VAR_IS_RATE_LIMITED INT UNSIGNED DEFAULT 0;
INSERT INTO rate_limit (ip, rate)
VALUES (P_IP, 1)
ON DUPLICATE KEY UPDATE
rate =
CASE
WHEN (rate + 1) > P_MAX_RATE THEN
SET VAR_IS_RATE_LIMITED = 1;
rate
ELSE
rate + 1
END;
RETURN VAR_IS_RATE_LIMITED;
END; //
DELIMITER ;
To set something during a data modification statement, it has to be part of an expression, not a statement. And as far as I know you can't set a stored function variable in an expression. But you can set a user variable:
DELIMITER //
CREATE FUNCTION F_RATE_LIMITED(P_IP varchar(45),
P_MAX_RATE int unsigned
)
RETURNS INT UNSIGNED
BEGIN
SET #VAR_IS_RATE_LIMITED = 0;
INSERT INTO rate_limit (ip, rate)
VALUES (P_IP, 1)
ON DUPLICATE KEY UPDATE
rate =
CASE
WHEN (rate + 1) > P_MAX_RATE THEN
CASE WHEN #VAR_IS_RATE_LIMITED := 1 THEN rate END
ELSE
rate + 1
END;
RETURN #VAR_IS_RATE_LIMITED;
END; //
DELIMITER ;
Here the variable is set in a CASE test (you can also use an IF expression) that is always true so rate is always returned from the CASE.

Why I received "null" even if the table contains a specific value for that column? [duplicate]

I have just started to create a stored function this is my first time so I am having a few problems. Currently I call the function using SELECT test(); (test is the function name for now). I want to send a number to the function (username ID) and have the username returned.
I have this working by using SELECT test(1); 1 is the ID of a user in the table. This seems to work as the username is returned, but if I type in any number the same username is returned also.
BEGIN
DECLARE new_username VARCHAR(90);
SELECT `username` INTO new_username FROM `users` WHERE `ID` = ID;
return new_username;
END
I have set the paramter as ID int .
Am I right in thinking that the keyword INTO will put the value of the username into the variable new_username ? If I run it without the INTO I get the error:
Not allowed to return a result set from a function
Have I made any obvious mistakes in this, I hope I havent done it totally wrong. Thanks for any advice :).
Edit : I just added a few more rows into my table , I now get the error:
Result consisted of more than one row
Full sql version:
CREATE DEFINER=`elliotts`#`%` FUNCTION `test`(ID int)
RETURNS varchar(32) CHARSET latin1
BEGIN
DECLARE new_username VARCHAR(32);
SELECT `username`
INTO new_username
FROM `users`
WHERE `ID` = ID;
return new_username;
END
Use:
DROP FUNCTION IF EXISTS `example`.`test` $$
CREATE FUNCTION `example`.`test` (param INT) RETURNS VARCHAR(32)
BEGIN
DECLARE new_username VARCHAR(32);
SELECT `username`
INTO new_username
FROM `users`
WHERE `ID` = param;
RETURN COALESCE(new_username, 'Username not found');
END $$
Mind that the VARCHAR length of the RETURN value matches the variable, which should match the column length you want to return.

Function that generate Code returns the same things

There is a MySQL function in our web system to generate Code. The structure of the code is
district_cd(length:2) + date(length:8) + sequence no(length:5,start at 1).<like : ab2016090800001>
The sequence no was saved in table and will be updated (+1) when generate a new code.
But sometimes it returned two same codes and makes us fall in trouble. Here are the captures to replicate this problem, I will attach the DDL after this.
Step 1.Client1->change to manual commit then generate a code, but do not commit.
SET autocommit = 0;
select * from applies;
select * from sequence where apply_date = "2016-09-08";
select nextval("ab");
insert into applies (apply_id,apply_no,created,district_cd) values (2,"ab2016090800002","ab",now());
select * from sequence where apply_date = "2016-09-08";
Step2.Client2->change to manual commit then generate a code, stuck as Client1 locked
SET autocommit = 0;
select * from applies;
select * from sequence where apply_date = "2016-09-08";
insert into applies (apply_id,apply_no,created,district_cd) values (3,"ab20160908123456780","ab",now());
Step3.Client1->commit;
commit;
select * from sequence where apply_date = "2016-09-08";
Step4.Client2->code was generated and two records appeared in sequence table
select * from sequence where apply_date = "2016-09-08";
capture of Step4
Step5.Client2->commit;one of the two records that appeared in sequence table was deleted.The codes generated are duplicated.
commit;
select * from sequence where apply_date = "2016-09-08";
select * from applies;
capture of Step5
※DDL
Table:applies (apply_no:save the code)
CREATE TABLE `applies` (
`apply_id` varchar(100) NOT NULL DEFAULT '',
`apply_no` varchar(100) NOT NULL DEFAULT '',
`district_cd` varchar(100) DEFAULT NULL,
`created` datetime DEFAULT NULL,
PRIMARY KEY (`apply_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Table:sequence (current_value:save current sequnce value)
CREATE TABLE `sequence` (
`district_cd` varchar(3) NOT NULL DEFAULT '',
`current_value` int(11) NOT NULL DEFAULT '0',
`apply_date` date NOT NULL DEFAULT '0000-00-00',
PRIMARY KEY (`district_cd`,`current_value`,`apply_date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Function:currval->get current sequence value by district_cd
DELIMITER ;;
CREATE DEFINER=`usr`#`%` FUNCTION `currval`(d VARCHAR(3)) RETURNS int(11)
DETERMINISTIC
BEGIN
DECLARE value INTEGER;
DECLARE needInitSequence INTEGER;
DECLARE today DATE;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET needInitSequence = 1;
SET value = 0;
SET today = current_date();
SELECT `current_value` INTO value
FROM `sequence`
WHERE `district_cd` = d AND `apply_date` = today limit 1;
IF needInitSequence = 1 THEN
INSERT INTO `sequence` (`district_cd`, `current_value`, `apply_date`) VALUES (d, value, today);
END IF;
RETURN value;
END
;;
DELIMITER ;
Function:nextval->generate code by district_cd
DELIMITER ;;
CREATE DEFINER=`usr`#`%` FUNCTION `nextval`(d VARCHAR(3)) RETURNS varchar(16) CHARSET utf8
DETERMINISTIC
BEGIN
DECLARE value INTEGER;
SET value = currval(d);
UPDATE `sequence`
SET `current_value` = `current_value` + 1
WHERE `district_cd` = d AND `apply_date` = current_date();
RETURN concat(d, date_format(now(), '%Y%m%d'), LPAD(currval(d), 5, '0'));
END
;;
DELIMITER ;
Triggers of applies->a business logic,if the length of apply_no is greater than 18,it will call the function:nextval to generate a new code
DELIMITER ;;
CREATE TRIGGER `convert_long_no` BEFORE INSERT ON `applies` FOR EACH ROW BEGIN
IF ((SELECT LENGTH(NEW.apply_no)) >= 18) THEN
SET NEW.apply_no = (SELECT nextval(NEW.district_cd));
END IF;
END
;;
DELIMITER ;
My Questions:
Why did the function:nextval returns two same codes?
Why did two records appear in sequnce when update the record.

Mysql variable value is null

CREATE PROCEDURE `checkAdminAccess`
(
IN accountID INT
)
BEGIN
DECLARE staffID INT;
#DECLARE num INT;
SELECT StaffID INTO staffID
FROM staff
WHERE AccountID = accountID
AND IsAdmin = 1;
SELECT staffID;
IF (staffID IS NULL) THEN
CALL raise(1356, 'Admin access required.');
END IF;
END;
For any input I get the staffID as NULL.
For example:
call checkAdminAccess(3); #returns null
call checkAdminAccess(6); #returns null
And my data is below:
INSERT INTO `staff` (`StaffID`,`AccountID`,`RoleID`,`ManagerID`,`IsAdmin`) VALUES (1,3,1,1,0);
INSERT INTO `staff` (`StaffID`,`AccountID`,`RoleID`,`ManagerID`,`IsAdmin`) VALUES (2,6,2,1,1);
Can someone tell me how to do select a value into a variable in Mysql?
The problem is that you check the return value of a procedure, which must be null, since a procedure does not return any value. It maximum returns a resultset.
Possible solutions:
Change the procedure to function, that returns the staffid.
Add an out parameter to the procedure and use that to retrieve the staffid. The above link also describes how to do this.

Stored Procedure for Login Authentication not working MySQL

I am working Login Authentication with Stored Procedures in MySQL Database.
Below is the code, i wrote but its not working. Let me know, what is wrong.
I have below questions
How to check, whether CURSOR is empty or null
Is there any way, we write the procedure.
What I am doing..
Taking two input parameters and two parameters for ouput.
Check if the user or password is not valid, stored those values in OUT parameters
SELECT 'Invalid username and password', 'null' INTO oMessage, oUserID;
If user and password in valid, but isActive column is 0 then
SELECT 'Inactive account', 'null' INTO oMessage, oUserID;
If success,
SELECT 'Success', v_UserID INTO oMessage, oUserID;
SQL Code
DELIMITER $$
USE `acl`$$
CREATE
DEFINER = `FreeUser`#`localhost`
PROCEDURE `acl`.`checkAuthenticationTwo`(
IN iUsername VARCHAR(50),
IN iPassword VARCHAR(50),
OUT oMessage VARCHAR(50),
OUT oUserID INT
)
BEGIN
DECLARE v_isActive INT;
DECLARE v_UserID INT;
DECLARE v_count INT;
DECLARE cur1 CURSOR FOR SELECT UserID, IsActive FROM m_users WHERE (LoginName = TRIM(iUsername) OR Email = TRIM(iUsername)) AND `Password` = iPassword;
OPEN cur1;
SET v_count = (SELECT FOUND_ROWS());
IF (v_count > 0)
FETCH cur1 INTO v_UserID, v_isActive;
IF (v_isActive = 0) THEN
SELECT 'Inactive account', 'null' INTO oMessage, oUserID;
ELSE
SELECT 'Success', v_UserID INTO oMessage, oUserID;
END IF;
ELSE
SELECT 'Invalid username and password', 'null' INTO oMessage, oUserID;
END IF;
END$$
DELIMITER ;
You definitely don't need CURSORs for that; use plain simple SELECT. A more concise version of your SP might look like
DELIMITER $$
CREATE DEFINER = `FreeUser`#`localhost` PROCEDURE `acl`.`checkAuthenticationTwo`
(
IN iUsername VARCHAR(50),
IN iPassword VARCHAR(50),
OUT oMessage VARCHAR(50),
OUT oUserID INT
)
BEGIN
SELECT CASE WHEN IsActive = 0 THEN 'Inactive account' ELSE 'Success' END,
CASE WHEN IsActive = 0 THEN NULL ELSE UserID END
INTO oMessage, oUserID
FROM m_users
WHERE (LoginName = TRIM(iUsername)
OR Email = TRIM(iUsername))
AND `Password` = iPassword
LIMIT 1; -- you better protect yourself from duplicates
SET oMessage = IFNULL(oMessage, 'Invalid username and password');
END$$
DELIMITER ;
What it does it tries to select a row where username or email equals to iUsername and password equals to iPassword and outputs two values to output variables. Along the way it uses CASE to look at isActive value. If it's 0 then sets a message to 'Inactive' and NULL for userid. Otherwise it returns 'Success' message and real userid that has been found. Now, if a user has not been found both variables will be set to NULL. We can leverage that and use IFNULL() function to detect that fact and set a message to 'Invalid username and password'.
Here is SQLFiddle demo
Personally I'd go further and simplify it a bit more and make it a one-statement SP with the following interface:
Returns:
userid (which is > 0) if a user with username and password if found
0 - username and(or) password incorrect
-1 - a user is inactive
The idea is that it's a presentation layer's task to produce appropriate messages for the user and not scatter all those message literals across data layer.
CREATE DEFINER = `FreeUser`#`localhost` PROCEDURE `acl`.`checkAuthenticationThree`
(
IN iUsername VARCHAR(50),
IN iPassword VARCHAR(50),
OUT oUserID INT
)
SET oUserID = IFNULL(
(
SELECT CASE WHEN IsActive = 0 THEN -1 ELSE UserID END
FROM m_users
WHERE (LoginName = TRIM(iUsername)
OR Email = TRIM(iUsername))
AND `Password` = iPassword
LIMIT 1 -- you better protect yourself from duplicates
), 0);
Here is SQLFiddle demo