【问题标题】:How to select randomly with doctrine如何用学说随机选择
【发布时间】:2012-05-25 23:51:24
【问题描述】:

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

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

我发现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 之后添加偏移量。

知道如何实现吗?

【问题讨论】:

    标签: php doctrine-orm dql


    【解决方案1】:

    与 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()')
    

    【讨论】:

    • 这绝对是正确的答案!对于任何问题,您总会找到 ^^ 的扩展名
    【解决方案2】:

    按照以下步骤操作:

    在您的项目中定义一个新类:

    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
    

    【讨论】:

    • 这显然是我认为最好的解决方案,因为您仍然可以使用 DQL/querybuilder 和 Doctrine,而且还具有 SQL 性能。对我来说,orderBy 子句需要是 'rand' 而不是 'rand()' 才能工作(这是有道理的,因为您使用的是 var 而不是调用函数)。
    • 这个答案指出了一种在查询中提供随机结果的方法,这种方法可能早在 2014 年就是正确的解决方案。但是,正如@Jonny 所解释的,有一种更简单的方法。无需定义额外的 Rand 类。
    【解决方案3】:

    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 技术,我不会在这里详细说明,您会在本网站)。

    【讨论】:

    • 好的,我尝试了第一种方法,似乎 rand 函数正在引导中添加到学说中,我仍然收到此错误“错误:'rand'未定义。”我使用的 DQL 看起来像这样 $dql = "SELECT w FROM DbEntities\Entity\Word w WHERE w.indictionary = 0 AND w.frequency > 3 order by rand()";假设该功能被学说接受,我应该如何使用它?
    • 您是否在您的 Doctrine 配置中使用addCustomNumericFunction() 注册了该功能,如页面所述?另外,尝试使用大写的RAND,不确定是否区分大小写。
    • 是的,我确实使用了大写字母,但没有帮助。我接受了您的第二个建议,即本机查询,我不知道将来使用此功能是否会面临更多限制,希望不会。非常感谢。
    • 好的,根据this link,你只能先SELECTORDER BY自定义函数。那应该是SELECT w, RAND() AS r FROM Word w ORDER BY r
    • “先发出原始 SQL 查询以随机获取一些 ID”,您是如何做到的?本机查询?是不是同样的问题?
    【解决方案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);
        }
    }
    

    【讨论】:

      【解决方案5】:

      或者你可以这样做 -->

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

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

      【讨论】:

      • 如果我有限制怎么办。我总是会得到相同的 n 然后洗牌。拒绝好的解决方案。
      • 如果您只想随机选择数据固定装置,这将非常方便快捷。
      • “非常低效”是一个非常重要的细节。
      【解决方案6】:

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

          $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 你是对的。这样它永远不会与 RAND() 相同。如果有人想要改进组合,他们必须添加更多列。如果有人想要行为 RAND(),最好阅读其他答案。问候
      【解决方案7】:

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

      【讨论】:

      • 如果有任何未分配的 ID,这将不起作用,因此它需要至少两个 DB 调用(以防 PHP 在随机发生器中选择使用的 ID),并且在最坏的情况下它使用更多的查询
      【解决方案8】:

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

      【讨论】:

      • 如果有任何未分配的 ID,这将不起作用,因此它需要至少两个 DB 调用(以防 PHP 在随机发生器中选择使用的 ID),并且在最坏的情况下它使用更多的查询
      【解决方案9】:

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

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

      【讨论】:

      • 如果中间的 id 丢失可能是因为实体被删除了怎么办?不会出错吗?例如,$id_limits 返回“1”作为最小值,将“1000”作为最大值......在 1 和 1000 之间随机化会给你 430......但是实体 430 在之前被删除......
      【解决方案10】:

      只需添加以下内容:

      ->orderBy('RAND()')
      

      【讨论】:

      • 请为您的答案添加一些解释。通过阅读其他答案,我猜还需要更多的东西吗?
      【解决方案11】:

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

      为了从实体中随机选择,我更喜欢在 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;
      }
      

      【讨论】:

        【解决方案12】:

        我希望这对其他人有帮助:

                $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:Question 实体。相应地更改详细信息。问题的数量取自编辑表单,请确保检查表单构建器的变量并相应地使用。

        【讨论】:

          【解决方案13】:

          @Krzysztof 的解决方案在这里是最好的,恕我直言,但是 RAND() 在大型查询上非常慢,所以我更新了@Krysztof 的解决方案以提供更少的“随机”结果,但它们仍然足够随机。受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);
              }
          }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-10-07
            • 2013-07-31
            • 1970-01-01
            • 2018-01-24
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多