Doctrine query: delete with limit - symfony

I am trying to delete only x objects with a delete query from Doctrine. And since there is no LIMIT in doctrine, we should use $query->setMaxResults($limit) instead. I am using Symfony2.
However it does not work with the following query (with or without $query->setMaxResults($limit), it delete everything instead of deleting the $limit first entities).
$limit = 10;
$query = $entityManager->createQuery(
'DELETE FROM MyProject\Bundle\MyBundle\Entity\MyEntity myEntity
WHERE myEntity.cost = 50'
)
$query->setMaxResults($limit);
$query->execute();

One solution that works is to use native SQL with Doctrine like this (instead of DQL).
$limit = 10;
$sql = 'DELETE FROM my_entity
WHERE cost = 50
LIMIT ' . $limit;
$stmt = $entityManager->getConnection()->prepare($sql);
$stmt->execute();

setMaxResults works only in some cases. Doctrine seems to ignore it if it's not managed.
check the Doctrine doc : https://www.doctrine-project.org/projects/doctrine1/en/latest/manual/dql-doctrine-query-language.html#driver-portability
If it does not work, try in native SQL, like the other solution posted.

Use a sub query so you can use setMaxResults
$qb = $this->em->getRepository(MyClass::class)->createQueryBuilder('x');
$subQb = $this->em->getRepository(MyClass::class)->createQueryBuilder('x_sub');
// We can not use "setMaxResults" on delete query so we need a sub query
$subQb
->select('x_sub.id')
// ... your where clauses
->setMaxResults(500)
;
$qb
->delete()
->andWhere($qb->expr()->in('x.id', ':ids'))
->setParameter('ids', $subQb->getQuery()->getResult())
;

Related

Symfony / Doctrine findBy

Hove to create custom Repository function who query by json field. I have params column in my database who look like this:
"params": {
"product": "stopper",
"itemIdentifier": ""
}
I want to query record by product value. In this case stopper term.
You can achieve this with a classic example :
In your repository :
For one result
public function findOneProduct($value): ?Params
{
return $this->createQueryBuilder('p')
->andWhere('p.product = :val')
->setParameter('val', $value)
->getQuery()
->getOneOrNullResult()
;
}
For multiple result
public function findParamsByProduct($value): ?Params
{
return $this->createQueryBuilder('p')
->andWhere('p.product = :val')
->setParameter('val', $value)
->orderBy(/*some field */)
->setMaxResults(/*if needed*/)
->getQuery()
->getResults()
;
}
In your controller:
$stoppers = $entityManager->getRepository(Params::class)->findParamsByProduct('stopper');
If I understood your question correctly, you have a table with a column named params. And inside this mysql column, you store JSON text.
And then you want to query that table and filter by looking into the JSON in your column.
This can be a bit tedious and was also highly discouraged in the past (prior to the JSON Type in Mysql 5.7.8).
Best practices would be to have a NoSQL DB such as MongoDB which is actual JSON stored in a collection(table).
Anyways, there is a solution for you.
Taking into account #AppyGG explained how to make a custom repository function.
First of all, we have to make a query using pure SQL.
It can be done two ways:
1.Return arrays containing your data.
$conn = $this->getEntityManager()->getConnection();
$sql = '
SELECT * FROM product p
WHERE p.price > :price
ORDER BY p.price ASC
';
$stmt = $conn->prepare($sql);
$stmt->execute(['price' => $price]);
// returns an array of arrays (i.e. a raw data set)
return $stmt->fetchAll();
2.Return hydrated Entities
use Doctrine\ORM\Query\ResultSetMappingBuilder;
$rsm = new ResultSetMappingBuilder($entityManager);
$rsm->addRootEntityFromClassMetadata('MyProject\Product', 'p');
$sql = '
SELECT * FROM product p
WHERE p.price > :price
ORDER BY p.price ASC
';
$nql = $this->_em->createNativeQuery( $sql, $rsm );
$nql->setParameter('price', $price);
//Return loaded entities
return $nql->getResult();
Now, knowing how to make make a MySQL query with doctrine, we want to select results filtered in JSON data.
I'm am referencing this beautiful stackoverflow which explains it all:
How to search JSON data in MySQL?
The easiest solution proposed in there requires at least MySQL 5.7.8
Your MySQL query would be as follow:
//With $entity->getParams() == '{"params": {"product":"stopper", "itemIdentifier":""}}'
$conn = $this->getEntityManager()->getConnection();
$sql = '
SELECT * FROM Entity e
WHERE JSON_EXTRACT(e.params, "$.params.product") = :product
';
//Or Like this if the column is of Type JSON in MySQL(Not doctrine, yes check MySQL).
$sql = '
SELECT * FROM Entity e
WHERE e.params->"$.params.product" = :product
';
$stmt = $conn->prepare($sql);
$statement->bindValue("product","stopper");
$stmt->execute();
return $statement->fetchAll();
Hope this helps!
P.S: Note that my example uses a column named 'params' with a Json containing also a named attribute 'params', this can be confusing. The intended purpose is to show how to do multiple level filtering.

