mysql, check exists first, or just attempt to update? - mysql

Currently, when a user clicks a notification on my site, I check it exists first before setting it as read. Is that wasteful? Would there be any issues if I simply did an update query?
Right now:
$check_note = $dbl->run("SELECT 1 FROM `user_notifications` WHERE `id` = ? AND `owner_id` = ?", array((int) $_GET['clear_note'], (int) $_SESSION['user_id']))->fetchOne();
if ($check_note)
{
// they have seen it and when they saw it
$dbl->run("UPDATE `user_notifications` SET `seen` = 1, `seen_date` = ? WHERE `id` = ?", array(core::$date, (int) $_GET['clear_note']));
}
Would it just be better to do this?
$dbl->run("UPDATE `user_notifications` SET `seen` = 1, `seen_date` = ? WHERE `id` = ? AND `owner_id` = ?", array(core::$date, (int) $_GET['clear_note'], (int) $_SESSION['user_id']));

Just do the update. If there's nothing that matches the WHERE condition, it won't do anything. The database has to do just as much work to test the condition during the UPDATE as it does in the SELECT, so you're not saving it any work, you're just doing an extra, redundant query.

Related

MySQL Error (1093)

I read a number of posts and replies about this error, but all the replies give specific answers to the specific question. Can someone explain what the issue is, and how to overcome it in general, so that I don't have to post every problem query here.
This is the code for my procedure
CREATE PROCEDURE `sp_FixEntityNames`(IN `importId` BIGINT UNSIGNED)
BEGIN
UPDATE ImportedSymbols s
JOIN ExchangeMappings m ON s.ExchangeMappingId = m.ExchangeMappingId
SET s.EntityName =
(
SELECT s1.EntityName
FROM ImportedSymbols s1
JOIN ExchangeMappings m1 ON s1.ExchangeMappingId = m1.ExchangeMappingId
WHERE
s1.ImportId = importId
AND
s1.Symbol = s.Symbol
AND
s1.Suffix = NULL
AND
s1.MarketId = NULL
AND
s1.SecurityTypeId = 1
AND
m1.NamespaceId = m.NamespaceId
)
WHERE
s.ImportId = importId
AND
(
s.Suffix != NULL
OR
s.MarketId != NULL
OR
s.SecurityTypeId != 1
);
END
The general explanation is that in MySQL, you can't modify the same table which you use in the SELECT part.
you can verify al this mysql Reference doc : http://dev.mysql.com/doc/refman/5.6/en/update.html
(four line before user comment you find )
You cannot update a table and select from the same table in a
subquery.

UPDATE multiple rows with different values in one query in MySQL

