Comment choisir au hasard avec la doctrine
Voici comment je recherche dans ma base de données certains mots
$query = $qb->select('w')
->from('DbEntitiesEntityWord', 'w')
->where('w.indictionary = 0 AND w.frequency > 3')
->orderBy('w.frequency', 'DESC')
->getQuery()
->setMaxResults(100);
j'utilise mysql et j'aimerais obtenir des lignes aléatoires qui correspondent aux critères, j'utiliserais order by rand() dans ma requête.
j'ai trouvé cette question similaire qui suggère fondamentalement puisque L'ordre Par RAND n'est pas soutenue dans la doctrine, vous pouvez randomiser la clé primaire à la place. Cependant, cela ne peut pas être fait dans mon cas parce que j'ai un critère de recherche et un lorsque la clause de sorte que toutes les clés primaires ne satisferont pas à cette condition.
j'ai aussi trouvé un code snippet qui suggère d'utiliser le décalage pour randomiser les lignes comme ceci:
$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();
je suis un peu confus quant à savoir si cela va m'aider à contourner le manque de soutien pour l'ordre par hasard dans mon cas ou pas. Je n'ai pas pu ajouter offset après setMaxResult.
N'importe quelle idée comment cela peut être accompli?
12 réponses
La Doctrine de "l'équipe de 151930920" n'est pas disposé à mettre en œuvre cette fonctionnalité .
il y a plusieurs solutions à votre problème, chacune ayant ses propres inconvénients:
- Ajouter un fonction numérique personnalisée : voir ce DQL Rand() fonction
(peut être lent si vous avez beaucoup de lignes correspondant) - utiliser un requête natif
(J'essaie personnellement d'éviter cette solution, que j'ai trouvé difficile à maintenir) - lancer une requête SQL brute d'abord pour obtenir quelques IDs au hasard, puis utiliser le DQL
WHERE x.id IN(?)
pour charger les objets associés, en passant le tableau D'IDs comme paramètre.
Cette solution implique deux requêtes distinctes, mais pourrait donner de meilleures performances que la première solution (autres techniques brutes de SQL queORDER BY RAND()
existe, Je ne vais pas les détailler ici, vous trouverez de bonnes ressources sur ce site).
suivez ces étapes:
définissez une nouvelle classe à votre projet comme:
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()';
}
}
Enregistrer la classe config.yml
:
doctrine:
orm:
dql:
numeric_functions:
Rand: My\Custom\Doctrine2\Function\Rand
utilisez-le directement comme:
$qb->addSelect('RAND() as HIDDEN rand')->orderBy('rand');
Ou vous pouvez le faire -->
$words = $em->getRepository('Entity\Word')->findAll();
shuffle($words);
bien sûr, ce serait très inefficace si vous avez de nombreux enregistrements donc utiliser avec prudence.
pourquoi ne pas utiliser le dépôt?
<?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);
}
}
en ligne avec ce que Hassan Magdy a dit suggéré , vous pouvez utiliser le populaire extensions doctrine bibliothèque:
Voir mysql mise en œuvre ici: https://github.com/beberlei/DoctrineExtensions/blob/master/src/Query/Mysql/Rand.php
# config.yml
doctrine:
orm:
dql:
numeric_functions:
rand: DoctrineExtensions\Query\Mysql\Rand
testé dans la Doctrine ORM 2.6.x-dev, vous pouvez alors réellement faire:
->orderBy('RAND()')
Shuffling peut être fait sur le résultat de la requête (tableau), mais shuffling ne choisit pas au hasard.
afin de choisir au hasard une entité, je préfère le faire en PHP, ce qui pourrait ralentir le choix au hasard, mais cela me permet de garder le contrôle des tests que je fais et rend le débogage éventuel plus facile.
l'exemple ci-dessous met tous les ID de l'entité dans un tableau, que je peux ensuite utiliser pour" random-treat " en 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;
}
j'espère que cela aidera les autres:
$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 ici la question de table est une AppBundle:question Entity. Changez les détails en conséquence. Le nombre de questions est tiré du formulaire éditer, assurez-vous de vérifier la variable pour le constructeur de formulaire et utiliser en conséquence.
je sais que c'est une vieille question. Mais j'ai utilisé la solution suivante pour obtenir la rangée aléatoire.
utilisant une EntityRepository méthode:
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();
}
la solution de @Krzysztof est la meilleure ici, mais RAND () est très lent sur les grandes requêtes, donc j'ai mis à jour la solution de @Krysztof pour donner des résultats moins "aléatoires", mais ils sont encore assez aléatoires. Inspiré par cette réponse 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);
}
}
probablement la façon la plus facile (mais pas nécessairement la plus intelligente) d'obtenir un seul objet résultat dès que possible serait d'implémenter ceci dans votre classe de dépôt:
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()
;
}