Doctrine, setMaxResults, and get the number of result like setMaxResults hadnt been given

I have a usual querybuilder:
$this->createQueryBuilder('x')->select()->setMaxResults(10);
but actually I need the total number of records too. Its a paginator-thing btw. How to do it elegantly? Should I run a second COUNT query too?
Doctrine ships with a pagination tool, which I would recommend to use. And yes, it perform two or three queries (depends on flag $fetchJoinCollection).
use Doctrine\ORM\Tools\Pagination\Paginator;
$query = $this->createQueryBuilder('x')
->select()
->setFirstResult(0)
->setMaxResults(10)
->getQuery();
$paginator = new Paginator($query, $fetchJoinCollection = false);
$totalRecords = $paginator->count();
foreach ($paginator as $entry) {
$entry->getId();
}

Get total number of rows by using 'SQL_CALC_FOUND_ROWS' and Doctrine NativeQuery

I have a simple query which selects entities and uses limit statement. I am using Doctrine NativeQuery because I have FIELD() function in sql query, and I need a collection of objects as a result.
That query works.
However I need also a total number of records, so I use SQL_CALC_FOUND_ROWS in the first query. After the first gets the result I create another ResultSetMapping, another $nativeQuery, execute SELECT FOUND_ROWS() AS found_rows and I keep getting total number of '1'.
$rsm = new ResultSetMapping();
$rsm->addEntityResult('\\MyCompany\\Administration\\Domain\\Model\\Applicant\\Applicant', 'a');
$rsm->addFieldResult('a', 'first_name', 'firstName');
$rsm->addFieldResult('a', 'last_name', 'lastName');
$query = $this->em->createNativeQuery('SELECT SQL_CALC_FOUND_ROWS * FROM recruitment_applicant ORDER BY FIELD(id,5,15,8,17,2,1,16,9,7,11,6,10,12,13,14,18)', $rsm);
$result = $query->getResult(); // this result is ok
$sqlCountRows = "SELECT FOUND_ROWS() AS found_rows";
$countRowsRsm = new ResultSetMapping();
$countRowsRsm->addScalarResult('found_rows', 'foundRows');
$countRowsQuery = $this->em->createNativeQuery($sqlCountRows,$countRowsRsm);
$rowsCount = $countRowsQuery->getResult();
$total = $rowsCount[0]['foundRows']; // result is '1' when it should be '16'
I used this example.
You don't have to use native query. FIELD() is really very easy to implement as a custom DQL function:
Read DQL User Defined Functions and How to Register Custom DQL Functions on Doctrine/Symfony documentation.
FIELD() implementation:
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;
class Field extends FunctionNode
{
private $field = null;
private $values = array();
public function parse(Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$this->field = $parser->arithmeticPrimary();
while (count($this->values) < 1 || $parser->getLexer()->lookahead['type'] !== Lexer::T_CLOSE_PARENTHESIS) {
$parser->match(Lexer::T_COMMA);
$this->values[] = $parser->arithmeticPrimary();
}
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
}
public function getSql(SqlWalker $sqlWalker)
{
$values = array();
foreach ($this->values as $value) {
$values[] = $value->dispatch($sqlWalker);
}
return sprintf('FIELD(%s, %s)', $this->field->dispatch($sqlWalker), implode(', ', $values));
}
}
You won't event need a count query. However, if you'd need COUNT(*) query you can easily clone your original query and use CountWalker to create count query from select query.
I found out what might be a cause of the problem: Symfony2 profiler, queries section, shows total of 22 queries executed. My first query gets run third in a row and my second query, the one to return the number of rows gets executed 13th.
SQL_CALC_FOUND_ROWS works if SELECT FOUND_ROWS() is run immediately after the first query.

