如何使用Doctrine进行随机选择

42

以下是我如何查询数据库中的一些单词:

$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,并想获取符合特定条件的随机行,我会在我的查询中使用order by rand()。
我找到了类似的问题(链接),其中建议由于Doctrine不支持ORDER BY RAND,因此可以随机化主键。但是,在我的情况下无法这样做,因为我有一个搜索条件和where子句,因此并非每个主键都满足该条件。
我还发现了一个代码片段,建议您使用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();

我有点困惑这是否可以帮助我解决在我的情况下缺乏order by random支持的问题。我无法在设置setMaxResult之后添加偏移量。

有任何想法如何实现吗?

15个回答

49

Doctrine团队不打算实现这个功能

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

  • 添加一个自定义数值函数: 可以查看此DQL RAND()函数
    (如果匹配的行很多可能会很慢)
  • 使用本地查询
    (个人尽量避免此解决方案,我发现它难以维护)
  • 首先发出原始SQL查询以随机获取一些ID,然后使用DQLWHERE x.id IN(?)通过将ID数组作为参数传递来加载相关对象。
    这个解决方案涉及两个单独的查询,但可能比第一个解决方案更有效(除了ORDER BY RAND()之外,还存在其他原始SQL技术,我不会在此详细介绍它们,在这个网站上你会找到一些很好的资源)。

好的,我尝试了第一种方法,似乎rand函数被添加到引导程序中的doctrine中,但我仍然收到这个错误“错误:'rand'未定义”。我正在使用类似于以下的DQL:$dql =“SELECT w FROM DbEntities\Entity\Word w WHERE w.indictionary = 0 AND w.frequency > 3 order by rand()”;假设该函数被doctrine接受,我应该如何使用它? - Yasser1984
是的,我使用了大写字母,但没有帮助。我采用了你提出的第二个建议,使用本地查询,我不知道将来是否会遇到更多限制,希望不会。非常感谢。 - Yasser1984
2
好的,根据这个链接,你只能通过先选择自定义函数来ORDER BY。应该像这样写:SELECT w, RAND() AS r FROM Word w ORDER BY r - BenMorel
首先发出一个原始的SQL查询以随机获取一些ID,你怎么做?使用本地查询吗?这不是同样的问题吗? - httpete
不,通过原始查询,我是指直接在连接上执行:$em->getConnection()->query('SELECT id FROM table ORDER BY RAND()'); 然后获取这些id,并将其作为参数传递给DQL查询的数组。 - BenMorel
显示剩余5条评论

48

这绝对是正确的答案!对于任何问题,你总能找到一个扩展程序 ^^ - Thomas Decaux

46

请按照以下步骤进行操作:

在您的项目中定义一个新类,如下所示:

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()'); //Missing curly brackets

2
在我看来,这显然是最好的解决方案,因为你仍然可以使用DQL/查询构建器和Doctrine,但也具有SQL性能。对于我来说,orderBy子句需要是'rand'而不是'rand()'才能起作用(这是有道理的,因为你正在使用变量而不是调用函数)。 - Frank Houweling
1
这个答案指出了一种处理查询中提供随机结果的方法,也许在2014年它是正确的解决方案。然而,正如@Jonny所解释的那样,有一种更简单的方法。没有必要定义额外的Rand类。 - xarlymg89

9
或者你可以这样做 -->
$words = $em->getRepository('Entity\Word')->findAll();
shuffle($words);

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

如果我有限制,我将始终获得相同的n,然后对它们进行洗牌。这不是一个好的解决方案。 - MilanG
1
如果您只想为数据夹具选择一个随机值,那么这非常快捷方便。 - Madhab452
3
“非常低效”是一个非常重要的细节。 - Nico Haase

7

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

2

对我来说,最有用的方法是创建两个数组,一个用于指定订单类型,另一个用于指定实体的不同属性。例如:

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

这种方法提高了我的应用程序的性能。希望这对某些人有所帮助。

这并不完全是随机的,这种方法总共给出6种不同的组合。 - nacholibre
1
@nacholibre,你是对的。这样就永远不会与RAND()相同。如果有人想要改善组合,他们必须添加更多的列。如果有人想要具有RAND()的行为,最好阅读其他答案。问候 - Andrés Moreno

0

@Krzysztof的解决方案在我看来是最好的,但RAND()在大查询上非常慢,因此我更新了@Krysztof的解决方案,以提供更少的“随机”结果,但它们仍然足够随机。受这个答案启发https://dev59.com/KG855IYBdhLWcg3weEGJ#4329492

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
警告:
顶级答案忘记了一件事:使用RAND()随机排序的是SQL查询结果行。但是,如果进行连接操作,似乎Doctrine返回的“根”对象(来自查询的主实体对象)会按照它们在SQL结果行中首次出现的顺序进行排序。
这样,假设有一个与1个子对象相关联的根对象R1,以及一个与10个子对象相关联的根对象R2,如果我们按照RAND()对行进行排序,那么在getResult()调用的返回结果中,R2对象出现在R1对象之前的概率将高出10倍。
我找到的一个解决方案是使用$rand = rand();生成整个查询共享的随机值。
然后使用以下方式进行排序:$qb->addOrderBy("rand(rootAlias.id + $rand)") 这样,同一个根对象的每一行都具有相同的随机排序值,因为它们共享相同的种子。

0

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

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

下面的示例将实体中的所有ID放入一个数组中,然后我可以在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
  1. 在您的Symfony项目中创建一个新的服务来注册自定义DQL函数:src/Doctrine/RandFunction.php
    namespace App\Doctrine;
    
    use Doctrine\ORM\Query\AST\Functions\FunctionNode;
    use Doctrine\ORM\Query\Lexer;
    
    class RandFunction extends FunctionNode
    {
        public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
        {
            return 'RAND()';
        }
    
        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);
        }
    }

(config/packages/doctrine.yaml):

    doctrine:
    orm:
        dql:
            string_functions:
                RAND: App\Doctrine\RandFunction

RAND() 函数,这里发布:

public function findRandomPosts()
{
    $query = $this->createQueryBuilder('p')
        ->orderBy('RAND()')
        ->setMaxResults(3)
        ->getQuery();

    return $query->getResult();
}

Symfony4.4,我可以使用它。 - hellcat thành
请修复代码格式。 - Sybille Peters

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接