I have a field in an entity in string type, I need order result in my dql to value integer
I have a field in an entity in string type, I need the result sorted by that field but converted in an integer.
Some like this (MySQL Query):
SELECT * FROM table1 ORDER BY CONVERT(code, UNSIGNED);
How do I create this query in doctrine?
UPDATE
I managed to do it with the cast function thanks to this post:
CASTING attributes for Ordering on a Doctrine2 DQL Query
I have created my own function to implement this feature.
Official doc in doctrine:
http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/dql-doctrine-query-language.html#adding-your-own-functions-to-the-dql-language
AFAIK You can't do it directly . Doctrine don't support native mysql functions (convert ,day,month etc).
Idea od doctrine is to be able to talk with many different databases - and it's why there isn't any native functions.
but
you can do it on your own.
Some years ago i needed data functions (day/month etc ) in doctrine so i manage to add it do doctrine .
look here :
https://github.com/poznet/SF2Core/blob/master/src/Poznet/CoreBundle/Dql/Year.php
or
https://github.com/beberlei/DoctrineExtensions
seen solutions for conver too , but never tested it , look here
https://gist.github.com/liverbool/6345800
<?php
class ConvertUsing extends FunctionNode
{
public $field;
public $using;
public $charset;
/**
* #override
*/
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
{
return sprintf('CONVERT(%s USING %s)',
$sqlWalker->walkArithmeticPrimary($this->field),
//$sqlWalker->walkSimpleArithmeticExpression($this->using), // or remove USING and uncomment this
$sqlWalker->walkSimpleArithmeticExpression($this->charset)
);
}
/**
* #override
*/
public function parse(\Doctrine\ORM\Query\Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$this->field = $parser->ArithmeticPrimary();
// adopt use bypass validate variable of parse by using AliasResultVariable ...!!
$this->using = $parser->AliasResultVariable();
$this->charset = $parser->AliasResultVariable();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
}
}
If you only want to sort your query you can sort the code even if it's a string like this.
SELECT * FROM table1 ORDER BY code ASC; <- for ascending
SELECT * FROM table1 ORDER BY code DESC; <- for descending
If you want to convert it to integer try this
SELECT * FROM table1 ORDER BY (SELECT CONVERT(int, code));
Just make sure your codes are all numbers so you will not get an error.
I hope it helps.
Related
How to get count of rows in database by id?
SELECT count(*) FROM members;
Without performance issues. What are ways to write this query using entityManager?
I am using php version 5.6 and symfony 3
You have to use your EntityRepository
Add a function in it and write something like this:
$queryBuilder = $this->_em->createQueryBuilder()
->select('COUNT(e)')
->from('AppBundle:Entity', 'e');
return $queryBuilder->getQuery()->getSingleScalarResult();
Edit: Just saw Gregoire's answer. That will work. However, if you already have the Entity which has the relation, and it's initialized, the below would allow you to get this info without an additional query to the DB.
You could use the association and get it from the Collection (see Working with Associations in the docs
class SomeEntity
{
/**
* #var Collection|Member[]
*/
protected $members;
// other properties
// PHP >= 7 -> public function countMembers(): int
public function countMembers()
{
return $this->getMembers()->count();
}
// PHP >= 7 -> public function getMembers(): Collection
public function getMembers()
{
return $this->members;
}
// other getters/setters
}
Good morning, as seen in the image below, I have some tables linked.
Using Doctrine (in Symfony2) I'm trying to get an array of Objects Issue which itself contains all IssueMessages and IssueStatusChanged objects but can not.
I have no idea how I can do to join two tables (IssueMessage and IssueStatusChanged) to through their identifiers.
The most we've done is get all Issue with an account of the messages that have:
$dql = 'SELECT x, COUNT(im.id) FROM PanelBundle:Issue x LEFT JOIN PanelBundle:IssueMessages im WITH x.id = im.idIssue';
Does anyone could give me a hand?
THANKS!
You want to use assication mapping; this will have Doctrine manage all the joins for you.
Once in place, $issue will always have the other associated models available automatically without you having to worry about joins.
For the example below (assuming you use annotation), to get messages for an issue just get the issue objects and then use $issue->getMessages();.
<?php
/** #Entity */
class issue
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
// ...
/**
* #OneToMany(targetEntity="issueMessages", mappedBy="issue")
*/
private $messages;
// ...
public function __construct()
{
$this->messages = new Doctrine\Common\Collections\ArrayCollection();
}
}
/** #Entity */
class issueMessages
{
// ...
/**
* #ManyToOne(targetEntity="issue", inversedBy="messages")
* #JoinColumn(name="issue_id", referencedColumnName="id")
*/
private $issue;
// ...
}
If you using yml format for schema orm files than
first you need to write schema and mention oneToMany, manyToOne relationship with table fields & generate entity, repository class.
Than you can use join with two or more tables as below example:
Example of repository class file function:
----------------------------------------------------
public function getReportInfo($idUserDetail)
{
$query = $this->createQueryBuilder('UR')
->select("UR.report_period_start_date, UR.report_period_end_date")
->leftJoin('UR.UserReportDetail', 'URD')
->andWhere('UR.id_user_detail = :id')
->setParameter('id', $id)
->orderBy('UR.report_year', 'DESC')
->addOrderBy('UR.report_month', 'DESC')
->setMaxResults(1);
$resultArray = $query->getQuery()->getArrayResult();
return $resultArray;
}
You can call this function from controller action as below:
-------------------------------------------------------------
public function getUserDetailAction($idUserDetail)
{
$em = $this->getDoctrine()->getManager();
$userDetail = $em->getRepository(
'DemoBundle:UserDetail')
->getReportInfo($idUserDetail);
return $userDetail;
}
I hope this would be useful to you.
I think the problem reside in the DQL syntax (+ missing inverse relation?).
By writing this:
SELECT x, COUNT(im.id) FROM PanelBundle:Issue x
LEFT JOIN PanelBundle:IssueMessages im WITH x.id = im.idIssue
you are joining two "random" table based on the condition provided in the WITH clause. This should usually be ok, but it may confuse the Hydrator component.
In your case you should configure the OneToMany side of the relation in Issue entity, then write something like this:
SELECT x, COUNT(im.id) FROM PanelBundle:Issue x
LEFT JOIN x.issueMessages im
Hope it helps!
Basic question How can I fetch the 'type' column as integer value from inside the table mapper?
I have a PHP Zend Framework 1.12 application running a website. Inside MySQL are multiple tables with multiple columns.
Inside two tables I use the SET type. The column is named 'type' and as 'set('LOCAL','EXTERNAL')'. Don't mix up this field type with the ENUM please!
So far no problems, querying the table and fetching the type column as INT or STRING is not a problem:
$Sql = $Db->select()->from('tablename', ['type_as_int' => new \Zend_Db_Expr('type+0')]); //returns INT (if both are selected: 3)
$Sql = $Db->select()->from('tablename', ['type']); //returns STRING (if both are selected: LOCAL,EXTERNAL)
But, in this application also has table mappers that extend Zend_Db_Table_Abstract.
Inside the mapper resides the 'find()' method. Default built in into the abstract to find records by their primary key.
But.. When I use the object to fetch a record , I find the following response inside my populate method:
array([type] => LOCAL,EXTERNAL)
Querying it by hand (and defining the columns myself) would be an options ($this->select()->from...), but isn't there a more elegant way?
(I know that I am using an older version of ZF, but upgrading would cost too much time at this moment.)
After the bounty was started I noticed that there wasn't a really simple anwer, so I began looking deeper into Zend Framework 1.12 and the mapper objects that I use.
I noticed that the 'find()' method just uses the primary key columns to build a query.
So starting with that knowledge I built my own 'find()' method which resides in the 'abstract model mapper' and uses the 'find()' mapper inside the class that extends \Zend_Db_Table_Abstract
/* sample abstract mapper class with find */
abstract class MapperAbstract {
/*
* Zend db table abstract object
* #var \Zend_Db_Table_Abstract
*/
private $DbTable;
public function find($id, $Model) {
$Select = $this->$DbTable->select(\Zend_Db_Table_Abstract::SELECT_WITH_FROM_PART);
//Fetch record and populate model if we got
//a result
$Row = $this->$DbTable->fetchRow($Select);
//do some extra shizzle
if ($Row !== null) {
return $Model->populate((array)$Row);
}
return;
}
}
Now I need to add a method that overrides the default columns.
So I created a method called 'overrideColumns()' that return an array filled with column names that need to be selected or must be overriden.
/**
* Returns array with columns that need to be overridden
* or selected as extra
* #return array
*/
public function overrideColumns() {
return ['type' => new \Zend_Db_Expr('type+0')];
}
And from that point I only needed to adjust the $Select query so it would use the 'overrideColumns()' method.
So the full class becomes something like:
/* sample abstract mapper class with find */
abstract class MapperAbstract {
/*
* Zend db table abstract object
* #var \Zend_Db_Table_Abstract
*/
private $DbTable;
/**
* Returns array with columns that need to be overridden
* or selected as extra
* #return array
*/
private function overrideColumns() {
return ['type' => new \Zend_Db_Expr('type+0')];
}
public function find($id, $Model) {
$Select = $this->DbTable->select(\Zend_Db_Table_Abstract::SELECT_WITH_FROM_PART);
//Check if we need to override columns in the select query
$overrideColumns = $this->getOverrideColumns();
if (!empty($overrideColumns)) {
$Select->columns($overrideColumns); //overrides the columns
}
//Add where clause to the query
//I know there can also be a compound primary key, but
//I'm just ignoring that in this example
$Select->where($this->DbTable->getPrimaryKeyColumn() . ' = ?', $id);
//doing some extra shizzle
//that is not important when I want to explain stuff
//Fetch record and populate model if we got a result
$Row = $this->DbTable->fetchRow($Select);
if ($Row !== null) {
return $Model->populate((array)$Row);
}
return;
}
}
So.. after a while I found the answer I was looking for, without having to declare all columns.
I'm doing a project in Symfony 2.3 with Doctrine 2.4 using MySQL database.
I have an Entity of FieldValue (simplified):
class FieldValue
{
/**
* The ID
*
* #var integer
*/
protected $fieldValueId;
/**
* Id of associated Field entity
*
* #var integer
*/
protected $fieldId;
/**
* Id of associated user
*
* #var integer
*/
protected $userId;
/**
* The value for the Field that user provided
*
* #var string
*/
protected $userValue;
/**
* #var \MyProjectBundle\Entity\Field
*/
protected $field;
/**
* #var \MyProjectBundle\Entity\User
*/
protected $user;
The problem I have is the fact that $userValue, while it's LONGTEXT in my database, can represent either actual text value , date or number, depending in the type of the Field.
The Field can be dynamically added. After adding a one to any of the users every other user can also fill it's own value for that Field.
While querying the database I use orderBy to sort on a certain column, which also can be one of those Fields. In that case I need to sort on $userValue. This is problematic when I need to have number fields sorted as numbers, and not as strings ('123' is less than '9' in that case...).
The solution for it (I thought) is to CAST the $sort, so I would get SQL like:
ORDER BY CAST(age AS SIGNED INTEGER) ASC
Since Doctrine does not have a built-in DQL function for that, I took the liberty of adding that to my project as INT DQL function (thanks to Jasper N. Brouwer):
class CastAsInteger extends FunctionNode
{
public $stringPrimary;
public function getSql(SqlWalker $sqlWalker)
{
return 'CAST(' . $this->stringPrimary->dispatch($sqlWalker) . ' AS SIGNED INTEGER)';
}
public function parse(Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$this->stringPrimary = $parser->StringPrimary();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
}
}
So happy with myself finding an easy solution I did that:
$sort = "INT(".$sort.")";
$queryBuilder->orderBy($sort, $dir);
which produced expected DQL:
ORDER BY INT(age) ASC
But also produced an exception:
An exception has been thrown during the rendering of a template ("[Syntax Error] line 0, col 12272: Error: Expected end of string, got '('") in MyProject...
So I've tried to find out what is going on and got into this in Doctrine\ORM\Query\Parser.php:
/**
* OrderByItem ::= (
* SimpleArithmeticExpression | SingleValuedPathExpression |
* ScalarExpression | ResultVariable
* ) ["ASC" | "DESC"]
*
* #return \Doctrine\ORM\Query\AST\OrderByItem
*/
public function OrderByItem()
{
...
}
Does that mean that there is no possibility to use DQL functions inside ORDER BY?
And if this is the case - is there any other way to achieve this?
UPDATE
I actually already have INT used in my select query, inside CASE WHEN:
if ($field->getFieldType() == 'number') {
$valueThen = "INT(".$valueThen.")";
}
$newFieldAlias = array("
(CASE
WHEN ...
THEN ".$valueThen."
ELSE ...
END
) as ".$field->getFieldKey());
Later on the $newFieldAlias is being added to the query.
Doesn't change anything...
UPDATE 2
Even when I add an extra select to the query, which will result in this DQL:
SELECT age, INT(age) as int_age
and then sort like that:
ORDER BY int_age ASC
I still don't het the correct result.
I've checked var_dump from $query->getResult(), and this is what I got:
'age' => string '57' (length=2)
'int_age' => string '57' (length=2)
Like CAST does not matter. I'm clueless...
Doctrine DQL does not accept functions as sort criteria but it does accept a "result variable". It means that you can do the following:
$q = $this->createQueryBuilder('e')
->addSelect('INT(age) as HIDDEN int_age')
->orderBy('int_age');
Doctrine 2 does not support INT by default, but you can use age+0.
$q = $this->createQueryBuilder('e')
->addSelect('age+0 as HIDDEN int_age')
->orderBy('int_age');
It is problem in your parser.php file. I have similar kind of issue and I solve this issue to replace below code in my parser file.
/**
* OrderByClause ::= "ORDER" "BY" OrderByItem {"," OrderByItem}*
*
* #return \Doctrine\ORM\Query\AST\OrderByClause
*/
public function OrderByClause()
{
$this->match(Lexer::T_ORDER);
$this->match(Lexer::T_BY);
$orderByItems = array();
$orderByItems[] = $this->OrderByItem();
while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
$this->match(Lexer::T_COMMA);
$orderByItems[] = $this->OrderByItem();
}
return new AST\OrderByClause($orderByItems);
}
Just use this:
->orderBy('u.age + 0', 'ASC');
Here is how I query my database for some words
$query = $qb->select('w')
->from('DbEntities\Entity\Word', 'w')
->where('w.indictionary = 0 AND w.frequency > 3')
->orderBy('w.frequency', 'DESC')
->getQuery()
->setMaxResults(100);
I'm using mysql and I'd like to get random rows that match the criteria, I would use order by rand() in my query.
I found this similar question which basically suggests since ORDER BY RAND is not supported in doctrine, you can randomize the primary key instead. However, this can't be done in my case because I have a search criteria and a where clause so that not every primary key will satisfy that condition.
I also found a code snippet that suggests you use the OFFSET to randomize the rows like this:
$userCount = Doctrine::getTable('User')
->createQuery()
->select('count(*)')
->fetchOne(array(), Doctrine::HYDRATE_NONE);
$user = Doctrine::getTable('User')
->createQuery()
->limit(1)
->offset(rand(0, $userCount[0] - 1))
->fetchOne();
I'm a little confused as to whether this will help me work around the lack of support for order by random in my case or not. I was not able to add offset after setMaxResult.
Any idea how this can be accomplished?
The Doctrine team is not willing to implement this feature.
There are several solutions to your problem, each having its own drawbacks:
Add a custom numeric function: see this DQL RAND() function
(might be slow if you have lots of matching rows)
Use a native query
(I personally try to avoid this solution, which I found hard to maintain)
Issue a raw SQL query first to get some IDs randomly, then use the DQL WHERE x.id IN(?) to load the associated objects, by passing the array of IDs as a parameter.
This solution involves two separate queries, but might give better performance than the first solution (other raw SQL techniques than ORDER BY RAND() exist, I won't detail them here, you'll find some good resources on this website).
Follow these steps:
Define a new class at your project as:
namespace My\Custom\Doctrine2\Function;
use Doctrine\ORM\Query\Lexer;
class Rand extends \Doctrine\ORM\Query\AST\Functions\FunctionNode
{
public function parse(\Doctrine\ORM\Query\Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
}
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
{
return 'RAND()';
}
}
Register the class config.yml:
doctrine:
orm:
dql:
numeric_functions:
Rand: My\Custom\Doctrine2\Function\Rand
Use it directly as:
$qb->addSelect('RAND() as HIDDEN rand')->orderBy('rand()'); //Missing curly brackets
In line with what Hassan Magdy Saad suggested, you can use the popular DoctrineExtensions library:
See mysql implementation here: https://github.com/beberlei/DoctrineExtensions/blob/master/src/Query/Mysql/Rand.php
# config.yml
doctrine:
orm:
dql:
numeric_functions:
rand: DoctrineExtensions\Query\Mysql\Rand
Tested in Doctrine ORM 2.6.x-dev, you can then actually do:
->orderBy('RAND()')
Or you could do this -->
$words = $em->getRepository('Entity\Word')->findAll();
shuffle($words);
Of course this would be very inefficient if you have many records so use with caution.
Why not to use repository?
<?php
namespace Project\ProductsBundle\Entity;
use Doctrine\ORM;
class ProductRepository extends ORM\EntityRepository
{
/**
* #param int $amount
* #return Product[]
*/
public function getRandomProducts($amount = 7)
{
return $this->getRandomProductsNativeQuery($amount)->getResult();
}
/**
* #param int $amount
* #return ORM\NativeQuery
*/
public function getRandomProductsNativeQuery($amount = 7)
{
# set entity name
$table = $this->getClassMetadata()
->getTableName();
# create rsm object
$rsm = new ORM\Query\ResultSetMapping();
$rsm->addEntityResult($this->getEntityName(), 'p');
$rsm->addFieldResult('p', 'id', 'id');
# make query
return $this->getEntityManager()->createNativeQuery("
SELECT p.id FROM {$table} p ORDER BY RAND() LIMIT 0, {$amount}
", $rsm);
}
}
For me, the most useful way was to create two arrays where i say order type and different properties of the Entity. For example:
$order = array_rand(array(
'DESC' => 'DESC',
'ASC' => 'ASC'
));
$column = array_rand(array(
'w.id' => 'w.id',
'w.date' => 'w.date',
'w.name' => 'w.name'
));
You could add more entries to array $column like criteria.
Afterwards, you can build your query with Doctrine adding $column and $order inside ->orderBy. For example:
$query = $qb->select('w')
->from('DbEntities\Entity\Word', 'w')
->where('w.indictionary = 0 AND w.frequency > 3')
->orderBy($column, $order)
->getQuery()
->setMaxResults(100);
This way improved the performance of my application. I hope this helps someone.
Shuffling can be done on the query (array) result, but shuffling does not pick randomly.
In order to pick randomly from an entity I prefer to do this in PHP, which might slow the random picking, but it allows me to keep control of testing what I am doing and makes eventual debugging easier.
The example below puts all IDs from the entity into an array, which I can then use to "random-treat" in php.
public function getRandomArt($nbSlotsOnPage)
{
$qbList=$this->createQueryBuilder('a');
// get all the relevant id's from the entity
$qbList ->select('a.id')
->where('a.publicate=true')
;
// $list is not a simple list of values, but an nested associative array
$list=$qbList->getQuery()->getScalarResult();
// get rid of the nested array from ScalarResult
$rawlist=array();
foreach ($list as $keyword=>$value)
{
// entity id's have to figure as keyword as array_rand() will pick only keywords - not values
$id=$value['id'];
$rawlist[$id]=null;
}
$total=min($nbSlotsOnPage,count($rawlist));
// pick only a few (i.e.$total)
$keylist=array_rand($rawlist,$total);
$qb=$this->createQueryBuilder('aw');
foreach ($keylist as $keyword=>$value)
{
$qb ->setParameter('keyword'.$keyword,$value)
->orWhere('aw.id = :keyword'.$keyword)
;
}
$result=$qb->getQuery()->getResult();
// if mixing the results is also required (could also be done by orderby rand();
shuffle($result);
return $result;
}
#Krzysztof's solution is IMHO best here, but RAND() is very slow on large queries, so i updated #Krysztof's solution to gives less "random" results, but they are still random enough. Inspired by this answer https://stackoverflow.com/a/4329492/839434.
namespace Project\ProductsBundle\Entity;
use Doctrine\ORM;
class ProductRepository extends ORM\EntityRepository
{
/**
* #param int $amount
* #return Product[]
*/
public function getRandomProducts($amount = 7)
{
return $this->getRandomProductsNativeQuery($amount)->getResult();
}
/**
* #param int $amount
* #return ORM\NativeQuery
*/
public function getRandomProductsNativeQuery($amount = 7)
{
# set entity name
$table = $this->getClassMetadata()
->getTableName();
# create rsm object
$rsm = new ORM\Query\ResultSetMapping();
$rsm->addEntityResult($this->getEntityName(), 'p');
$rsm->addFieldResult('p', 'id', 'id');
# sql query
$sql = "
SELECT * FROM {$table}
WHERE id >= FLOOR(1 + RAND()*(
SELECT MAX(id) FROM {$table})
)
LIMIT ?
";
# make query
return $this->getEntityManager()
->createNativeQuery($sql, $rsm)
->setParameter(1, $amount);
}
}
I hope this would help others:
$limit = $editForm->get('numberOfQuestions')->getData();
$sql = "Select * from question order by RAND() limit $limit";
$statement = $em->getConnection()->prepare($sql);
$statement->execute();
$questions = $statement->fetchAll();
Note here the table question is an AppBundle:Question Entity. Change the details accordingly. The number of questions is taken from the edit form, make sure to check the variable for the form builder and use accordingly.
First get the MAX value from DB table & then use this as offset in PHP i.e
$offset = mt_rand(1, $maxId)
I know this is an old question. But I used the following solution to get the random row.
Using an EntityRepository method:
public function findOneRandom()
{
$id_limits = $this->createQueryBuilder('entity')
->select('MIN(entity.id)', 'MAX(entity.id)')
->getQuery()
->getOneOrNullResult();
$random_possible_id = rand($id_limits[1], $id_limits[2]);
return $this->createQueryBuilder('entity')
->where('entity.id >= :random_id')
->setParameter('random_id', $random_possible_id)
->setMaxResults(1)
->getQuery()
->getOneOrNullResult();
}
Probably the easiest (but not necessarily the smartest) way to get a single object result ASAP would be implementing this in your Repository class:
public function findOneRandom()
{
$className = $this->getClassMetadata()->getName();
$counter = (int) $this->getEntityManager()->createQuery("SELECT COUNT(c) FROM {$className} c")->getSingleScalarResult();
return $this->getEntityManager()
->createQuery("SELECT ent FROM {$className} ent ORDER BY ent.id ASC")
->setMaxResults(1)
->setFirstResult(mt_rand(0, $counter - 1))
->getSingleResult()
;
}
Just add the following:
->orderBy('RAND()')