web-dev-qa-db-fra.com

Comment choisir au hasard avec la doctrine

Voici comment j'interroge quelques mots dans ma base de données

$query = $qb->select('w')
    ->from('DbEntities\Entity\Word', '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 en substance puisque ORDER BY Rand n'est pas pris en charge dans la doctrine, vous pouvez randomiser la clé primaire. Cependant, cela ne peut pas être fait dans mon cas parce que j'ai un critère de recherche et une clause where afin que toutes les clés primaires ne répondent pas à cette condition.

J'ai aussi trouvé un extrait de code suggérant d'utiliser OFFSET pour randomiser les lignes de la manière suivante:

$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 perplexe quant à savoir si cela va m'aider à résoudre le problème du manque de soutien à l'ordre aléatoire dans mon cas ou non. Je n'ai pas pu ajouter de décalage après setMaxResult.

Une idée de comment cela peut être accompli?

29
Yasser1984

L'équipe Doctrine n'est pas disposée à implémenter cette fonctionnalité .

Il existe plusieurs solutions à votre problème, chacune ayant ses propres inconvénients:

  • Ajouter une fonction numérique personnalisée : voir ceci fonction DQL Rand ()
    (peut être lent si vous avez beaucoup de lignes correspondantes)
  • Utilisez une requête native
    (J'essaie personnellement d'éviter cette solution difficile à maintenir)
  • Emettez d'abord une requête SQL brute pour obtenir des identifiants de manière aléatoire, puis utilisez DQL WHERE x.id IN(?) pour charger les objets associés, en transmettant le tableau d'identifiants en tant que paramètre.
    Cette solution implique deux requêtes distinctes, mais pourrait donner de meilleures performances que la première solution (des techniques SQL brutes autres que ORDER BY Rand() existent, je ne les détaillerai pas ici, vous trouverez de bonnes ressources sur ce site Web. ).
39
Benjamin

Suivez ces étapes:

Définissez une nouvelle classe dans votre projet comme suit:

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()';
    }
}

Enregistrez 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');
27
HMagdy

En accord avec ce que Hassan Magdy Saad a suggéré , vous pouvez utiliser le populaire DoctrineExtensions library:

Voir l'implémentation de mysql 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 Doctrine ORM 2.6.x-dev, vous pouvez alors réellement faire:

->orderBy('Rand()')
13
Jonny

Ou vous pourriez faire ceci ->

$words = $em->getRepository('Entity\Word')->findAll();
shuffle($words);

Bien sûr, cela serait très inefficace si vous avez plusieurs enregistrements, utilisez-le avec prudence.

7
Derek

Doctrine 2 ne prend pas en charge ORDER BY Rand (), mais j’ai trouvé cet article contenant des correctifs. 

5
Sn00p

Pourquoi ne pas utiliser le référentiel?

<?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);
    }
}
4
Krzysztof Trzos

Pour moi, le moyen le plus utile était de créer deux tableaux où je dis le type d'ordre et différentes propriétés de l'entité. Par exemple:

    $order = array_Rand(array(
        'DESC' => 'DESC',
        'ASC' => 'ASC'
    ));

    $column = array_Rand(array(
        'w.id' => 'w.id',
        'w.date' => 'w.date',
        'w.name' => 'w.name'
    ));

Vous pouvez ajouter plus d'entrées à array $ column comme des critères.

Ensuite, vous pouvez construire votre requête avec Doctrine en ajoutant $ column et $ order inside -> orderBy. Par exemple:

$query = $qb->select('w')
->from('DbEntities\Entity\Word', 'w')
->where('w.indictionary = 0 AND w.frequency > 3')
->orderBy($column, $order)
->getQuery()
->setMaxResults(100);

De cette façon, les performances de mon application ont été améliorées. J'espère que ça aidera quelqu'un.

0
Andrés Moreno

Commencez par obtenir la valeur MAX de la table de base de données, puis utilisez-la comme offset dans PHP i.e $ offset = mt_Rand (1, $ maxId)

0
SiteTester

Je sais que c'est une vieille question. Mais j'ai utilisé la solution suivante pour obtenir la ligne aléatoire.

Utilisation d’une méthode EntityRepository :

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();
}
0
Hossam

La solution de @ Krzysztof est la meilleure à mon humble avis ici, mais Rand () est très lente pour les requêtes volumineuses. J'ai donc mis à jour la solution de @ Krysztof afin d'obtenir moins de résultats "aléatoires", mais ils sont toujours suffisamment 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);
    }
}
0
PayteR

Le moyen le plus simple (mais pas nécessairement le plus intelligent) d’obtenir un seul résultat object dès que possible serait de l’implémenter dans votre classe Repository:

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()
    ;
}
0
kyeno

J'espère que cela aiderait 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();

Notez ici que la question du tableau est une entité AppBundle: Question. Changer les détails en conséquence. Le nombre de questions provient du formulaire de modification. Vérifiez la variable du générateur de formulaire et utilisez-la en conséquence. 

0
Safwan Bakais

Le brassage peut être effectué sur le résultat de la requête (tableau), mais le brassage ne prend pas au hasard.

Afin de choisir au hasard dans une entité, je préfère le faire en PHP, ce qui peut ralentir le choix aléatoire, mais cela me permet de garder le contrôle de ce que je fais et facilite le débogage éventuel.

L'exemple ci-dessous met tous les identifiants de l'entité dans un tableau, que je peux ensuite utiliser pour "traiter au hasard" 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;
}
0
araldh