I am working on Symfony 3.4 in PHP 5.6.
Here is the context of my problem:
I have an "information" table that contains several lines. I want to sort these lines and display them according to a certain column called "Zone". So the goal is to have for example
"Zone 1"
* Lines of information corresponding to this area "
"Zone 2"
* Lines of information corresponding to this area "
...
I realized my functions in the Repository, and I also made the layout under Twig. Everything worked. All I had to do was optimize my code on the controller to make it cleaner.
My idea was therefore:
Retrieve an array containing all the existing distinct areas by a query.
Loop on this array using each value of the array as a parameter for the SQL query that retrieves the rows corresponding to the passed field and retrieve them in an array variable
Make an array_push () of the array of each zone, in another array that will contain the array of each zone.
But I can't. This is my code
Repository :
public function getInformationsZone($zone)
{
$queryBuilder = $this->createQueryBuilder("i")
->where("i.zone = :zone")
->orderBy("i.updatedAt","DESC")
->orderBy("i.criticite","DESC")
->setParameter('zone',$zone);
return $queryBuilder->getQuery()->getResult();
}
public function getZonesActives()
{
$queryBuilder = $this->createQueryBuilder("i")
->select("i.zone")
->distinct(true);
return $queryBuilder->getQuery()->getResult();
}
controller
/**
* Lists all information entities.
*
* #Route("/", name="informations_index")
* #Method("GET")
*/
public function indexAction()
{
$em = $this->getDoctrine()->getManager();
$information = $em->getRepository('PagesBundle:Information')->findAll();
$listZones = $this->getDoctrine()->getRepository('PagesBundle:Information')->getZonesActives();
$tabInfos = array();
foreach($listZones as $key=>$value)
{
$zone = $this->getDoctrine()->getRepository('PagesBundle:Information')->getInformationsZone($value);
array_push($tabInfos,$zone);
}
// This is what i did just before
/* $infosZone1 = $this->getDoctrine()->getRepository('PagesBundle:Information')->getInformationsZone("Zone 1");
$infosZone2 = $this->getDoctrine()->getRepository('PagesBundle:Information')->getInformationsZone("Zone 2");
$infosZone3 = $this->getDoctrine()->getRepository('PagesBundle:Information')->getInformationsZone("Zone 3");
$tabInfos = array($infosZone1,$infosZone2,$infosZone3);*/
return $this->render('information/index.html.twig', array(
/* 'information' => $information,
'infosZone1'=> $infosZone1,
'infosZone2'=> $infosZone2,
'infosZone3'=> $infosZone3,*/
'tabInfos'=>$tabInfos,
));
}
I've this error :
An exception occurred while executing 'SELECT i0_.id AS id_0, i0_.contenu AS contenu_1, i0_.updated_at AS updated_at_2, i0_.zone AS zone_3, i0_.titre AS titre_4, i0_.criticite AS criticite_5 FROM information i0_ WHERE i0_.zone = ? ORDER BY i0_.criticite DESC' with params ["Zone 1"]:
SQLSTATE[HY093]: Invalid parameter number: parameter was not defined
Replace:
$zone = $this->getDoctrine()->getRepository('PagesBundle:Information')->getInformationsZone($value);
With this:
$zone = $this
->getDoctrine()
->getRepository('PagesBundle:Information')
->getInformationsZone($value->getZone());
You are passing the all zone entity to the getInformationsZone method.
So to get the title of the zone, you must call the getter of the zone.
$value to $value->getZone();
Edit: So, just change $value->getZone() to $value['zone'];
I am working in a legacy system that uses row() plus limit() to get one result. I didn't understand why, because row() already give me one result, but a coworker said that improves performance. Example:
$this->db
->select()
->select('extract(epoch from cadevolucao.dt_sistema) as data_sistema')
->select('extract(epoch from cadevolucao.dt_previsao_alta) as data_previsao')
->select('cadevolucao.cd_evolucao, cadevolucao.dt_sistema')
->join('contatnd', 'cadevolucao.num_atend = contatnd.num_atend')
->join('cadplanejamento', 'cadevolucao.cd_evolucao = cadplanejamento.cd_evolucao')
->where('contatnd.cd_pessoa', $cd_pessoa)
->where('tp_evolucao', -1)
->where('tipo', 1)
->order_by('cadevolucao.cd_evolucao','desc')
->limit(3)
->get('cadevolucao')
->row();
I looked for in the CI Documentation and Google, not founding anything useful about that.
Can someone explain if it's needed the limit() when using row() in Active Record's CI and why?
According to what i know row method returns a single result row. If your query has more than one row, it returns only the first row.But internally its still fetching all the rows fetched by the query and storing it in an array. Yes i think i must agree with your co-worker indeed limit will have a performance impact.
this is what row method does internally
/**
* Returns a single result row - object version
*
* #param int $n
* #return object
*/
public function row_object($n = 0)
{
$result = $this->result_object();
if (count($result) === 0)
{
return NULL;
}
if ($n !== $this->current_row && isset($result[$n]))
{
$this->current_row = $n;
}
return $result[$this->current_row];
}
as you its either returning the first element or the argument supplied i.e the row index.
row is actually an alias to this row_object
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()')
Im triing to create this logic, but i can't reach any goal.
i habe a list of Feeds.It is sometimes veri large almost 2000 entries.
now i want to create a Read function which gives me the first 40 entries and when i have read them or i have scrolled fully to the buttom the next 40 entries should be append at the buttom of the list.
My current staff:
i created a Paging with page and pagesize but the Problem is the following:
let say we have total 20 entries and a begin to request the entries:
page 1 5 items items 0 - 5 of the list (if the ar read they aren't in the list anymore!)
not i load page 2 => items 5-0 aso.. at a point it crosses itself and no items will be retrned!
has anyone an Idea how i can fix this ??
thanks
You must use Paginator class. There is a working example on one of my repositories class:
<?php
namespace Fluency\Bundle\GeneralBundle\Entity\Repository;
use Doctrine\ORM\EntityRepository,
Doctrine\ORM\Tools\Pagination\Paginator;
use Fluency\Bundle\GeneralBundle\Entity\Country;
/**
* CountryRepository
*
* This class was generated by the Doctrine ORM. Add your own custom
* repository methods below.
*/
class CountryRepository extends EntityRepository
{
/**
* #param array $criteria
* #param null $limit
* #param null $start
* #return array with this format (0 => $result, 1 => $counter)
*/
public function findAllByCriteria($criteria = array(), $limit = null, $start = null)
{
$query = $this->getEntityManager()->createQueryBuilder()
->select('c')
->from($this->getEntityName(), 'c')
->orderBy('c.name', 'ASC');
if(null !== $limit)
{
$query->setMaxResults($limit);
}
if(null !== $start)
{
$query->setFirstResult($start);
}
if(!empty($criteria))
{
if($criteria['active'])
{
$query->andWhere($query->expr()->eq('c.active', $criteria['active']));
}
}
$paginator = new Paginator($query);
return array($query->getQuery()->getArrayResult(), $paginator->count());
}
.................
}
Hi guys i believe this could turn out to be trivial but i have the following code
$response = $groupsmapper->getDbTable()->fetchAll(
$groupsmapper->getDbTable()->select('group_area_residence')
->distinct()
which is supposed to get me all the distinct group_area_residence. However it fetches all the columns for the group.
I am using zend_db_table btw. How do i fix this?
According to select() in Zend/Db/Table/Abstract.php, it checks whether to include the from part, instead of getting the field name
/**
* Returns an instance of a Zend_Db_Table_Select object.
*
* #param bool $withFromPart Whether or not to include the from part of the select based on the table
* #return Zend_Db_Table_Select
*/
public function select($withFromPart = self::SELECT_WITHOUT_FROM_PART)
{
require_once 'Zend/Db/Table/Select.php';
$select = new Zend_Db_Table_Select($this);
if ($withFromPart == self::SELECT_WITH_FROM_PART) {
$select->from($this->info(self::NAME), Zend_Db_Table_Select::SQL_WILDCARD, $this->info(self::SCHEMA));
}
return $select;
}
See if the below code snippet helps (replacing table_name by desired one)
$select = $groupsmapper->getDbTable()
->select()
->distinct()
->from(array('table_name'), array('group_area_residence'));
$response = $groupsmapper->getDbTable()->fetchAll($select);