在 Repository 中使用 Doctrine 2 ORM 和 OneToOne 获得“排名”

Posted

技术标签:

【中文标题】在 Repository 中使用 Doctrine 2 ORM 和 OneToOne 获得“排名”【英文标题】:Obtain "rank" using Doctrine 2 ORM and OneToOne in Repository 【发布时间】:2019-10-20 12:48:22 【问题描述】:

我发现几乎不可能达到我想要的结果,在这里和其他网站上使用不同的解决方案(子查询、原始查询等)进行了多次尝试之后,我必须提出这个问题。

我的目标是根据每个“项目”的“分数”提取/获取它们的排名。

将“score”视为 int 并具有 1、2、6、4、8、10、200 等值。

排名是这样的:

排名 - 得分

    200 10 8 6

为了让我的问题尽可能简单明了,我将我的实际表/实体重命名如下:

MainEntity(main_table):

/**
* @ORM\Id
* @ORM\Column(name="id")
* @ORM\GeneratedValue
*/
protected $id;

// other fields, un-related to this question

/**
* @ORM\OneToOne(targetEntity="Application\Entity\SecondTable", mappedBy="second_table_data")
*/
protected $second_table;

/**
* @ORM\OneToOne(targetEntity="Application\Entity\ThirdTable", mappedBy="third_table_data")
* 
*/
protected $third_table;

SecondEntity(second_table):

/**
* @ORM\Id
* @ORM\Column(name="id")
* @ORM\GeneratedValue
*/
protected $id;

// other fields, un-related to this question

/**
* @ORM\OneToOne(targetEntity="Application\Entity\SecondTable", inversedBy="second_table")
* @ORM\JoinColumn(name="project_id", referencedColumnName="id")
*/
private $second_table_data;

第三实体(third_table):

/**
* @ORM\Id
* @ORM\Column(name="id")
* @ORM\GeneratedValue
*/
protected $id;

// other fields, un-related to this question

/** 
 * @ORM\Column(name="score")  
 */
protected $score;

/**
* @ORM\OneToOne(targetEntity="Application\Entity\ThirdTable", inversedBy="third_table")
* @ORM\JoinColumn(name="project_id", referencedColumnName="id")
*/
private $third_table_data;

以及选择按分数排序的“所有项目”的存储库功能:

public function findAllProjects()

    $entityManager = $this->getEntityManager();

    $queryBuilder = $entityManager->createQueryBuilder();

    $queryBuilder->select('u')
       ->from(MainEntity::class, 'u')
       ->leftJoin('u.third_table', 't')
       ->orderBy('t.score', 'DESC');

    return $queryBuilder->getQuery()->getResult();

这很好用(我相信),因为我根据他们的“project_id”从 ma​​in_table + second_table + third_table 获得所有“项目” ”。

但是问题是我找不到正确计算或获取每个项目的“排名”数字的方法。我还尝试使用“foreach”并将“index”用作“rank”,但这无法正常工作,因为我使用的是 ORMPaginator 所以每次单击“foreach”的“页面”时"index" 将从 0 重置。

我希望问题足够清楚,让您清楚地了解我的问题。

请告诉我如何实现这一点,如果我的整个方法是错误的,请指出。

我们非常感谢每一个建议/提示/解决方案。

【问题讨论】:

基本上,你想做一个左连接并按连接表中的一个字段排序,对吗?您想获取的 findAllProjects() 的返回值中缺少什么? @SergioRinaudo 简单地按文件订购不会让我得到我需要/想要的东西,请看这个:***.com/questions/3333665/rank-function-in-mysql 这就是我想要的。我可以通过“分数”对他们进行排序,这很好,但我想根据他们的分数获得他们的“排名”,1、2、3、4……这有意义吗? 其实,你想要的不是“他们的排名”,而是他们在有序列表中的位置。 这将是同一件事,所以是的。 您是否尝试将OrderBy Annotation 添加到您希望订购的属性上,例如“分数” ? (我的意思是,我在这里看到了复杂的尝试,但你也标记了 Doctrine,所以为了简单起见 -> 下一个 Criteria 过滤 MainEntity 的集合) 【参考方案1】:

首先,要真正注意列类型。 您的所有列都是由varchar(255) 的原则创建的,如果这些列是您想要在其上创建order by 的数字,那么这不是理想的选择。这是vendor/bin/doctrine-module orm:schema-tool:create --dump-sql的输出的sn-p:

CREATE TABLE main_entity (
  id VARCHAR(255) NOT NULL,
  PRIMARY KEY(id)
) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB;

因此,您必须做的第一件事是向列中添加列类型:

/**
 * @ORM\Id
 * @ORM\Column(name="id", type="integer")
 * @ORM\GeneratedValue
 */
protected $id;

我使用了这些数据:

