WriteQueries :: UpdateByWhere
Hello, i don't know if this idea has been already discussed, but i think that could be helpful to have an method updateByWhere.
I've done this method for me
public function updateByWhere(Where $where, Array $params) {
$entities = $this->findWhere($where);
foreach ($entities as $entity) {
foreach ($params as $key => $value) {
$entity->$key = $value;
}
$this->updateOne($entity, array_keys($params));
}
return $entities;
}
Hello @Pierozi
There have been a lot of discussions about this method and the solution you present is one of the reasons there are no such method :smile:
In your method, all the relevant records are fetched from the database and updated one by one, this is a typical object oriented instance in mind approach resulting in a nested loop. This is a design flaw issuing as many requests as rows to be updated.The relational solution of the problem would be something like:
UPDATE {relation} set {field} = $* WHERE {where} RETURNING {projection}
But we could not find out a way to code a generic update such as field = my_func(another_field) or SET field1 = $*, field2 = $*.
The complexity of the different UPDATE cases is a reason why there is no such method … yet.
Sorry, I accidentally closed the issue. I let it open for discussion as it would be nice if we could figure out a solution for this.
Yes you right, my case have a Where with limited row returned and it's execute in worker.
But for large update, the best case should create temporary table, fill in, and then make update by sql request, this way can be done easily with the Where no ?
I do not understand why a temp table would be needed. The most complex cases can be handled by a CTE.
Right, still not the reflex to think about CTE.
first though was we need it for the complex condtion like SET foo = my_func(TMP.foo)
You can join sets in an UPDATE statement, see the from-list paragraph in the documentation.
Last time I thought about updating entities, I came up with a idea to rewrite the Where helper and let it base on a similar concept named Expression. The idea was to use updateWhere like this
/**
* @param Expression[] $expressions
* @param Where[] $conditions
*/
public function updateWhere(array $expressions, array $conditions)
An Expression could be simple a a = 1 term, but also a a = $*, a = b + 1 or a = myfunc(c). Expression should be chainable like Where is, but in a more generic way. Where would just be a specialized case of an Expression, offering AND, OR, etc. chaining methods (probably the stack engine from Where could therefor be used within Expression for chaining).
I already did some work on it some time ago, but I stopped, because I haven't had anytime anymore. If you think it is worth it, I could review how far I came...
:+1:
Something like
$expr = (new UpdateExpression('a = 1'))
->add('b = a + $*::int4', [4])
->add('c = sql_func(b -a)')
;
sprintf("… SET %s WHERE …", $expr);
// return "… SET a = 1, b = a + $*::int4, c = sql_func(b-a) WHERE …"
$expr->getValues(); // return [4];
Could even be:
$expr = (new UpdateExpression(['a' => 1, 'b' => 'a + $*::int4'], [4])
->add(['c' => 'sql_func(b-a)'])
;
$expr->getUpdatedFields(); // ['a', 'b', 'c']
$expr->getUpdatedExpressions(); // [1, 'a + $*::int4', 'sql_func(b-a)']
(string) $expr; // a = 1, b = a + $*::int4, c = sql_func(b-a)
$expr->getValues(); // [4]
Any thoughts ?
:+1:
Still, I believe that even UpdateExpression is still a specialized implementation of a more generic Expression object. It could generally introduce handling of numbered parameters like a = $1 or even named ones like a = $foo. But this is another task.
I think it is best to go with specialized first :-)
I do fully agree with you, we have to design APIs with UX in mind :construction_worker: scrutinizer will yell at us if we do not share common code between Where and UpdateExpression anyway…
I fully agree with this kind of implementation for UpdateExpression() :+1:
Function must be kept simple for simple operations:
$model->updateWhere(
['field_b' => null, 'field_d' => 'done'],
Where::create('field_a < $*::timestamp', [new \DateTime])
)
/*
UPDATE {relation}
SET
field_b = $*::bool,
field_d = $*::varchar
WHERE field_a < $*::timestamp
RETURNING {projection}
*/
More complex cases could be explicitly described. Variable escaping and type hinting should then be present:
$model->updatewhere(
new UpdateExpression(
[
'field_a' => '$*::timestamptz',
'field_b' => '$*::bool and field_c',
'field_d' => 'func_sql($*::varchar)',
],
[
new \Datetime(),
null,
'done',
]
),
Where::create(
"field_a < $*::timestamp and field_b is not null",
[new \Datetime()]
)
);
/*
UPDATE {relation}
SET
field_a = $*::timestamp,
field_b = $*::bool and field_c,
field_d = func_sql($*::varchar)
WHERE
field_a < $*::timestamp
AND field_b is not null
RETURNING {projection}
*/