Doctrine DQL execute passing params - php

I used this DQL in Doctrine
$q->update('product')
->set('quantity','?')
->where('id=?');
$q->execute(array(20,5));
I check the server for the query and this the generated sql
UPDATE product SET quantity = '20', updated_at = '5'
WHERE (id = '2010-04-26 14:34);
So I need to know why the arguments aren't in the correct places?

I got caught by the exact same bug myself a few days ago. I believe it's caused by a bug in the Timestampable behavior. I'm guessing you have it enabled in the Product model, and Doctrine adds the updated_at field into the series of fields to update (SET) and doesn't pay attention to the fact that you have SQL parameters after that (in the where clause). This bug never comes up when doing SELECTs because Timestampable isn't involved.
The only solution I found is to supply the parameters as you build the query rather than in the execute's array parameter and Doctrine won't get confused. Like this:
$q->update('product')
->set('quantity', 20)
->where('id = ?', 5);
$q->execute();
However if you need to run the same query many times with different values, you'd be losing the performance benefits of separate prepare & execute phases. It appears this is the only solution.
Potentially better solution without performance loss:
I have not verified this, however, I would hope that bug would not surface if you used named parameters instead of the anonymous ? placeholders. Doctrine's support for named parameters is described here: http://www.doctrine-project.org/documentation/manual/1_2/en/dql-doctrine-query-language
EDIT: I have since tried the alternate approach with named parameters and unfortunately the bug remains. Doctrine gives an error saying you can't mix named and anonymous parameters in the same query. This really should have been fixed a long time ago IMO.

Related

How I can convert normal SQL query in to doctrine ORM

I just started studying Symfony 3.4 and Doctrine ORM. I need to take count of users. I tested with the normal query it is working fine. How can I convert the normal query into Doctrine ORM? Any help would be appreciated.
"SELECT count(DATE_FORMAT(application.reviewed_at, '%Y-%m-%d')) AS count, user_id FROM application WHERE user_id = 6 AND DATE(reviewed_at) = CURDATE() GROUP BY DATE(reviewed_at)";
I tried below code. It is not working.
$qb = $em->createQueryBuilder();
$entries = $qb->select("count(DATE_FORMAT(application.reviewed_at, '%Y-%m-%d')) AS count", "user_id")
->where("user_id = 6", "DATE(reviewed_at) = CURDATE()")
->from('AppBundle\Entity\Application', 'u');
->groupBy("DATE(reviewed_at)");
->getQuery()
->getResult();
Simply put, Doctrine ORM is not intended to transform SQL queries back into DQL. DQL is a query language that can be translated into multiple vendor-specific SQL variants, so this is a one-way transformation from DQL to SQL.
Thus, if you have just a SQL query, it could be a good idea to use just that, using Doctrine DBAL. Of course, you'd probably have to deal with result set mappings, but there could be no need to drag all the ORM stuff into your code.
What you're doing here is essentially building a DQL query using the Doctrine's QueryBuilder API. The FROM part is absolutely correct, but those using the DATE_FORMAT and DATE functions look wrong to me. Since DQL translates to several different SQL implementations, depending on the driver you're using, it needs a compatibility layer because functions such as DATE_FORMAT, DATE and CURDATE are not common for all the platforms.
First, take a look at the documentation for user-defined functions. For example, the DATEDIFF() function in MySQL is a good example. If it turns out that no one has already implemented support for the functions you're using, that's a good place to start, since it basically describes how to implement an extension for the Doctrine Query Language (DQL).
Second, there's a good chance that the functions you're using are already implemented in some of the third-party libraries, such as OroCRM's or Benjamin Eberlei's Doctrine extension collections.
Also, if you're selecting an aggregation or a function call result, it's always a good idea to alias it. What you could try here is use the $qb->expr() function subset to limit the DQL to a more strict grammar.
It is important to remember that Doctrine DQL is not an SQL dialect. It is a custom language that attempts to look close to SQL, but it operates with Doctrine entities and associations, not with database tables and columns. Of course at the end it transforms into SQL, but until this transformation you need to stay within DQL syntax.
One of reasons why your query did not work is an attempt to use native SQL (MySQL to be true) functions inside DQL query. DQL have no such functions and you have 2 ways to go at this point:
Rewrite your query in a way that it will be compatible with Doctrine. It may involve updates for your database scheme. In your particular case you may want to convert reviewet_at column from whatever it is now (timestamp I suppose) into date type that allows direct date comparisons. It will let you to get rid of custom MySQL date transformation functions and will make your query compatible with Doctrine
You can choose a harder path and implement all necessary custom functions directly in Doctrine. It is not a simple thing, but Doctrine is flexible enough to provide you with such ability. There is good article into Doctrine documentation about this topic if you will be interested.
Try to give a go to this:
public function select()
{
$queryBuilder = $this->createQueryBuilder('a');
$queryBuilder
->addSelect(['a.user_id', $queryBuilder->expr()->count('a.reviewed_at')])
->where('a.user_id = :idUser')
->andWhere('a.reviewed_at = :date')
->setParameters([
'idUser' => 6,
'date' => new \DateTime(),
])
->groupBy('a.reviewed_at')
;
return $queryBuilder
->getQuery()
->getResult()
;
}

Is there a way to add a LIMIT to an UPDATE query in Doctrine ORM?

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?
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
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)
}
}
}