third_table                      main_entity
+----+------------+-------+      +----+
| id | project_id | score |      | id |
+----+------------+-------+      +----+
| 1  | 1          | 8     |      | 1  |
| 2  | 2          | 10    |      | 2  |
| 3  | 3          | 6     |      | 3  |
| 4  | 4          | 200   |      | 4  |
+----+------------+-------+      +----+

关于排名,基本上是行号,SQL 查询是:

SELECT 
   ROW_NUMBER() OVER (ORDER BY t.score DESC) AS r_rank,
   t.score AS score 
FROM main_entity m 
LEFT JOIN third_table t ON m.id = t.project_id 
ORDER BY t.score DESC
-- LIMIT 2 OFFSET 2 -- Limits for pagination
without limits           with limits
+--------+-------+       +--------+-------+
| r_rank | score |       | r_rank | score |
+--------+-------+       +--------+-------+
| 1      | 200   |       | 3      | 8     |
| 2      | 10    |       | 4      | 6     |
| 3      | 8     |       +--------+-------+
| 4      | 6     |
+--------+-------+

不幸的是,ROW_NUMBER 没有针对 SQL 查询实现。如果你试试这个:

$queryBuilder->select(['ROW_NUMBER() OVER (ORDER BY t.score DESC)','t.score'])
        ->from(MainEntity::class, 'u')
        ->leftJoin('u.third_table', 't')
        ->orderBy('t.score', 'DESC');
$queryBuilder->getQuery()->getResult();

您将收到以下错误:[Syntax Error] line 0, col 7: Error: Expected known function, got 'ROW_NUMBER' 另一种可能是:

$sql = 'SELECT 
            ROW_NUMBER() OVER (ORDER BY t.score DESC) AS r_rank,
            t.score AS score 
        FROM main_entity m 
        LEFT JOIN third_table t ON m.id = t.project_id 
        ORDER BY t.score DESC';
$results = $this->entityManager->getConnection()->executeQuery($sql);

我看到 paginator walkers (?!) 中有类似的东西,但并非所有数据库都支持它们,as you can see in the walker itself:

public function walkSelectStatement(SelectStatement $AST)

    if ($this->platformSupportsRowNumber()) 
        return $this->walkSelectStatementWithRowNumber($AST);
    
    return $this->walkSelectStatementWithoutRowNumber($AST);


private function platformSupportsRowNumber()

    return $this->platform instanceof PostgreSqlPlatform
        || $this->platform instanceof SQLServerPlatform
        || $this->platform instanceof OraclePlatform
        || $this->platform instanceof SQLAnywherePlatform
        || $this->platform instanceof DB2Platform
        || (method_exists($this->platform, 'supportsRowNumberFunction')
            && $this->platform->supportsRowNumberFunction());

作为最后的机会,您可以自己“添加”索引,即使使用分页器也是如此。

如果知道页面大小和当前页面,就可以计算排名:

$pageSize = 2;
$currentPage = 1;
$queryBuilder = $this->entityManager->createQueryBuilder();
$queryBuilder->select('t.score') // Here I use only the t.score, but you could put the whole class
        ->from(MainEntity::class, 'u')
        ->leftJoin('u.third_table', 't')
        ->orderBy('t.score', 'DESC');
$queryBuilder->setMaxResults($pageSize);
$queryBuilder->setFirstResult(($currentPage - 1) * $pageSize);

$paginator = new \Doctrine\ORM\Tools\Pagination\Paginator($queryBuilder->getQuery());
$adapter = new \DoctrineORMModule\Paginator\Adapter\DoctrinePaginator($paginator);

$results = [];
$currentItem = 1 + ($currentPage - 1) * $pageSize;
foreach($adapter->getPaginator()->getIterator()->getArrayCopy() as $result)
    $results[] = [
        'rank' => $currentItem++,
        'item' => $result
    ];

var_dump($results);

结果:

$pageSize = 2;                                        $pageSize = 2;
$currentPage = 1;                                     $currentPage = 2;

Array                                                 Array
(                                                     (
    [0] => Array                                          [0] => Array
        (                                                     (
            [rank] => 1                                           [rank] => 3
            [item] => Array                                       [item] => Array
                (                                                     (
                    [score] => 200                                        [score] => 8
                )                                                     )

        )                                                     )

    [1] => Array                                          [1] => Array
        (                                                     (
            [rank] => 2                                           [rank] => 4
            [item] => Array                                       [item] => Array
                (                                                     (
                    [score] => 10                                         [score] => 6
                )                                                     )

        )                                                     )

)                                                     )

【讨论】:

以上是关于在 Repository 中使用 Doctrine 2 ORM 和 OneToOne 获得“排名”的主要内容,如果未能解决你的问题,请参考以下文章

在 Doctrine QueryBuilder 中计算行数

如何在 Doctrine 中使用 findBy() 对结果进行排序

当字段有下划线时,Magic Doctrine2 finders?

如何在Doctrine2中为单个实体使用多个存储库?

用LIKE进行Doctrine 2查询

左连接 ON 条件和 Doctrine 中的其他条件语法