I am trying to understand how to UPDATE multiple rows with different values and I just don't get it. The solution is everywhere but to me it looks difficult to understand.
For instance, three updates into 1 query:
UPDATE table_users
SET cod_user = '622057'
, date = '12082014'
WHERE user_rol = 'student'
AND cod_office = '17389551';
UPDATE table_users
SET cod_user = '2913659'
, date = '12082014'
WHERE user_rol = 'assistant'
AND cod_office = '17389551';
UPDATE table_users
SET cod_user = '6160230'
, date = '12082014'
WHERE user_rol = 'admin'
AND cod_office = '17389551';
I read an example, but I really don't understand how to make the query. i.e:
UPDATE table_to_update
SET cod_user= IF(cod_office = '17389551','622057','2913659','6160230')
,date = IF(cod_office = '17389551','12082014')
WHERE ?? IN (??) ;
I'm not entirely clear how to do the query if there are multiple condition in the WHERE and in the IF condition..any ideas?
You can do it this way:
UPDATE table_users
SET cod_user = (case when user_role = 'student' then '622057'
when user_role = 'assistant' then '2913659'
when user_role = 'admin' then '6160230'
end),
date = '12082014'
WHERE user_role in ('student', 'assistant', 'admin') AND
cod_office = '17389551';
I don't understand your date format. Dates should be stored in the database using native date and time types.
MySQL allows a more readable way to combine multiple updates into a single query. This seems to better fit the scenario you describe, is much easier to read, and avoids those difficult-to-untangle multiple conditions.
INSERT INTO table_users (cod_user, date, user_rol, cod_office)
VALUES
('622057', '12082014', 'student', '17389551'),
('2913659', '12082014', 'assistant','17389551'),
('6160230', '12082014', 'admin', '17389551')
ON DUPLICATE KEY UPDATE
cod_user=VALUES(cod_user), date=VALUES(date)
This assumes that the user_rol, cod_office combination is a primary key. If only one of these is the primary key, then add the other field to the UPDATE list.
If neither of them is a primary key (that seems unlikely) then this approach will always create new records - probably not what is wanted.
However, this approach makes prepared statements easier to build and more concise.
UPDATE table_name
SET cod_user =
CASE
WHEN user_rol = 'student' THEN '622057'
WHEN user_rol = 'assistant' THEN '2913659'
WHEN user_rol = 'admin' THEN '6160230'
END, date = '12082014'
WHERE user_rol IN ('student','assistant','admin')
AND cod_office = '17389551';
You can use a CASE statement to handle multiple if/then scenarios:
UPDATE table_to_update
SET cod_user= CASE WHEN user_rol = 'student' THEN '622057'
WHEN user_rol = 'assistant' THEN '2913659'
WHEN user_rol = 'admin' THEN '6160230'
END
,date = '12082014'
WHERE user_rol IN ('student','assistant','admin')
AND cod_office = '17389551';
To Extend on #Trevedhek answer,
In case the update has to be done with non-unique keys, 4 queries will be need
NOTE: This is not transaction-safe
This can be done using a temp table.
Step 1: Create a temp table keys and the columns you want to update
CREATE TEMPORARY TABLE temp_table_users
(
cod_user varchar(50)
, date varchar(50)
, user_rol varchar(50)
, cod_office varchar(50)
) ENGINE=MEMORY
Step 2: Insert the values into the temp table
Step 3: Update the original table
UPDATE table_users t1
JOIN temp_table_users tt1 using(user_rol,cod_office)
SET
t1.cod_office = tt1.cod_office
t1.date = tt1.date
Step 4: Drop the temp table
In php, you use multi_query method of mysqli instance.
$sql = "SELECT COUNT(*) AS _num FROM test;
INSERT INTO test(id) VALUES (1);
SELECT COUNT(*) AS _num FROM test; ";
$mysqli->multi_query($sql);
comparing result to transaction, insert, case methods in update 30,000 raw.
Transaction: 5.5194580554962
Insert: 0.20669293403625
Case: 16.474853992462
Multi: 0.0412278175354
As you can see, multiple statements query is more efficient than the highest answer.
Just in case if you get error message like this:
PHP Warning: Error while sending SET_OPTION packet
You may need to increase the max_allowed_packet in mysql config file.
UPDATE Table1 SET col1= col2 FROM (SELECT col2, col3 FROM Table2) as newTbl WHERE col4= col3
Here col4 & col1 are in Table1. col2 & col3 are in Table2 I Am trying to update each col1 where col4 = col3 different value for each row
I did it this way:
<update id="updateSettings" parameterType="PushSettings">
<foreach collection="settings" item="setting">
UPDATE push_setting SET status = #{setting.status}
WHERE type = #{setting.type} AND user_id = #{userId};
</foreach>
</update>
where PushSettings is
public class PushSettings {
private List<PushSetting> settings;
private String userId;
}
it works fine

SQL partially works but needs a few WHERE clauses - I think

