INSERT IGNORE INTO and UPDATE in one statement - php

I'm running a script which serves downloads to my users. I would like to monitor their traffic on a per byte level, and I hold how many bytes they've downloaded in $bytes. I want to log it to my database and I'm using the following function:
register_shutdown_function(function() {
global $bytes;
/* Save the traffic to the database */
$db = new PDO('mysql:host=localhost;dbname=test', 'root', '');
$st = $db->prepare('INSERT IGNORE INTO `stats`
SET `date` = CURDATE(), `bytes` = :bytes');
$st->bindParam(':bytes', $bytes);
$st->execute();
$st = null;
$db = null;
});
This query seems to work once, when the download is complete there's a new record in the table with the following data:
date | bytes
------------------------
2013-02-03 | 2799469
however, on every other download the bytes field doesn't change. No new records and no change to the record that's already in the table. It's pretty obvious what the problem is, the query tries to insert a record but if it already exists then it aborts. I need an update statement like this:
UPDATE `stats`
SET `bytes` = `bytes` + :bytes
WHERE `date` = CURDATE()
but I would like to do the entire operation in one query. A query that will create the record if it doesn't exist and if it does exist, update the existing record.
Can this be done or am I going to have to run two queries on every download?

You might want to look into ON DUPLICATE KEY UPDATE. You can read about it here.
Your query would look something like this.
$st = $db->prepare("INSERT INTO `STATS`
VALUES('CURDATE()', :bytes)
ON DUPLICATE KEY UPDATE `BYTES` = `BYTES` + :bytes");
You also should avoid using INSERT IGNORE INTO because in case of duplicate rows, no error will be generated, but only a warning.

Related

MySql trigger not doing any action

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$$

Lock a Select on the Postgres Database and updating the Column of "value+1" when necessary

I am updating this question, please do not mind the comments below as, instead of deleting this question, I reworked it to give it a sense.
A form on a php page let me create a csv file, to name this file I need to run a SELECT on the database, it the name does not exists, my query must create it; if the name exist, it must update it.
The problem is, there is a chance where 2 or more users can push the submit button at the same time.
This will cause the query to reurn the same value to all of them, therefore creating or updating the file in a non-controlled way.
I need to create a system, that will LOCK the table for INSERT/UPDATE and, if in the meantime another connection appear, the column on the database that will name the file must be incremented of +1.
$date = date("Ymd");
$csv = fopen("/tmp/$user_$date_$id_$reference.csv", 'w+');
Where "reference" is a progressive number in the format of "Axxxx". x's are numbers.
The SELECT would be:
$sql = pg_query($conn, " SELECT user, identification, reference, FROM orders WHERE identification = '$_POST[id_order]' ORDER BY date DESC LIMIT 1");
while ($row = pg_fetch_row($sql)) {
$user = $row[0];
$id = $row[1];
$reference = $row[2];
}
I need to create a function, like the one below, where users can both INSERT and UPDATE, and in the case of concurrent connection, the ones that are not the first will have "reference" incremented of 1.
CREATE OR REPLACE FUNCTION upsert_identification( in_identification TEXT, in_user TEXT ) RETURNS void as $$
BEGIN
UPDATE table SET identification=in_identification, user=in_user, reference=in_reference WHERE identification = in_identification;
IF FOUND THEN
RETURN;
END IF;
BEGIN
INSERT INTO table ( identification, user, reference ) VALUES (in_identification, in_user, in_reference );
EXCEPTION WHEN OTHERS THEN
-- Should the increment be here?
END;
RETURN;
END;
$$ language plpgsql;
I hope what I'm asking is clear, re-read and I do understand it. Please comment below for any question you might have.
I really hope someone can help me!
I was looking for some clues in the postgres manual, I found this link about locking but I am not so sure this is what I need: LINK

MySQL alter table change column throw error if rows change

This question might probably have been asked somewhere but I just can't seem to find it after much searching.
I have a basic question on how I can prevent the last SQL statement in my code below from executing if it caused any row/data in the table to change.
$query = 'SELECT pdt_code FROM `Products`';
$stmt = $con->query($query);
while ($obj = $stmt->fetch()) {
$pdt_code[] = $obj['pdt_code'];
}
$args = implode(',',
array_map(function($el) {
return '('.implode(',', $el).')';
},
array_chunk(array_fill(0, count($pdt_code), '?'), count($pdt_code))
)
);
$query = 'ALTER TABLE `MapProduct` CHANGE product_code product_code SET '.$args.' COLLATE utf8mb4_unicode_ci NOT NULL';
$stmt = $con->prepare($query);
$stmt->execute($pdt_code); // how do I prevent this line from executing if it causes the data in table `MapProduct` to change?
I am trying to execute this from PHP where I query another table for updates to product codes and update the SET() accordingly. However, I had a horrific experience of losing all my data because the update was not properly executed and all the data in product_code got wiped.
Although mysql's SET data type can be used to restrict list of values being entered. However, it is not flexible and requires an alter table to update the list of values. If you frequently have to do this - and if you build a functionality to do that, then you need to do this frequently - then the SET data type is simply not for you.
I would create a separate table for product types and I would simply reference this table with a foreign key from your mapproduct table. This way adding a new product type to the allowed list inly requires an insert statement.

ON DUPLICATE KEY UPDATE - Condition WHERE vs CASE WHEN vs IF?

I am refering to this post. I am stuck with a problem I can't resolve. I try to insert multiple rows with a php script into a MySQL database. I don't succeed in updating the whole thing using ON DUPLICATE KEY UPDATE and using a WHERE condition (at the end of the code below) I would like to use to update only an entry has been modified recently:
// for information (used in a foreach loop):
$args[] = '("'.$row['lastname'].'", '.$row['phone'].', "'.$row['lastModification'].'")';
// then:
$stringImplode = implode(',', $args);
// Where I am stuck - WHERE statement:
$sql = $mysqli->query('INSERT INTO table_name '. (lastname, phone, timestamp) .' VALUES '.$stringImplode .'ON DUPLICATE KEY UPDATE lastname=VALUES(lastname), phone=VALUES(phone) WHERE timestamp > VALUES(lastModification);
Everything works fine except I cannot set any WHERE condition at this point that involves multiples entries. Maybe the WHERE statement in this case is not intended to refer to a condition in this statement.
I was told to try with a database procedure using a JOIN statement and a temporary table with first all my entries and then querying some conditions. But I have to admit I don't understand very well how I could leverage such a table to update an other table.
Is there an easy and lovely way to use a "CASE WHEN" or an "IF" statement in this case?
Would something like
INSERT INTO ... ON KEY DUPLICATE UPDATE lastname = VALUES(lastname), phone = VALUES(phone)
CASE WHEN (timestamp > VALUES(lastModification)) THEN do nothing ...
or
...ON KEY DUPLICATE UPDATE... IF (timestamp > VALUES(lastModification)) ...
If anyone could help me, I would be very grateful.
EDIT: Since I will have many variables, could it be used in this way:
INSERT INTO ... ON KEY DUPLICATE UPDATE
IF(timestamp > VALUES(timestamp),
(
name = VALUES(name),
number = VALUES(number),
timestamp = VALUES(timestamp)
....many other variables
),
(
name = name,
number = number,
timestamp = timestamp
....many other variables)
)
You can use simple IF function in value like this:
INSERT INTO ... ON KEY DUPLICATE UPDATE
name = VALUES(name),
number = VALUES(number),
timestamp = IF(timestamp > VALUES(timestamp), VALUES(timestamp), timestamp)
If condition is not met, it will update timestamp with the same timestamp which already exists. It does not matter, because update to same values is optimized before it is even executed, so MySQL will not make real update. You should not afraid of some performance penalty.
EDIT:
IF works likes this:
IF(condition, returned when true, returned when false)
Maybe you need to switch those two arguments to fit your condition like you want.

joomla-php mysql not updating a record with data from previous query

I'm counting the right answers field of a table and saving that calculated value on another table. For this I'm using two queryes, first one is the count query, i retrieve the value using loadResult(). After that i'm updating another table with this value and the date/time. The problem is that in some cases the calculated value is not being saved, only the date/time.
queries look something like this:
$sql = 'SELECT count(answer)
FROM #_questionsTable
WHERE
answer = 1
AND
testId = '.$examId;
$db->setQuery($sql);
$rightAnsCount = $db->loadResult();
$sql = 'UPDATE #__testsTable
SET finish = "'.date('Y-m-d H:i:s').'", rightAns='.$rightAnsCount.'
WHERE testId = '.$examId;
$db->setQuery($sql);
$db->Query();
answer = 1 means that the question was answered ok.
I think that when the 2nd query is executed the first one has not finished yet, but everywhere i read says that it waits that the first query is finished to go to the 2nd, and i don't know how to make the 2nd query wait for the 1st one to end.
Any help will be appreciated. Thanks!
a PHP MySQL query is synchronous ie. it completes before returning - Joomla!'s database class doesn't implement any sort of asynchronous or call-back functionality.
While you are missing a ';' that wouldn't account for it working some of the time.
How is the rightAns column defined - eg. what happens when your $rightAnsCount is 0
Turn on Joomla!'s debug mode and check the SQL that's generated in out the profile section, it looks something like this
eg.
Profile Information
Application afterLoad: 0.002 seconds, 1.20 MB
Application afterInitialise: 0.078 seconds, 6.59 MB
Application afterRoute: 0.079 seconds, 6.70 MB
Application afterDispatch: 0.213 seconds, 7.87 MB
Application afterRender: 0.220 seconds, 8.07 MB
Memory Usage
8511696
8 queries logged.
SELECT *
FROM jos_session
WHERE session_id = '5cs53hoh2hqi9ccq69brditmm7'
DELETE
FROM jos_session
WHERE ( TIME < '1332089642' )
etc...
you may need to add a semicolon to the end of your sql queries
...testId = '.$examID.';';
ah, something cppl mentioned is the key I think. You may need to account for null values from your first query.
Changing this line:
$rightAnsCount = $db->loadResult();
To this might make the difference:
$rightAnsCount = ($db->loadResult()) ? $db->loadResult() : 0;
Basically setting to 0 if there is no result.
I am pretty sure you can do this in one query instead:
$sql = 'UPDATE #__testsTable
SET finish = NOW()
, rightAns = (
SELECT count(answer)
FROM #_questionsTable
WHERE
answer = 1
AND
testId = '.$examId.'
)
WHERE testId = '.$examId;
$db->setQuery($sql);
$db->Query();
You can also update all values in all rows in your table this way by slightly modifying your query, so you can do all rows in one go. Let me know if this is what you are trying to achieve and I will rewrite the example.

Categories