Yii2多对多关系:连接表中with()字段的排序结果


Yii2 many-to-many relation: order results of with() by field in junction table

只是为了防止这种想法,即这是重复的问题,如这个,这个,或者这个:这些情况是不同的,因为他们谈论的是获得所有B项,按照连接表字段定义的顺序,为一个特定的a项。我说的是查询许多A项,并且对于每个A项,按照连接表中字段定义的顺序急切加载它的B项。


假设您有paperauthor表,它们是多对多的,通过paper_author表相关联。但对于学术论文,作者的顺序很重要,因此paper_author表中有一个数字rank字段。

现在您想要显示所有论文标题的列表,并在每篇论文的标题旁边列出作者,以适当的顺序。您还需要急切地加载作者,以避免在论文列表有100篇论文时必须运行100个作者查询。

您已经设置了标准的Yii2 ActiveRecord关系。你可以输入

<?php
$papers = Paper::find()->with('authors')->all();
foreach ($papers as $paper) {
    echo "Title: " . $paper->title . "'n"
        . "Authors: ";
    foreach ($paper->authors as $author) {
        echo $author->name . " ";
    }
    echo "'n";
}
?>

然而,每篇论文的作者都没有明确的顺序。好了,你对Paper类中作者关系的声明做了一个特殊的修改:

<?php
class Paper extends ActiveRecord {
    // ...
    public function getAuthors() {
        return $this
            ->hasMany(Author::className(), ['id' => 'author_id'])
            ->viaTable('paper_author', ['paper_id' => 'id'],
                // SPECIAL CHANGE: 3rd param to viaTable()
                function ($query) {
                    $query->orderBy('rank');
                }
            )
    }
    // ...
}
?>

但是,这不起作用,因为被修改的$querypaper_author上的SELECT,其目的是获取author_ids列表。在此之后,对author表和WHERE id IN (...author_ids...)运行另一个SELECT查询,但是该查询的结果不尊重给定给IN()函数的id的顺序——这不是IN()的工作方式。

即使您可以使查询的结果遵循该顺序,您也会遇到这个问题:查询返回的作者列表中每个作者只出现一次。这让它更有效率。然而,在您显示的论文列表中,给定的作者可能附属于不止一篇论文。作者顺序(rank)不是作者的属性;它是作者-论文关系的一个属性。

所以这是一个Yii2能够解决而我不知道的问题吗?还是需要一些自定义的结果处理?

我认为在这种情况下,正确的方法是为连接表定义额外的AR类,因为它不仅是多对多链接,而且还有自己的含义——作者在作者列表中的顺序位置。

所以,当你有像PaperAuthorship这样的类时(在PaperPaperAuthorshipAuthor之间有适当的关系),你可以这样执行你的查询:
$papers = Paper::find()
    ->with('paperAuthorships', 'paperAuthorships.author')
    ->all();
foreach ($papers as $paper) {
    echo "Title: " . $paper->title . "'n"
        . "Authors: ";
    foreach ($paper->paperAuthorships as $paperAuthorship) {
        echo $paperAuthorship->author->name . " ";
    }
    echo "'n";
}

并且它将很容易实现正确的排序顺序(例如在关系Paper.paperAuthorships)