问题
I have an array like
Array (
[1] => 85590762,22412382,97998072
[3] => 22412382
)
Where key is the item_id and value is the value of a column which I need to update against an item. I can use db_update in a loop but i want to avoid this strategy due to performance. I want to update all the rows in a single db call. Also using db_query I think will not be a good idea. So is there any way using db_update to update these rows?
According to above data, standard mysql queries will be like
update items set sold= 1, users = '85590762,22412382,97998072' Where item_id = 1;
update items set sold = 1, users = '22412382' Where item_id = 3;
回答1:
Unfortunately you can't do that. Currently and probably in the feature, there won't be support for updating (multiple) values on different rows with different values with one db_update.
If your data is this:
$for_update = array(
1 => "85590762,22412382,97998072",
3 => "22412382",
);
You can do these:
CASE 1: Create a loop and call every time the update function:
foreach ($for_update as $key => $value) {
$fields = array('sold' => 1, 'users' => $value);
db_update('items')->fields($fields)->condition('item_id', $key, '=')->execute();
}
Pros: other modules can hook inside your query, can support multiple DB drivers
Cons: Object initialization is slow, lots of DB connection/traffic
CASE 2: It's faster if you use db_query()...
... because in that case there's no object instantiation/operation, which is a bit costly and other conversion. (i.e.: https://drupal.stackexchange.com/questions/129669/whats-faster-db-query-db-select-or-entityfieldquery)
foreach ($for_update as $key => $value) {
db_query("UPDATE items SET sold = :sold, users = :users WHERE item_id = :item_id",
array(':sold' => 1, ':users' => $value, ':item_id' => $key)
);
}
Pros: no object initialization and operation < so it's faster
Cons: other modules can't hook inside your query, your code can break under different DB drivers, lots of DB connection/traffic
CASE 3: Also you can use transaction
This can boost a bit your performance at some case.
$transaction = db_transaction();
try {
foreach ($for_update as $key => $value) {
$fields = array('sold' => 1, 'users' => $value);
db_update('items')->fields($fields)->condition('item_id', $key, '=')->execute();
}
}
catch (Exception $e) {
$transaction->rollback();
watchdog_exception('my_type', $e);
}
Note: transaction mostly used, when you want everything or nothing. But with some DB drivers, you can optimize out the COMMIT commands.
In case 1 you get something like this:
UPDATE items SET sold = 1, users = '85590762,22412382,97998072' WHERE item_id = 1;
COMMIT;
UPDATE items SET sold = 1, users = '22412382' WHERE item_id = 3;
COMMIT;
With transaction you do something like this:
UPDATE items SET sold = 1, users = '85590762,22412382,97998072' WHERE item_id = 1;
UPDATE items SET sold = 1, users = '22412382' WHERE item_id = 3;
COMMIT;
With COMMIT you write out the data to the final place (into long-term storage, until this, it's only somewhere in the memory or a temporary place). When you use rollback, you drop these changes. At least this is how I understand this. (i.e. I get performance improvement with this when I used sqlite.)
Same as case 1 or 2 +
Pros: you can rollback your data if you need to be consistent, you write out data only once to the drive < so sql only "once" do IO, lots of DB connection/traffic
CASE 4: Or you can build one sql statement for this:
$ids = array();
$when_then = "";
foreach ($for_update as $key => $value) {
$ids[] = $key;
$when_then .= "WHEN $key THEN '$value' ";
}
db_query("UPDATE items
SET sold = 1, users = CASE item_id
$when_then
ELSE users
END
WHERE item_id IN(:item_ids)", array(':item_ids' => $ids));
Probably this is the fastest way from all from here.
NOTE: $when_then
variable doesn't contain the best solution, it's better if you can use the params or you escape unsafe data.
Pros: between your server and sql there will be only one request and less redundant data, one bigger sql queries faster then a lot of small ones
Cons: other modules can't hook inside your query, your code can break under different DB drivers
Also please note, after (I think) PHP 5.3 you can't run more than one statement in db_query or in PDO by default, because of it can be an SQL Injection, so it blocks. However you can set this, but not recommended. (PDO support for multiple queries (PDO_MYSQL, PDO_MYSQLND))
13-05-2019 edit:
Just a side note, I did a few month ago some performance measurement on medium dataset to query down some data via db_select
and db_query
. The magnitude of the performance of these are: under you run one db_select
, you can run two db_query
. This also applies to db_update
and db_query
. The test queried down some columns with a simple SELECT
, no JOINS
, no fancy dandy stuff, just two condition. Of course, the measurement also contained/included the time which was required to build up these queries (at both case). However please note, the Drupal Coding Standard requires to use db_update
, db_delete
, db_merge
on modification normally, only 'allows' db_query
instead of db_select
operation.
回答2:
Using BULK UPDATE you can update multiple values on diferent rows or in the same row with the dinamic query db_update .
Note: By bulk updating only one query can be sent to the server instead of one query for each row to update.
Syntax:
UPDATE yourTableName
SET yourUpdateColumnName =
(CASE yourConditionColumnName WHEN Value1 THEN 'UpdatedValue'
WHEN Value2 THEN 'UpdatedValue'
.
.
N
END)
WHERE yourConditionColumnName IN(Value1,Value2,.....N);
More info about expressions on UpdateQuery on: https://api.drupal.org/api/drupal/includes%21database%21query.inc/function/UpdateQuery%3A%3Aexpression/7.x
With your data will be:
//Create array
$for_update = [
1 => "85590762,22412382,97998072",
3 => "22412382",
];
$item_ids_to_filter = [];
//Setup when-then conditions
$when_then = "CASE item_id ";
foreach ($for_update as $item_id => $users_id) {
$when_then .= "WHEN {$item_id} THEN '{$users_id}' ";
$item_ids_to_filter[] = $item_id;
}
$when_then .= "END";
//Execute query and update the data with db_update
db_update('items')
->expression("users",$when_then)
->condition('item_id', $item_ids_to_filter)
->execute();
As result the next query is generated:
UPDATE items
SET users =
(CASE item_id = WHEN 1 THEN '85590762,22412382,97998072'
WHEN 3 THEN '22412382'
END)
WHERE (item_id IN ('1', '3'))
回答3:
I think you could do something like
$query = db_udpate('table')->fields(array('field_1','field_2','field_3'));
foreach ($fields as $record) {
$query->values($record);
}
return $query->execute();
Where $fields = array ('field_1' => VALUE, 'field_2' => VALUE)
来源:https://stackoverflow.com/questions/25646525/drupal-7-update-multiple-rows-using-db-update