如何随机选择学说

时间:2012-05-25 23:51:24

标签: php doctrine-orm dql

以下是我在数据库中查询某些单词的方法

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

我正在使用mysql,我想获得符合条件的随机行,我会在查询中使用rand()命令。

我发现this similar问题基本上是建议的,因为在教条中不支持ORDER BY RAND,您可以随机化主键。但是,在我的情况下无法做到这一点,因为我有一个搜索条件和一个where子句,以便不是每个主键都满足这个条件。

我还发现code snippet建议您使用OFFSET随机化行,如下所示:

$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();

我有点困惑的是,这是否会帮助我解决在我的情况下随机缺乏对订单的支持。我无法在setMaxResult之后添加偏移量。

知道如何实现这一目标吗?

14 个答案:

答案 0 :(得分:41)

The Doctrine团队is not willing to implement this feature

您的问题有几种解决方案,每种解决方案都有其自身的缺点:

  • 添加custom numeric function:请参阅此DQL RAND() function
    (如果你有很多匹配的行,可能会很慢)
  • 使用native query
    (我个人试图避免这种解决方案,我发现很难维护)
  • 首先发出原始SQL查询以随机获取一些ID,然后使用DQL WHERE x.id IN(?)加载相关对象,方法是将ID数组作为参数传递。
    此解决方案涉及两个单独的查询,但可能提供比第一个解决方案更好的性能(除了ORDER BY RAND()之外的其他原始SQL技术,我不会在这里详述它们,你会发现一些好的本网站上的资源)。

答案 1 :(得分:30)

请按照以下步骤操作:

在项目中定义一个新类:

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

注册课程config.yml

doctrine:
     orm:
         dql:
             numeric_functions:
                 Rand: My\Custom\Doctrine2\Function\Rand

直接用作:

$qb->addSelect('RAND() as HIDDEN rand')->orderBy('rand');

答案 2 :(得分:16)

与Hassan Magdy Saad suggested一致,您可以使用受欢迎的DoctrineExtensions库:

在此处查看mysql实现:https://github.com/beberlei/DoctrineExtensions/blob/master/src/Query/Mysql/Rand.php

# config.yml

doctrine:
     orm:
         dql:
             numeric_functions:
                 rand: DoctrineExtensions\Query\Mysql\Rand

在Doctrine ORM 2.6.x-dev中测试,您可以实际执行:

->orderBy('RAND()')

答案 3 :(得分:7)

或者你可以这样做 - >

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

当然,如果您有许多记录,这将非常低效,因此请谨慎使用。

答案 4 :(得分:5)

Doctrine 2不支持ORDER BY rand(),但我发现this文章中包含对此进行修复的问题。

答案 5 :(得分:4)

为什么不使用存储库?

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

答案 6 :(得分:1)

对我来说,最有用的方法是创建两个数组,在这里我说订单类型和实体的不同属性。例如:

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

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

您可以像条件一样向数组$ column添加更多条目。

然后,您可以使用Doctrine在-> orderBy内添加$ column和$ order来构建查询。例如:

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

这样可以提高应用程序的性能。我希望这对某人有帮助。

答案 7 :(得分:0)

可以对查询(数组)结果进行混洗,但是随机抽取不会随机选择。

为了从实体中随机选择,我更喜欢在PHP中执行此操作,这可能会减慢随机选择速度,但它允许我控制测试我正在做的事情并使最终调试更容易。

下面的示例将实体中的所有ID放入一个数组中,然后我可以将其用于&#34;随机处理&#34;在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;
}

答案 8 :(得分:0)

我希望这会有助于其他人:

        $limit = $editForm->get('numberOfQuestions')->getData();
        $sql = "Select * from question order by RAND() limit $limit";

        $statement = $em->getConnection()->prepare($sql);
        $statement->execute();
        $questions = $statement->fetchAll();

请注意,表格问题是AppBundle:问题实体。相应地更改详细信息。问题的数量来自编辑表单,请确保检查表单构建器的变量并相应地使用。

答案 9 :(得分:0)

@ Krzysztof的解决方案在这里是最好的恕我直言,但是RAND()在大型查询上非常慢,所以我更新了@Krysztof的解决方案,以减少&#34;随机&#34;结果,但它们仍然是随机的。灵感来自这个答案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);
    }
}

答案 10 :(得分:0)

可能最简单(但不一定是最聪明)的方法来获得单个对象结果ASAP将在您的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()
    ;
}

答案 11 :(得分:0)

首先从数据库表中获取最大值,然后将其用作PHP中的偏移量,即 $ offset = mt_rand(1,$ maxId)

答案 12 :(得分:-1)

我知道这是一个老问题。但我使用以下解决方案来获取随机行。

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

答案 13 :(得分:-4)

只需添加以下内容:

->orderBy('RAND()')