7

I am using Doctrine 2.5.x and I am having problems with getting the LIMIT clause to work for UPDATE queries. It always updates all matched records (i.e. it seems to ignore the LIMIT clause).

setMaxResults() seems to have no effect when used together with UPDATE queries.

As a quick workaround I am using a native MySQL query but that cannot be the best solution.

I tried these examples but none are working:

Doctrine update query with LIMIT

https://recalll.co/app/?q=doctrine2%20-%20Doctrine%20update%20query%20with%20LIMIT

QueryBuilder with setMaxResults() (does not work):

$qb = $em->createQueryBuilder();

$query = $qb->update('\Task\Entity', 't')
    ->set('t.ClaimedBy', 1)
    ->where('t.Claimed IS NULL')
    ->getQuery();
$query->setMaxResults(20);

$this->log($query->getSQL());

Hope someone can help in finding a better solution than a native query. It takes away the whole benefit of the ORM.

Is it even possible to use a LIMIT clause in an UPDATE statement?

Pieter van den Ham
  • 4,381
  • 3
  • 26
  • 41
Robert
  • 176
  • 1
  • 19
  • Read [Doctrine Query Builder](http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/query-builder.html) and [Limiting the Result](http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/query-builder.html#limiting-the-result). PS: actually your post is a bit off-topic as it's sounds more like a tutorial request. – gp_sflover May 27 '17 at 11:38
  • 1
    I appreciate your feedback but i am not a noob. Have you tested your suggested links? It is not working. Its working for SELECT but not for update queries. I could post pseudo DQL or QueryBuilder code but that wouldnt change the fact that setMaxResults is not working on update queries. So i dont understand why its off topic. But thanks anyway – Robert May 27 '17 at 12:57
  • Yes, to be more comprensible and on-topic, just update you question with an example query builder on which you are stuck. Read [How to Ask a Question on StackOverflow](http://stackoverflow.com/help/how-to-ask) to be able to improve your question accordingly. – gp_sflover May 27 '17 at 13:02
  • Done. Curious if you see now why it should not work. – Robert May 27 '17 at 13:13
  • At a first look in your QueryBuilder example you forgot to `$query->execute();` the query (_but maybe you've only forgotten to copy/paste it_). For the rest it seems right. – gp_sflover May 27 '17 at 13:23
  • Yes i was not adding it. Bt as you might have seen i am printing the query and there is no LIMIT clause. As i told you, not a noob. You could test on your environment if you can execute a limited update query. I saw other people with the same problem. So would be glad if you have real input to that topic. – Robert May 27 '17 at 13:27
  • Unfortunately I can't test it now because I'm at sea :-). Can you take a look also at the symfony profiler to see if the limit clause was present in there? – gp_sflover May 27 '17 at 13:38
  • Not using it over symfony. But as you see we print the query before executing... So no LIMIT. – Robert May 27 '17 at 13:48
  • Have you tried using `setMaxResults` on the QueryBuilder instead of the Query object? – ccKep May 27 '17 at 16:31
  • Consider updating your question with a sample SQL using both UPDATE and LIMIT that works for you. I have only seen code like UPDATE ... WHERE IN (SELECT ... LIMIT 100); Is this what you are trying to do? I suspect you will need two queries. – Cerad May 27 '17 at 16:56
  • I dont understand all the confusion. Was no one ever running a similar query: – Robert May 27 '17 at 17:14
  • ** UPDATE tbl_task SET ClaimedBy = 1 WHERE ClaimedBy IS NULL LIMIT 20 ** – Robert May 27 '17 at 17:15
  • @ccKep: was trying what you suggested but has no effect. Generated SQL Query still does not include the LIMIT clause. But still found it suprising that the query and the querybuilder obejct do have the setMaxResults function. – Robert May 27 '17 at 17:18
  • @Cerad: I saw the same thing on other rosourced. just wannted an "official" feedback on that. Why should it be like that? Why is it not possible for UPDATE but for SELECT it is. I dont wanna do a roundtrip over a subquery. Using Doctrine since the early days when it was not even final but dont understand these kind of limitations. I would place a bet that 90% + of the doctrine projects use mysql and thats why these kind of things should work. – Robert May 27 '17 at 17:25
  • @Rob - Doctrine ORM was specifically designed to be cross database compatible. Many common mysql functions such as some of the date functions are not supported out of the box. I suppose you could look at setMaxResults and see if it can be tweaked for your purposes. – Cerad May 27 '17 at 17:35
  • @Cerad: I know it is meant to be cross database compatible. But LIMIT clause is available for all SQL Databases. SQL Server uses "UPDATE TOP (20)....", Oracle uses "... WHERE ROWNUM < 20" so for every Dialect there is an equivalent... Its a standard SQL functionality to limit a query and i just need a solution for doctrine ;-) i guess we need to check the QueryBuilder class for it but thats not my job. Thats the main purpose of using a component otherwise we can start with our own plain SQL queries.... – Robert May 27 '17 at 17:45
  • @Rob - Actually, I stopped using the Doctrine ORM a few years ago because of these sorts of issues. The ORM works fine for simple stuff but once you go past that, straight SQL just simply works. – Cerad May 27 '17 at 18:39
  • Have you come across [this thread](https://groups.google.com/forum/#!topic/doctrine-user/gGOhhhsPLOg) yet? – Pieter van den Ham May 27 '17 at 19:51
  • Hi @Cerad, If it is really not working then you might be correct. – Robert May 27 '17 at 21:40
  • @Pete: No but its not really a solution in there. Right? – Robert May 27 '17 at 21:41

2 Answers2

4

In short, no, because the SQL specification does not support UPDATE ... LIMIT ..., so none of the ORM trying to achieve portability should allow you to do it.


Please also have a look at MySQL Reference Manual itself stating that UPDATE ... LIMIT ... is not a standard SQL construction:

MySQL Server supports some extensions that you probably will not find in other SQL DBMSs. Be warned that if you use them, your code will not be portable to other SQL servers. In some cases, you can write code that includes MySQL extensions, but is still portable, by using comments of the following form:

  • SQL statement syntax
    • The ORDER BY and LIMIT clauses of the UPDATE and DELETE statements.

So by essence because what you are trying to achieve is not standard SQL the ORM will not have a portable way to implement it and will probably not implement it at all.


Sorry, but what you are trying to achieve is not possible through DQL, because:

Ocramius commented on Sep 2, 2014
DQL doesn't allow limit on UPDATE queries, as it is not portable.

As suggested in this issue of DoctrineBundle repository by its owner, Marco Pivetta (he also happen to be the owner of the ORM repository).

Further information, although it might needs a good link to the right ISO specification documentation that is sadly not freely available:

The ISO standard of UPDATE instruction do not allow LIMIT in an UPDATE, where SELECT is, of course, an instruction that does allow it.

As you were raising it by yourself, the purpose of an ORM is to not write pure SQL in order to have it cross DBMS compatible. If there is no possibility to make it that way, then it makes sense that the ORM does not implement it.

Also note that on other SQL variant than MYSQL, the limit is actually part of the SELECT clause:

select * from demo limit 10

Would translate in a SQL Server to

select top 10 from demo

Or in Orcale to

select * from demo WHERE rownum = 1

Also see: https://stackoverflow.com/a/1063937/2123530

Community
  • 1
  • 1
β.εηοιτ.βε
  • 33,893
  • 13
  • 69
  • 83
  • Thanks for your link. Saw that one as well. Still there is no explanation or official feedback that this is not supported. The question why "limit" should be available on select but not update remains open to me. Of course a lot more people need the select limit and the ORM would be useless without. But its also without update limit its kind of useless. – Robert May 28 '17 at 13:54
  • @Rob well to my understanding, even short, that answer from the owner of the ORM is crystal clear. I updated my answer to reflect at least my understanding of it. – β.εηοιτ.βε May 28 '17 at 16:47
  • Thanks for your very detailed explaination. It seems that its really not supported. Will need to execute native queries then. Dont like that approach but using a select and then update them one by one is not a real solution for me. Thanks for your support. – Robert May 29 '17 at 14:11
1

As b.enoit.be already stated in his answer, this is not possible in Doctrine because using LIMIT's in an UPDATE statement is not portable (only valid in MySQL).

Hope someone can help in finding a better solution than a native query. It takes away the whole benefit of the ORM.

I would argue that you are mixing business rules with persistence (and the ORM does not play well with that, luckily).

Let me explain: Updating an entity's state is not necessarily a business rule. Updating max. 20 entities is (where does that 20 come from?).

In order to fix this, you should properly separate your business rules and persistence by separating it into a service.

class TaskService
{

    private $taskRepository;

    public function __construct(TaskRepository $taskRepository)
    {
        $this->taskRepository = $taskRepository;
    }

    public function updateClaimedBy()
    {
        $criteria = ['Claimed' => null];
        $orderBy = null;

        // Only update the first 20 because XYZ
        $limit = 20;

        $tasks = $taskRepository->findBy($criteria, $orderBy, $limit);

        foreach($tasks as $task) {
            $task->setClaimedBy(1)
        }
    }

}
Pieter van den Ham
  • 4,381
  • 3
  • 26
  • 41
  • 1
    Nicely trying to understand the underlaying issue. That said, when I see the code like you are presenting it, that 20 number screams magic number to be now and I feel it need a `const` or to be a param injected in the service. – β.εηοιτ.βε May 28 '17 at 00:25
  • @b.enoit.be you are absolutely correct! However I won't add it into the answer for simplicity's sake. But, that would be the correct approach, yes. – Pieter van den Ham May 28 '17 at 00:35
  • @Pete i appreciate your work but for me its off topic. Was never a matter of business rules or persistence. I have a function in the repository which accepts a limit parameter. I know that your solution works but its a solution which "costs" much more execution time. So its not a solution for me. The question was regarding the UPDATE LIMIT. For SELECT queries this "unportable" feature was implemented so your statement makes no sense to me. And i dont see why i am mixing business rules here... So please get out of this thread when you cant help on this topic. Thanks – Robert May 28 '17 at 13:49
  • @Rob "For SELECT queries this "unportable" feature was implemented so your statement makes no sense to me" Maybe because LIMIT in SELECT queries _is_ portable? This sentence makes no sense because it compares apples to oranges. "So please get out of this thread when you cant help on this topic" I find this rather rude when I am devoting my free time to "your" thread. – Pieter van den Ham May 28 '17 at 13:57
  • Yes it was implemented even if you couldnt run the resulting query on Oracle or MSSQL... So you dont get the whole story. Here the setMaxResult is aware of the dbms. You ever worked with Oracle or MSSQL? There are other solutions for the limit. I would argue that an abastraction layer like doctrine should be capable of handling these "edge cases" – Robert May 28 '17 at 14:04