How can I change this SQL to do the following:
If user is not in table, add them. [THIS PART WORKS]
If user IS in table AND favourite_active = 0 change it to favourite_active = 1.
If user is in table AND favourite_active = 1 change it to favourite_active = 0.
I need to add in WHERE clauses I think.
$result = mysql_query("SELECT * FROM tbl_favourites WHERE (user_id = '$who' AND favourite_id = '$usernum' AND favourite_active = '1')");
// if user does not exist in favourites, add them
if(mysql_num_rows($result) == 0)
{
mysql_query("INSERT INTO tbl_favourites (user_id, favourite_id, favourite_active) VALUES ('$who', '$usernum', '1')");
echo"You have added this user as a favourite";
}
// if user does exist and favourite_active = 1 change to 0
else {
mysql_query("UPDATE tbl_favourites SET favourite_active='0' WHERE user_id='$who' AND favourite_id='$usernum'");
echo"You have removed this user as a favourite";
}
// if user does NOT exist and favourite_active = 0 change to 1
else {
mysql_query("UPDATE tbl_favourites SET favourite_active='1' WHERE user_id='$who' AND favourite_id='$usernum'");
echo"You have removed this user as a favourite";
}
You can't have two else clauses in an IF. And you can't do the UPDATE in two separate queries, because the second one will simply undo the first one. Use the following query:
UPDATE tbl_favourites SET favourite_active = NOT favourite_active WHERE user_id = '$who' and `favourite_id = '$usernum'
I'm also not sure that the first part, which you say works, really works. Your SELECT query looks specifically for favourite_active = 1. If the user already exists with favourite_active = 0, you'll try to INSERT them. Is that really what you want? Maybe you should remove AND favourite_active = '1'.
If user_id and favourite_id form a unique index on the table, you can do the entire thing in a single query:
INSERT INTO tbl_favourites (user_id, favourite_id, favourite_active)
VALUES ('$who', '$usernum', '1')
ON DUPLICATE KEY UPDATE favourite_active = NOT favourite_active

MySQL AND keyword issue

I have a query which is behaving strange...
Firstly, here is a query to get all PMs whether or not they've been read or deleted for the user ID 1:
SELECT * FROM `pms` WHERE `toid` = '1'
This returns 3 rows as expected. Next, let's see if I can get only unread messages for this user:
SELECT * FROM `pms` WHERE `toid` = '1' AND `read` = '0'
This returns 2 rows as expected. Let's see if I can get any read and unread messages which have been binned:
SELECT * FROM `pms` WHERE `toid` = '1' AND `binned` = '0'
This returns 2 rows as expected.
The query which I need to run is getting all unread and not binned messages for a specified user id. To do this, I am doing this:
SELECT * FROM `pms` WHERE `toid` = '1' AND `read` = '0' AND `binned` = '0'
However, it should be returning 1 row as I know in the database there is a message with toid as 1, read as 0 and binned as 0 but for some reason this query above is returning 0 rows...
Why is this?
UPDATE
Here is a screenshot of my table structure as seen in Sequel Pro:
Here is a screenshot of the data inside the table as seen in Sequel Pro:
As you can see there is definitely 1 record with toid as 1, read as 0 and binned as 0.
UPDATE 2
The reason these are ENUM is because I'm wishing to store a boolean value in MySQL. I do this by enforcing the column to be either a '1' or a '0' and making it default to '0' as well. If anyone has a better way of storing boolean values in MySQL then I'd love to learn.
Secondly, here is my PHP function inside of my User.class.php file which is getting the unread count using this SQL. This function is returning 0 when it should be returning 1. The $this->getUserId() is returning 1 as that is the current user I am using:
public function getUnreadCount()
{
global $database;
$sql = "SELECT * FROM `pms` WHERE `toid` = '".$this->getUserID()."' AND `read` = '0' AND 'binned' = '0'";
$query = $database->query($sql);
$count = $database->count($query);
return $count;
}
Thanks for the help so far but I still cannot work out why this isn't working. I'm using the read in the query adding backticks to prevent MySQL from using it as a keyword.
I bet its something really obvious I'm missing...
James, I think the problem might have to do with how the table was populated.
Since the "read" and "binned" columns' datatypes are ENUMs, you probably have to either set the correct default value ('0' or '1') or always provide a valid value when inserting a row into this table. In other words, you can't omit a value for either the "read" or "binned" columns when inserting a "pms"-row.
In other words, if your "pms" table is set up as follows, without defaults:
create table pms (
toid int,
`read` ENUM('0','1') ,
binned ENUM('0','1')
);
then you have to insert fully specified row-values like so:
insert into pms (toid, `read`, binned) values
(1, '0', '0'),
(1, '0', '1'),
(1, '1', '0'),
(1, '1', '1')
;
and avoid inserting sparse data like this:
insert into pms (toid) values (1);
insert into pms (toid, binned) values (1, '1');
insert into pms (toid, `read`) values (1, '1');
insert into pms (toid, `read`, binned) values (1, '1', '1');
Providing the correct default enum-value for those columns would also solve this issue:
create table pms (
toid int,
`read` ENUM('0','1') default '0',
binned ENUM('0','1') default '0'
);
I've set up a sqlfiddle to illustrate.
if your columns are integers try doing this
SELECT * FROM `pms` WHERE `toid` = 1 AND `read` = 0 AND `binned` = 0
EDIT:
it should be your columns to be integers like that in this demo.
SQLFIDDLE DEMO
or to be enum with values as strings like here
SELECT * FROM `pms`
WHERE `toid` = 1 AND `read` = '0' AND `binned` = '0'
sqllfiddle demo
Try to test if you have set your variables correctly. I suggest by testing if you get the right results when querying for just one variable.:
SELECT * FROM `pms` WHERE `toid` = '1'; -- 3;
SELECT * FROM `pms` WHERE `read` = '0'; -- 4;
SELECT * FROM `pms` WHERE `binned` = '0'; -- 4;
Classic mistakes would be that you have used integer values instead of string (ENUM) values or have substituted the zero for an null.
SQL FIDDLE DEMO
Wow haha I've just found why its not been returning the rows.
I'd mistakenly used single quotes instead of backticks in my PHP implementation of the SQL query...
So my query was actually:
$sql = "SELECT * FROM `pms` WHERE `toid` = '".$this->getUserID()."' AND `read` = '0' AND 'binned' = '0'";
When it should've been:
$sql = "SELECT * FROM `pms` WHERE `toid` = '".$this->getUserID()."' AND `read` = '0' AND `binned` = '0'";`
As you can see, near the end of the query for binned I had mistakenly used single quotes.
Can you believe it was that simple?
Just out of interest, how do you think I should be storing boolean values in MySQL?