PHP PDO ODBC unexpected empty result set

I am trying to track down a problem with using PDO via an ODBC connection to a SQL Server database where I am getting an empty result set for a known good query. I would appreciate any guidance from the community. This is part of a large system that I have been working on for about five years; it takes an XML representation of a report, generates SQL from it, runs the query, formats the result set as requested, and generates a web page for presentation. More than you probably needed to know, but I am trying to convey that I understand much of how this is supposed to work and in most cases it works reliably. But I have a customer who wanted something new, and it broke my system.
I refer to this as a known good query in the sense that I can copy and paste the query from my log file into SSMS (SQL Server console) and run it. It yields 62 rows of results. But when I run the same query through PDO, I get a PDOStatement back, no errorInfo(), no exceptions thrown, and so on. But fetchAll() returns an empty array. I was originally using query(), but it seemed safer to use prepare() and execute() in case there was something I was missing in the query. It made no difference.
I realize that there can be type conversion issues, but in the example below, the two retrieved fields are of type nvarchar(128) and nvarchar(32), respectively, which return successfully with other queries.
I should mention that the query is executed exactly once in the app, so it's not a matter of some previous execution interfering with the next one, as far as I can tell. Also, the PDO object has setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION);
Here's the PDOStatement returned by execute():
Result Set PDOStatement Object
(
[queryString] => SELECT [dbo].[Supplier].[SupplierName] AS suppliername,[dbo].[Item].[ItemLookupCode] AS itemlookupcode FROM [dbo].[Order] LEFT JOIN [dbo].[OrderEntry] ON [dbo].[Order].ID=[dbo].[OrderEntry].OrderID LEFT JOIN [dbo].[Item] ON [dbo].[Item].ID=[dbo].[OrderEntry].ItemID,[dbo].[Supplier] WHERE ([dbo].[Order].Time >= '2015-01-01 00:00:00') AND ([dbo].[Order].Time <= '2015-03-31 23:59:59') AND ([dbo].[Item].SupplierID=[dbo].[Supplier].ID) ORDER BY [dbo].[Supplier].[SupplierName]
)
It's not that complex, and other SQL queries work fine against this database. There's just something about this one that fails via PDO, but works inside SSMS.
Any ideas? Has anyone seen this behavior before? Is there some other way to see what's going on here? I have looked at several questions on this theme, but they all seemed to have something wrong that I am not doing.
PHP 5.4.22, by the way.
After breaking your query down into a format such as I am using below, I noticed that you were mixing explicit joins (LEFT JOIN, INNER JOIN, etc) and implicit joins (FROM table1, table2). This is not only considered a very bad practice, but has been known to cause unusual and unexpected query responses on occasion. So, looking at the implicit logic of your joins, I have rewritten the query as below:
SELECT
[dbo].[Supplier].[SupplierName] AS suppliername,
[dbo].[Item].[ItemLookupCode] AS itemlookupcode
FROM [dbo].[Order]
INNER JOIN [dbo].[OrderEntry]
ON [dbo].[Order].ID=[dbo].[OrderEntry].OrderID
INNER JOIN [dbo].[Item]
ON [dbo].[Item].ID=[dbo].[OrderEntry].ItemID
INNER JOIN [dbo].[Supplier]
ON [dbo].[Item].SupplierID=[dbo].[Supplier].ID
WHERE ([dbo].[Order].Time >= '2015-01-01 00:00:00')
AND ([dbo].[Order].Time <= '2015-03-31 23:59:59')
ORDER BY [dbo].[Supplier].[SupplierName]
I changed the LEFT JOINs in your query to INNER JOINs, because the [Item].SupplierID and [Supplier].ID had to match in your original query (and thus, to exist, since equals will not return a TRUE value if either or both values are NULL.) Thus, the OrderEntry value also had to exist for a valid response to return. If the row must exist for valid data to be returned, you should always use an INNER JOIN - it simplifies internal logic and can often result in faster query responses.
I realize this is an old question at this point, but a good answer is never wasted.
I don't disagree with your point. If I was hand-crafting SQL queries, they would come out much like yours.
But the context here is diffferent. In this system (http://www.calast.com/DynaCRUX.html), there is an abstraction of the database tables and their relationships, expressed in XML. And, there is an abstraction of the desired report, also in XML. The app that creates SQL has to deal with the inputs it is given. Sometimes there is enough information to generate "good" SQL like you and I would write. And sometimes there isn't, leaving the system to do the best it can. The fallback is at times pre-ANSI join syntax.
Also, I did point out that (ugly as we might think it), the generated query (1) is legal T-SQL, and (2) produced the output the customer wanted, when run inside SSMS. The problem was never in the query, as it turns out. It was just a configuration bug in my system, so I need to close this question out.
That said, I have recently rewritten the SQL generation engine to use a different approach that produces queries that are much more reasonable. That is, ones that look as you and I would write them.
Your answer is a good one, and I suspect it will help others write better queries.

Updating parameters in MySQLI

I am trying to update a row in mySql database. However I only want to update specific parameters, which are not null:
db->prepare("UPDATE table SET userFirstName = $userFirstName, userLastName = $userLastName WHERE xx = $xx");
this is what I do for now, but it may be that userFirstName does not need updating, or userLastName,... Since I have may values I need a way to say something like:
if userLastName is not "null" then update even that...
MySQL is smart enough to determine whether to update the row or not. If it sees that the value to be updated with matches the value currently in the column it will skip rewriting it, and even if it didn't it's not really something you should care about anyway. I've had quite a bunch of these optimization obsessions myself and I can tell you from experience, they don't bring you anything good.
There is a greater problem with your code. Your use of prepare is senseless. Your SQL is still vulnerable. Search for prepared statements and mysql injection.
However to answer the question as is - you should use an object as a database mapper. They make your life a whole lot easier. Basically the idea is that you have an object that represents a row of your table, then it keeps track of which properties have been modified and when you call $object->save() it knows exactly which fields to update.

Why does column alias not work in doctrine?

My script is like this:
$query = Doctrine_Query::create ()
->select('count(p.product_id) as num_a')
->from ( 'ProductComments p' )
->groupBy('p.product_id')
->having('num_a =2 ');
And the generated sql is:
SELECT COUNT(i.product_id) AS i__0 FROM productcomments i GROUP BY i.product_id HAVING num_a=2
Thus I get an error when execute the sql.
I have two questions:
why is the alias of the table 'i' instead of 'p'?
why is the 'num_a' in having clause not replaced with 'i__0',how to fixed it?
Thanks for your suggestion...
I also had a problem with setting alias.
I had to set alias and then use "ORDER BY" with that alias.
Following solution worked for me:
$myQuery->addSelect('(<my select>) AS my_alias');
$myQuery->orderBy('my_alias');
In the result query looked like "...() AS p_0 ... ORDER BY p_0".
I hope it will help someone.
1: why is the alias of the table 'i'
instead of 'p'?
2: why is the 'num_a'
in having clause not replaced with
'i__0',how to fixed it?
Both questions are simply answered: Doctrine uses it's own aliases for the query. You do no need to know these aliases since they will not affect you nor will you need to work with it.
Even though Doctrine names the alias i__0 you can access the attribute with your custom alias, e.g. $yourObject->num_a will have the proper value, namely the result of count(p.product_id).
To see the output of your query is a useful debug feature, but relying on in inside your application is non-sense since these values are only used for the internal mechanisms of Doctrine.
This would not be valid SQL.
SQL standard state that SELECT will be logically executed after having. So You need to repeat aliased code in having.
Good advice. As long as You work with SQL consuming DBs stick as closely to SQL as possible.

Categories