In MySQL, I have a trigger:
BEGIN
IF (EXISTS(SELECT * FROM devices WHERE device_id = NEW.device_id)) THEN
SET NEW.id = NULL;
ELSE
INSERT INTO objects (object_type) VALUES ('3');
SET NEW.id = LAST_INSERT_ID();
END IF;
END
When this trigger gets a new id (from the objects table) it inserts the id into the id column of the devices table.
When I refer to it (for example with mysql_insert_id(); in PHP), its empty.
How can I return the insert id from the trigger (LAST_INSERT_ID();) to the function in PHP as the mysql_insert_id(); ?
Personally I use stored procedures.
Here is a basic example with PDO:
Code to create the Stored Procedures:
CREATE DEFINER=`user`#`localhost` PROCEDURE `InsertUser`(IN `Input_username` INT, OUT `Out_ID` INT)
LANGUAGE SQL
NOT DETERMINISTIC
CONTAINS SQL
SQL SECURITY DEFINER
COMMENT ''
BEGIN
INSERT INTO users(
username)
VALUES (
Input_username);
SET Out_ID = LAST_INSERT_ID();
SELECT Out_ID;
END
And PHP code:
$insert = "CALL InsertUser(:Input_username,
#Out_ID)";
$bdd = new PDO('mysql:host=localhost;dbname=db-name', 'user', 'password');
$stmt = $bdd->prepare($insert);
$stmt->bindParam(':Input_username', rand(), PDO::PARAM_STR); // to create random name
$stmt->execute();
$tabResultat = $stmt->fetch();
$id_user = $tabResultat['Out_ID'];
var_dump($id_user);
I hope I have helped. :)
This behaviour is by design:
If a stored procedure executes statements that change the value of LAST_INSERT_ID(), the changed value is seen by statements that follow the procedure call.
For stored functions and triggers that change the value, the value is restored when the function or trigger ends, so following statements will not see a changed value.
Workaround 1: Stored Procedures
Unfortunately this introduces a risk of inconsistencies between your table and objects, as insertions could still happen outside of this procedure (this problem could be adressed with convoluted access restrictions on the table)
Workaround 2:
Save the value in a user variable:
CREATE TRIGGER
....
BEGIN
INSERT INTO objects (object_type) VALUES ('3');
SET NEW.id = LAST_INSERT_ID();
SET #myLastInsertID = LAST_INSERT_ID();
END //
INSERT INTO your_table... -- trigger the above
SELECT #myLastInsertID; -- here is your value
Workaround 3:
Simply get the value from object ;)
INSERT INTO your_table... -- trigger the above
SELECT MAX(autoinc_column) FROM objects; -- here is your value!
Workarounds 2 and 3 should be wrapped in a transaction to ensure no-one interferes with #myLastInsertID or object during the process.
Related
It's probably something stupid but I'm unable to figure it out.The issue is that when i want to perform a check on email_token table,It's not passing.I have a simple query to update the needed data.But every time i get a false on my query.
Here is it:
DELIMITER $$
CREATE TRIGGER email_token_used
BEFORE INSERT
ON email_token FOR EACH ROW
BEGIN
IF(EXISTS(SELECT * FROM email_token WHERE used = 'N' AND usable = 'Y' AND user_id = NEW.user_id)) THEN
UPDATE email_token SET usable='N' WHERE user_id = NEW.user_id;
END IF;
END$$
Here is my table:
Here is the php if needed.It's working without the trigger ,the mail can't be sent since it's not updated in the table:
if(!password_verify($password,$output['password'])){
self::$status = 401;
echo "Pass 1";
}else{
$mail_result = self::$db->query("INSERT INTO email_token(user_id,token,created,expires,used) VALUES('{$output['user_id']}','{$token}',NOW(),DATE_ADD(NOW(), INTERVAL 30 MINUTE),'N')");
if($mail_result){
echo "Pass 2";
$mailer = new Mailer();
$mailer -> send_mail("Token authentication","Token authentication <a href='http://localhost:8888/scripts/functions/collector/login/login_step_2.php?user_id={$token}'>Link</a>",$output['email']);
}else{
self::$status = 409;
echo "Error 1";
}
}
Explanation of the final goal here.
The user logs in,he/she gets a token that is being sent to their email(like a 2 stop verification system).After they get the token,they finish with the login in process.I want to prevent people from having multiple tokens,and if they login several times ,they will get the token on their email.The tokens last for 2min,and i want to update the tokens before to be not valid.
Thanks...
You have run afoul of
A stored function or trigger cannot modify a table that is already
being used (for reading or writing) by the statement that invoked the
function or trigger.
https://dev.mysql.com/doc/refman/5.7/en/stored-program-restrictions.html
I am not sure why you need a trigger here in the first place. It seems to be that you have a situation where INSERT ON DUPLICATE KEY UDPATE can be used. Something like
INSERT INTO email_token(user_id,token,created,expires,used)
VALUES(?,?,NOW(),DATE_ADD(NOW(), INTERVAL 30 MINUTE),'N')
ON DUPLICATE KEY UPDATE usable = 'Y'
Note that you are using string concatenation in your queries. You should be using prepared statements instead.
Based on your comments. It sounds like all you need is a simple INSERT statement (The record needs to be inserted every time). Preceded by an UPDATE statement
UPDATE email_token SET usable='N' WHERE user_id = ?
INSERT INTO email_token(user_id,token,created,expires,used)
VALUES(?,?,NOW(),DATE_ADD(NOW(), INTERVAL 30 MINUTE),'N')
First one invalidates existing records. Second one creates a new record. Still no need for a trigger.
Try it like this:
DELIMITER $$
CREATE TRIGGER email_token_used
BEFORE INSERT
ON email_token FOR EACH ROW
BEGIN
IF(EXISTS(SELECT * FROM email_token WHERE used = 'N' AND usable = 'Y' AND user_id = NEW.user_id)) THEN
BEGIN
UPDATE email_token SET usable='N' WHERE user_id = NEW.user_id;
END;
END IF;
END$$
I wrote a stored procedure (mysql)
CREATE PROCEDURE `set_SiteAttendance`(IN _UserName VARCHAR(20) CHARSET utf8, IN _EventDesc VARCHAR(250) CHARSET utf8, IN _EventTime BIGINT(20))
BEGIN
DECLARE _EventID INT;
DECLARE cursorGetID CURSOR FOR
SELECT id
FROM client_pages
WHERE name = _EventDesc
LIMIT 0, 1;
-- try to add a new record to the database
INSERT INTO client_pages (id, name) SELECT (IFNULL(MAX(id), 0) + 1), _EventDesc FROM client_pages;
-- get id from the database records
OPEN cursorGetID;
FETCH cursorGetID INTO _EventID;
-- set the data on the visit of the page in the database
INSERT INTO login_history VALUES (NULL, _UserName, _EventID, _EventTime);
END
When I call it using MySQL Workbench, it works correctly.
CALL set_SiteAttendance('MyName', 'page#1', 100);
When I call it using php, then the stored procedure aborts on the INSERT statement:
$query = "CALL set_SiteAttendance('$user_name', '$user_page', $user_ticks)";
mysql_query($query);
mysql_error() call issues
Duplicate entry 'page#1' for key 'name'
why it happens and how to fix?
Well, that's pretty obvious. You have a unique index on name, so your query INSERT INTO client_pages (id, name) SELECT (IFNULL(MAX(id), 0) + 1), _EventDesc FROM client_pages; can only be run once with same _EventDesc parameter.
Remove the index or change the logic of your procedure.
I decided my problem:
INSERT IGNORE INTO client_pages (id, name) SELECT (IFNULL(MAX(id), 0) + 1), _EventDesc FROM client_pages;
Error (name was found) was ignored and procedure is not interrupted
I am using PostgreSQL 9.1.11.
I need to return result of SELECT to my php script. The invocation in php is like this:
$res = $pdb->getAssoc("SELECT * FROM my_profile();");
The class code to illustrate what is going on in php
public function getAssoc($in_query) {
$res = pg_query($this->_Link, $in_query);
if($res == FALSE) {
return array("dberror", iconv("utf-8", "windows-1251", pg_last_error($this->_Link)));
}
return pg_fetch_all($res);
}
Next comes my function in Postgres. I fully re-create database by dropping in a script when I update any function. (The project is in the early stage of development.) I have little to no experience doing stored procedures.
I get this error:
structure of query does not match function result type
CONTEXT: PL/pgSQL function "my_profile" line 3 at RETURN QUERY )
Trying to write:
CREATE FUNCTION my_profile()
RETURNS TABLE (_nick text, _email text) AS $$
BEGIN
RETURN QUERY SELECT (nick, email) FROM my_users WHERE id = 1;
END;
$$
LANGUAGE 'plpgsql' SECURITY DEFINER;
Table structure is:
CREATE TABLE my_users(
id integer NOT NULL,
nick text,
email text,
pwd_salt varchar(32),
pwd_hash character(128),
CONSTRAINT users_pk PRIMARY KEY (id)
);
When I return 1 column in a table the query works. Tried to rewrite procedure in LANGUAGE sql instead of plpgsql with some success, but I want to stick to plpgsql.
The Postgres 9.1.11, php-fpm I am using is latest for fully updated amd64 Debian wheezy.
What I want to do is to return a recordset containing from 0 to n rows from proc to php in an associative array.
This part is incorrect:
RETURN QUERY SELECT (nick, email) FROM my_users WHERE id = 1;
You should remove the parentheses around nick,email otherwise they form a unique column with a ROW type.
This is why it doesn't match the result type.
#Daniel already pointed out your immediate problem (incorrect use of parentheses). But there is more:
Never quote the language name plpgsql in this context. It's an identifier, not a string literal. It's tolerated for now since it's a wide-spread anti-pattern. But it may be considered a syntax error in future releases.
The SECURITY DEFINER clause should be accompanied by a local setting for search_path. Be sure to read the according chapter in the manual.
Everything put together, it could look like this:
CREATE FUNCTION my_profile()
RETURNS TABLE (nick text, email text) AS
$func$
BEGIN
RETURN QUERY
SELECT m.nick, m.email FROM my_users m WHERE m.id = 1;
END
$func$
LANGUAGE plpgsql SECURITY DEFINER SET search_path = public, pg_temp;
Replace public whit the actual schema of your table.
To avoid possible naming conflicts between OUT parameters in RETURNS TABLE ... and table columns in the SELECT statement I table-qualified column names with the given alias m.
I'm trying to create an SQL procedure where is returns a specified value from the members table.
DELIMITER $$
DROP PROCEDURE IF EXISTS `qrgdev`.`ConfirmMember` $$
CREATE PROCEDURE `qrgdev`.`ConfirmMember` (
check_Username varchar(45),
check_Password varchar(255))
BEGIN
DECLARE intcheckId INTEGER(1);
DECLARE intAccessLevel INTEGER(1) DEFAULT 0;
SELECT id INTO intCheckId FROM members WHERE Username=Check_Username;
IF (intCheckId=0)
SELECT AccessLevel INTO intAccessLevel FROM members WHERE passwrd=check_Password;
ELSE
IF (intCheckId>0) THEN
Update members
Set LastSignIn=CURRENT_TIMESTAMP
Where ID=intCheckId;
SELECT AccessLevel INTO intAccessLevel FROM members WHERE id=intCheckId;
ELSE
Insert into members
(ID, Username, Administrator, Passwrd, LastSignIn)
values
(null, check_Username, 0, null, CURRENT_TIMESTAMP);
END IF;
END IF;
RETURN(intAccessLevel);
END $$
DELIMITER ;
so that i can have php set conditions based on that value
//...
$result = $mysqli->query("Call ConfirmMember($username,$password)");
//...
this started as a function before i learned that php cant call sql functions, its why it still hols return at the bottom and also get the point i want to return the value.
create an SQL procedure where is returns a specified value
There's your biggest problem. Procedures do not return values. Functions return values. OTOH you can pass references to variables as arguments in both procedures and functions - and the procedure/function can change the value.
this started as a function before i learned that php cant call sql functions
Who told you that? It's complete nonsense.
$result = $mysqli->query("Call ConfirmMember($username,$password)");
I assume you've escaped and quoted those variables.
Change PROCEDURE to FUNCTION then invoke it as....
SELECT ConfirmMember($username,$password)
I have a stored procedure in MySQL that should update a column in a table. When I run
CALL recalculate_city_ids();
from a MySQL prompt, the correct number of rows are updated (a few hundred). When I run the command from PHP, only a single row is updated and I get no error.
Here's the PHP:
$con = mysqli_connect('localhost', 'user', 'pass', 'dbname' );
$result = $con->query( 'call recalculate_city_ids()' );
mysql_close($con);
And the SQL for the sproc:
DROP PROCEDURE IF EXISTS recalculate_city_ids;
DELIMITER $$
CREATE PROCEDURE recalculate_city_ids()
READS SQL DATA
BEGIN
DECLARE o_id INT DEFAULT 0;
DECLARE o_latitude FLOAT;
DECLARE o_longitude FLOAT;
DECLARE done INT DEFAULT 0;
DECLARE cur_users CURSOR FOR SELECT id, latitude, longitude FROM user WHERE latitude IS NOT NULL ORDER BY fname;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done=1;
OPEN cur_users;
users: LOOP
FETCH cur_users INTO o_id, o_latitude, o_longitude;
IF done=1 THEN
LEAVE users;
END IF;
SELECT #closest_city_distance:=fn_distance_cosine(o_latitude, o_longitude, latitude, longitude) AS distance, #closest_city_id:=id AS id FROM category WHERE zone="city" AND active=1 ORDER BY distance LIMIT 1;
UPDATE user SET city_id = IF(#closest_city_distance<=30, #closest_city_id, 0) WHERE id=o_id;
END LOOP users;
CLOSE cur_users;
END
$$
I can run other queries from PHP using mysqli (also tried the mysql object). I'm also unable to create stored procedures from PHP (no error) and had to do that part from a MySQL prompt as well.
PHP and my MySQL prompt are using the same username.
I think that PHP doesn't like it when a query returns multiple result sets. I don't really need to return anything since this is just a glorified UPDATE statement, so I changed my
SELECT #closest_city_distance:=fn_distance_cosine... query to
SELECT fn_distance_cosine(o_latitude, o_longitude, latitude, longitude) as distance,id into closest_city_distance, closest_city_id FROM category WHERE zone="city" AND active=1 ORDER BY distance LIMIT 1;
Since that was the only place a result set was getting returned, eliminating the returned sets fixed the problem.