I am trying to update 3 column values in a row in mysql only if any of the 3 values is different.
Say I have a table of
x,y,z,id columns
I have currently,
Method A
update foo set x = 'x_value', y = 'y_value', z = 'z_value' where id = 'unique_id'
and ((x <> 'x_value') or (y <> 'y_value') or (z <> 'z_value'))
I don't know much about the theoretical benchmarking/architecture of mysql, and I was wondering if the statements
Method B
update foo set x ='x_value' where id = 'unique_id' and ((x <> 'x_value'));
update foo set y ='y_value' where id = 'unique_id' and ((y <> 'y_value'));
update foo set z ='z_value' where id = 'unique_id' and ((z <> 'z_value'));
is better or superior.
I realize that Method B will only do one write and 3 reads if only one column has changed, vs 3 writes and 3 reads for the Method A. I just don't know if it is more time intensive because method B requires looking up the index row 3 times.
Thanks in advance!
Based on what I've read in the comments, I agree with octern that you should simply run an update. It will use significantly less resources and based on your table engine, it will free up your table/ row lock for less time, making your table perform a lot better.
However, if you insist on doing a check before doing a write, do so through PHP. Simply do a select statement, compare the code in PHP and then update the appropriate table(s). For example:
$res = mysql_query("SELECT * FROM table1 WHERE PK_ID = '0'");
$arr = mysq_fetch_assoc($res);
$update = false;
if ($arr["field_1"] != $_POST["field_1"])
{
$update = true;
}
if ($arr["field_2"] != $_POST["field_2"])
{
$update = true;
}
if ($update)
{
mysql_query(sprintf("UPDATE table1 SET field_1 = '%s', field_2 = '%s'", $_POST["field_1"], $_POST["field_2"]));
}
if (
Method B will of course be more costly, because you do 3 different selects vs Method A's single select / update on condition.
Its pretty much a comparison of 1 statement to 3 statements. 1 will be faster as they are both update statements.
Related
This may sound silly but i am trying to figure out the best approach to run a query only once in a stats table that have all values set to 0 as a starting point and i would like your opinion, my table is by example:
Col1 - Col2 - Col3 - Col4 - Created - Modified
0 0 0 0 Datetime- Datetime
My main script have several foreach loops where i evaluate some conditions, if there is a match, i build the query and run a mysqli multiquery at the end, by example (pseudo code):
$query = '';
foreach ($array as $val) {
if ($val == $mycondition) {
$query .= "UPDATE mytable SET Col1 = Col1 + 1;";
}
}
$db = mysqliConnection();
$num = $db->query("SELECT * FROM my table WHERE id = '".$ID."' AND Col1 = 0 AND Col2 = 0 AND Col3 = 0 AND Col4 = 0");
$result = mysqli_num_rows($num);
// if 0 it means that nothing has been modified yet so i can run the query
if ($result > 0) {
$db->multi_query($query)
}
$db->close();
This is the best approach i can think of right now, i created the columns created and modified but i think they will not work for this, as the creation time will always be before the modified time, probably in milisecs (but i dont want to risk taking the path to allow the mutiquery run after max 3 sec or so, cause if the user run the page several time by mistake between 3 secs it will store wrong values) and the modified column will change as soon as the first value is updated making the next queries fail if i rely on that column as condition in the WHERE clause (even if are just milisec).
What do you think guys is the best approach or i am in the right path?
Best Regards
I would suggest to use a flag. Something like:
$queryFlag = 0;
if($queryFlag == 0) {
//Do you things here
$queryFlag = 1;
}
A user can input it's preferences to find other users.
Now based on that input, I'd like to get the top 10 best matches to the preferences.
What I thought is:
1) Create a select statement that resolves users preferences
if ($stmt = $mysqli->prepare("SELECT sex FROM ledenvoorkeuren WHERE userid = you"))
$stmt->bind_result($ownsex);
2) Create a select statement that checks all users except for yourself
if ($stmt = $mysqli->prepare("SELECT sex FROM ledenvoorkeuren WHERE userid <> you"))
$stmt->bind_result($othersex);
3) Match select statement 1 with select statement 2
while ($stmt->fetch()) {
$match = 0;
if ($ownsex == $othersex) {
$match = $match + 10;
}
// check next preference
4) Start with a variable with value 0, if preference matches -> variable + 10%
Problem is, I can do this for all members, but how can I then select the top 10???
I think I need to do this in the SQL statement, but I have no idea how...
Ofcourse this is one just one preference and a super simple version of my code, but you'll get the idea. There are like 15 preference settings.
// EDIT //
I would also like to see how much the match rating is on screen!
Well, it was a good question from the start so I upvoted it and then wasted about 1 hour to produce the following :)
Data
I have used a DB named test and table named t for our experiment here.
Below you can find a screenshot showing this table's structure (3 int columns, 1 char(1) column) and complete data
As you can see, everything is rather simple - we have a 4 columns, with id serving as primary key, and a few records (rows).
What we want to achieve
We want to be able to select a limited set of rows from this table based upon some complex criteria, involving comparison of several column's values against needed parameters.
Solution
I've decided to create a function for this. SQL statement follows:
use test;
drop function if exists calcMatch;
delimiter //
create function calcMatch (recordId int, neededQty int, neededSex char(1)) returns int
begin
declare selectedQty int;
declare selectedSex char(1);
declare matchValue int;
set matchValue = 0;
select qty, sex into selectedQty, selectedSex from t where id = recordId;
if selectedQty = neededQty then
set matchValue = matchValue + 10;
end if;
if selectedSex = neededSex then
set matchValue = matchValue + 10;
end if;
return matchValue;
end//
delimiter ;
Minor explanation
Function calculates how well one particular record matches the specified set of parameters, returning an int value as a result. The bigger the value - the better the match.
Function accepts 3 parameters:
recordId - id of the record for which we need to calculate the result(match value)
neededQty - needed quantity. if the record's qty matches it, the result will be increased
neededSex - needed sex value, if the record's sex matches it, the result will be increased
Function selects via id specified record from the table, initializes the resulting match value with 0, then makes a comparison of each required columns against needed value. In case of successful comparison the return value is increased by 10.
Live test
So, hopefully this solves your problem. Feel free to use this for your own project, add needed parameters to function and compare them against needed columns in your table.
Cheers!
Use the limit and offset in query:
SELECT sex FROM ledenvoorkeuren WHERE userid = you limit 10 offset 0
This will give the 10 users data of top most.
You can set a limit in your query like this:
SELECT sex FROM ledenvoorkeuren WHERE userid <> yourid AND sex <> yourpreferredsex limit 0, 10
Where the '0' is the offset, and the '10' your limit
More info here
you may try this
SELECT sex FROM ledenvoorkeuren WHERE userid = you limit 0, 10 order by YOUR_PREFERENCE
I have the following challenge:
a "Tasks" table:
tasksId int
listId int
taskOrder float
in case i want to move all the tasks from list 2 to list 3 i would do something like:
// pseodo code //
#lastTaskOrder = last task order in list 3
loop - {
UPDATE tasks SET taskOrder = #lastTaskOrder + 1, listId = 3 WHERE listId = 2;
#lastTaskOrder++
}
thus the taskOrder stays unique.
in case i want to move all the tasks from list 2 to the beginning of list 3 i would do something like:
// pseodo code //
#firstTaskOrder = first task order in list 3
#delta = #firstTaskOrder / #numberOfTasksToMove
UPDATE tasks SET taskOrder = #firstTaskOrder + #delta, listId = 3 WHERE listId = 2;
#firstTaskOrder = #firstTaskOrder + #delta
is it possible with mySQL + PDO + PHP?
Short answer: yes.
Longer answer involves some code. To update your list_ids and increment them based on the highest current value in the old list, I had to use a subquery with a window function:
UPDATE tasks
SET list_id = :toList,
task_order =
(SELECT MAX(task_order) from tasks where list_id = :toList)
+ t2.task_sort_order
FROM ( SELECT task_id,
row_number() OVER (PARTITION BY list_id order by task_order)
AS task_sort_order
FROM tasks ) t2
WHERE tasks.task_id = t2.task_id AND tasks.list_id = :fromList
Edit This is heavily edited from the first version. I've thrown away all the PHP in favor of just showing the SQL. I changed the column names because my version of Postgres was complaining about the camel-case names.
this proved to be easier than i thought it would be.
It took me 2 hours but i got it figured out:
SET #start=100;
SET #delta=1.5;
UPDATE tasks SET taskOrder = #start:= (#start+#delta), listId = 3
WHERE listId=2
ORDER BY taskOrder
I PDOed this query with the correct values
I wrote a product price/stock update script for Magento. I load the csv into an array and then iterate through it. The current code takes around 10 minutes to complete for 5,000 products, is there a faster way to do this? I've already bypassed Magento's API as that was extremely slow and switched to updating the database directly since its not many tables and its faster. Using timers to record the time, it takes about 10 minutes for the foreach loop and two minutes for the reindexALL
$con = mysql_connect("localhost","root","");
$selected = mysql_select_db("magento",$con);
$processes = Mage::getSingleton('index/indexer')->getProcessesCollection();
$processes->walk('setMode', array(Mage_Index_Model_Process::MODE_MANUAL));
$processes->walk('save');
foreach($all_rows as $final)
{
$sql = mysql_query("SELECT entity_id from catalog_product_entity where sku = '".$final[ITEM]."'");
if ($row = mysql_fetch_array($sql)) {
//update price
$pricenew = $final['PRICE'] + ($final['PRICE']*.30);
mysql_query("UPDATE catalog_product_entity_decimal SET value = '$pricenew' where attribute_id = 75 AND entity_id = '".$row[entity_id]."' ");
//update retail price
$retailprice = $final['RETAIL'];
mysql_query("UPDATE catalog_product_entity_decimal SET value = '$retailprice' where attribute_id = 120 AND entity_id = '".$row[entity_id]."' ");
//update stock quantity and is in stock
$stockquantity = $final['QTY'];
$stockquantity = number_format($stockquantity, 4, '.', '');
mysql_query("UPDATE cataloginventory_stock_item SET qty = '$stockquantity', SET is_in_stock = 1 where product_id = '".$row[entity_id]."' ");
}
$processes->walk('reindexAll');
$processes->walk('setMode', array(Mage_Index_Model_Process::MODE_REAL_TIME));
$processes->walk('save');
mysql_close($con);
If your table catalog_product_entity_decimal has index, that covers id (obviously it is) - then you have no other ways to speed it up. Since the slowest thing here is physical changing of the value.
Probably you can put a WHERE clause to to avoid of updating the price to the same value.
Other thoughts:
While most people look at performance optimizations for SELECT statements, UPDATE and DELETE statements are often overlooked. These can benefit from the principles of analyzing the Query Execution Plan (QEP). You can only run an EXPLAIN on a SELECT statement, however it’s possible to rewrite an UPDATE or DELETE statement to perform like a SELECT statement.
To optimize an UPDATE, look at the WHERE clause. If you are using the PRIMARY KEY, no further analysis is necessary. If you are not, it is of benefit to rewrite your UPDATE statement as a SELECT statement and obtain a QEP as previously detailed to ensure optimal indexes are used. For example:
UPDATE t
SET c1 = ‘x’, c2 = ‘y’, c3 = 100
WHERE c1 = ‘x’
AND d = CURDATE()
You can rewrite this UPDATE statement as a SELECT statement for using EXPLAIN:
EXPLAIN SELECT c1, c2, c3 FROM t WHERE c1 = ‘x’ AND d = CURDATE()
You should now apply the same principles as you would when optimizing SELECT statements.
I have a table y Which has two columns a and b
Entries are:
a b
1 2
1 3
1 4
0 5
0 2
0 4
I want to get 2,3,4 if I search column a for 1, and 5,2,4 if I search column a.
So, if I search A for something that is in A, (1) I get those rows, and if there are no entries A for given value, give me the 'Defaults' (a = '0')
Here is how I would know how to do it:
$r = mysql_query('SELECT `b` FROM `y` WHERE `a` = \'1\';');
//This gives desired results, 3 rows
$r = mysql_query('SELECT `b` FROM `y` WHERE `a` = \'2\';');
//This does not give desired results yet.
//Get the number of rows, and then get the 'defaults'
if(mysql_num_rows($r) === 0) $r = mysql_query('SELECT `b` FROM `y` WHERE `a` = 0;');
So, now that it's sufficiently explained, how do I do that in one query, and what about performance concerns?
The most used portion would be the third query, because there would only be values in a for a number IF you stray from the defaults.
I think I have it:
SELECT b FROM y where a=if(#value IN (select a from y group by a),#value,0);
It checks if #value exists in the table, if not, then it uses 0 as a default.
#value can be a php value too.
Hope it helps :)
You can try something like this. I'm not 100% sure it will work because count() is a aggregate function but its worth a shot.
SELECT b
FROM table1
WHERE a = (
SELECT
CASE count(b)
WHEN 0 THEN :default_value
ELSE :passed_value
END
FROM table1
WHERE a = :passed_value
)
What about
$rows = $db->fetchAll('select a, b FROM y WHERE a IN (2, 0) ORDER BY a DESC');
if(count($rows) > 0) {
$a = $rows[0]['a'];
$i = 0;
while($rows[$i]['a'] === $a) {
echo $rows[$i++]['b']."\n";
}
}
One query, but overhead if there are a lot of 'zero' values.
Depends if you care about the overhead...
I think Michal Kralik best answer in my opinion based on server performance. Doing subselects or stored procedures for such simple logic really is not worth it.
The only way I would improve on Michal's logic is if you are doing this query multiple times in one script. In this case I would query for the 0's first, and then run each individual query, then checking if there was any value.
Pseudo-code
// get the value for hte zero's
$zeros = $db->fetchAll('select a, b FROM y WHERE a = 0');
//checking for 1's
$ones = $db->fetchAll('select a, b FROM y WHERE a = 1');
if(empty($ones)) $ones = $zeros;
//checking for 2's
$twos = $db->fetchAll('select a, b FROM y WHERE a = 2');
if(empty($twos)) $twos = $zeros;
//checking for 3's
$threes = $db->fetchAll('select a, b FROM y WHERE a = 3');
if(empty($threes)) $threes = $zeros;
You can do all this in a single stored procedure with a single parameter.
I have to run out, but I'll try to write one up for you and add it here as soon as I get back from my errand.
I don't know why this was marked down - please educate me. It is a valid, tested stored procedure, and I answered the question. The OP didn't require that the answer be in php. ??
Here's a stored proc to do what you want that works in SQL Server. I'm not sure about MySQL.
create proc GetRealElseGetDefault (#key as int)
as
begin
-- Use this default if the correct data is not found
declare #default int
select #default = 0
-- See if the desired data exists, and if so, get it.
-- Otherwise, get defaults.
if exists (select * from TableY where a = #key)
select b from TableY where a = #key
else
select b from TableY where a = #default
end -- GetRealElseGetDefault
You would run this (in sql server) with
GetRealElseGetDefault 1
Based on a quick google search, exists is fast in MySQL. It would be especially fast is column A is indexed. If your table is large enough for you to be worried about performance, it is probably large enough to index.