web-dev-qa-db-ja.com

doctrineでランダムに選択する方法

データベースにいくつかの単語を問い合わせる方法は次のとおりです

$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()による順序を使用します。

私は これに似た の質問を見つけました。これは基本的にORDER BYランドがドクトリンでサポートされていないため、代わりに主キーをランダム化できます。ただし、検索条件と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();

私の場合、これがランダムな順序のサポートの欠如を回避するのに役立つかどうかについて少し混乱しています。 setMaxResultの後にオフセットを追加できませんでした。

これをどのように達成できるか考えていますか?

32
Yasser1984

Doctrine team この機能を実装する意思はありません

問題にはいくつかの解決策があり、それぞれに欠点があります。

  • カスタム数値関数 を追加:これを参照 DQL Rand()関数
    (一致する行が多数ある場合は遅くなる可能性があります)
  • ネイティブクエリ を使用します
    (私は個人的にこの解決策を避けようとしていますが、保守が難しいと感じました)
  • 最初に生のSQLクエリを発行していくつかのIDをランダムに取得し、IDの配列をパラメーターとして渡すことにより、DQL WHERE x.id IN(?)を使用して関連オブジェクトを読み込みます。
    このソリューションには2つのクエリが含まれますが、mightは最初のソリューション(ORDER BY Rand()が存在するので、ここでは詳しく説明しません。このWebサイトでいくつかの優れたリソースを見つけることができます。
42
Benjamin

次の手順を実行します:

プロジェクトで新しいクラスを次のように定義します。

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

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()')
18
Jonny

または、これを行うことができます->

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

もちろん、多くのレコードがある場合、これは非常に非効率的であるため、注意して使用してください。

9
Derek

Doctrine 2はORDER BY Rand()をサポートしていませんが、 this この問題の修正を含む記事が見つかりました。

5
Sn00p

リポジトリを使用しない理由

<?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

私にとって、最も便利な方法は、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に$ columnと$ orderを-> orderByに追加してクエリを作成できます。例:

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

これにより、アプリケーションのパフォーマンスが向上しました。これが誰かの助けになることを願っています。

1
Andrés Moreno

最初にDBテーブルからMAX値を取得し、これをPHPつまり$ offset = mt_Rand(1、$ maxId)のオフセットとして使用します

0
R Sun

これは古い質問です。ただし、次のソリューションを使用してランダムな行を取得しました。

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

@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);
    }
}
0
PayteR

1つのobjectの結果を取得する最も簡単な(ただし、必ずしも最もスマートではない)方法は、ASAPをリポジトリクラスに実装することです。

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

これが他の人に役立つことを願っています:

        $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 Entityであることに注意してください。それに応じて詳細を変更します。質問の数は編集フォームから取得されます。フォームビルダーの変数を確認し、それに応じて使用してください。

0
Safwan Bakais

照会(配列)の結果に対してシャッフルを実行できますが、シャッフルはランダムに選択しません。

エンティティからランダムに選択するために、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
araldh