Using mysql_affected_row to prevent race condition

Is it safe to use the following code to prevent race conditions? (key and status fields and mysql_affected_rows are used to implement locking)
$mres = mysql_query("SELECT `values`, `key`, `status`
FROM `test`
WHERE `id` = 1");
$row = mysql_fetch_array($mres);
if($row['status'] != UPDATING) {
$mres = mysql_query("UPDATE `test` SET
`status` = UPDATING,
`key` = `key` + 1
WHERE `id` = 1 AND `key` = ".$row['key']);
if($mres && mysql_affected_rows()) {
//update here safely and then...
mysql_query("UPDATE `test` SET
`status` = NOT_UPDATING,
`key` = `key` + 1
WHERE `id` = 1");
}
}
My test shows that either it is not safe or I should search for a well-hidden mistake in my code.
Table is MyISAM
You should "acquire the lock" first before you retrieve values. Otherwise they may change before you get the lock.
$mres = mysql_query("UPDATE `test` SET
`status` = 'UPDATING'
WHERE `id` = 1 AND `status` = 'NOT_UPDATING'");
if ($mres && mysql_affected_rows()) {
// got the lock
// now select and update
}
id better be a unique field in the db or things may behave very weird
I couldn't see a reason to increment key
notice I quoted the strings 'UPDATING' and 'NOT_UPDATING' in sql
in your code, you should have also checked that $row['status'] had a meaningful value(what if it was false/null?) before comparing to the php constant UPDATING
hopefully you understand enough php to know that php strings should be quoted.
You can check for GET_LOCK and RELEASE_LOCK functions in MySql to simulate row locks.
http://dev.mysql.com/doc/refman/5.1/en/miscellaneous-functions.html#function_get-lock
With this approach you don't need to update rows. Also with mysql_affected_rows() if something goes wrong you may finish with always locked row (for example if you script crash before releasing row by updating it status to NOT_UPDATING). Locks granted with GET_LOCK are released automatically when connection is terminated.