Using wildcards with Doctrine's createQuery method

Can anyone enlighten me as to why this query isn't working please? I tried alternating between single and double quotes as well.
$em = $this->getDoctrine()->getManager();
$query = $em->createQuery('SELECT t FROM AcmeBlogBundle:BlogTag t WHERE t.title LIKE \'%:title%\'')
->setParameter('title', $keyword);
Doctrine simply returns Invalid parameter number: number of bound variables does not match number of tokens.
Also, is it better to perform such a query using the createQuery method or createQueryBuilder?
PDO treats both the keyword and the % wildcards as a single token. You cannot add the wildcards next to the placeholder. You must append them to the string when you bind the params.
Also see this comment on the php docs.
Therefore you will need to do the following:
$qb = $em->createQueryBuilder();
$qb
->select('tag')
->from('AcmeBlogBundle:BlogTag', 'tag')
->where($qb->expr()->like('tag.title', ':title'))
->setParameter('title', '%' . $keyword . '%')
;
or
$query = $em->createQuery('SELECT t FROM AcmeBlogBundle:BlogTag t WHERE t.title LIKE :title');
$query->setParameter('title', '%' . $keyword . '%');
I prefer to use the query builder because it is nicer for structuring and making your statement more maintainable

Symfony2 (doctrine2) native sql insert

How to insert data in symfony2 doctrine2 on native sql?
My query
insert into propriedades (id,name,descripcion,num_lote,cod_imovel,imovel,convenio,proprietar,cpf,area_ha,perimetro,location,centro) VALUES (nextval('propriedades_id_seq'),'?','?','?','?','?','?','?','?','?','?',ST_GeomFromKML('<Polygon><outerBoundaryIs><LinearRing><coordinates>".$terra['coordinates']."</coordinates></LinearRing></outerBoundaryIs></Polygon>'),ST_Centroid(ST_GeomFromKML('<Polygon><outerBoundaryIs><LinearRing><coordinates>".$terra['coordinates']."</coordinates></LinearRing></outerBoundaryIs></Polygon>')))
You have to use $conn->insert('table', $dataArray);. See documentation
In 2020 you can do something like (example query, adapt it to your params):
$query = "
INSERT INTO `user_challenges_claimed`
SET
`season_id` = :seasonId,
`user_id` = :userId,
`interval_type` = :intervalType,
`is_claimed` = true
ON DUPLICATE KEY UPDATE
`is_claimed` = true
;
";
// set query params
$queryParams = [
'seasonId' => $seasonId,
'userId' => $userId,
'intervalType' => $intervalType,
];
// execure query and get result
$result = $this->manager->getConnection()
->executeQuery(
$query,
$queryParams
);
// clear manager entities
$this->manager->clear();
// optional - assert row has been inserted/modified
if ($result->rowCount() < 1) {
throw new ChallengeAlreadyClaimedException(
"[{$seasonId}:{$userId} - {$intervalType}]"
);
}
$this->manager is an object implementing EntityManagerInterface (ie EntityManager).
Usually you do not use what you call native sql in a symfony 2 project but the high level Doctrine ORM layer.
However there is a doctrine dbal layer which enables e.g. mysql queries instead of DQL. Here is the reference
http://symfony.com/doc/2.0/cookbook/doctrine/dbal.html

Resources