【问题标题】:Select only relevant news items that are linked on a page (multiple one two many relationships)仅选择页面上链接的相关新闻项目(多一二多关系)
【发布时间】:2014-06-05 09:54:58
【问题描述】:

我有一个由多个containers 组成的page 实体。在这些容器中,用户可以链接news list。那些news lists 又包含news items

现在,我想搜索news items,但我需要pagenews list 也已链接。

我试过了:

$query = $this->getEntityManager()->createQuery('
    SELECT p, c, n, i
    FROM VendorNameBundle:Page p
    LEFT JOIN p.container c
    LEFT JOIN c.news n
    LEFT JOIN n.items i
    WHERE i.title LIKE :title
    GROUP BY i.id
');

这基本上可以为我提供正确的页面。但是要找到正确的news item,我必须遍历所有containers 和它们的news lists。当然,当我检索到所有项目时,我会得到所有项目,而不仅仅是对搜索很重要的项目。

我怎样才能优雅地解决这个问题?

总结:我需要news items 及其对应的page 实体。如何以简洁优雅的方式解决这个问题?

注意:如果news list 被列在多个页面上,结果中一个page 就足够了,不重要。

编辑

以下是 cmets 中要求的关系:

页面

/**
 * @ORM\OneToMany(targetEntity="Container", mappedBy="page", cascade={"remove"})
 * @ORM\OrderBy({"position" = "asc"})
 */
protected $containers;

容器

/**
 * @ORM\ManyToOne(targetEntity="Page", inversedBy="containers")
 * @ORM\JoinColumn(name="Page_id", referencedColumnName="id")
 */
private $page;

/**
 * @ORM\ManyToOne(targetEntity="News", inversedBy="containers")
 * @ORM\JoinColumn(name="news_id", referencedColumnName="id", onDelete="SET NULL")
 */
protected $news;

新闻

/**
 * @ORM\OneToMany(targetEntity="Container", mappedBy="news")
 */
protected $containers;

【问题讨论】:

  • 你能给我们一个例子,说明你们在 Doctrine 中是如何建立关系的吗?
  • Jasper N. Brouwer 有正确的答案。您必须颠倒选择顺序。这也不仅仅是特定于教义或 symfony,但如果你正在编写一个粗略的 mysql 查询,你应该这样做
  • @broncha 你说这是一个粗略的 MYSQL 查询。您能否举例说明如何以更好的方式解决此问题。
  • @Khez 我已经添加了关系。
  • @Khez 我所说的是,在这些情况下,您会以 Jasper N. Brouwer 的身份进行查询,即使它是粗略的 mysql 查询而不是 DQL。您正在寻找项目,那么它应该是您的第一个选择,然后选择您的方式到页面。

标签: php mysql symfony doctrine-orm


【解决方案1】:

查询

因为您正在搜索新闻项目,我建议您从这些新闻项目一直到页面(而不是相反):

SELECT i, n, c, p
FROM VendorNameBundle:Item i
JOIN i.news n
JOIN n.container c
JOIN c.page p
WHERE i.title LIKE :title

这个查询确实假设 每个 新闻项目都在一个新闻列表中,每个 新闻列表都在一个容器中,每个 容器都在一个容器中页面。如果不是这样,请使用LEFT JOIN 而不是JOIN,否则您会错过一些新闻。

分组

这个查询会给你你想要的数据,但可能没有按照你想要的方式格式化。如果您想按页面或新闻列表(等)对项目进行分组,则需要手动(在代码中)执行此操作。

但是,您可以让查询进行一些排序,因此分组(在代码中)变得更容易。以下示例将首先按页面标题排序结果,然后是新闻列表标题:

SELECT i, n, c, p
FROM VendorNameBundle:Item i
JOIN i.news n
JOIN n.container c
JOIN c.page p
WHERE i.title LIKE :title
ORDER BY p.title ASC, n.title ASC

现在可以这样分组:

$currentPage     = null;
$currentNewsList = null;

foreach ($newsItems as $newsItem) {
    $newsList = $newsItem->getNews();
    $page     = $newsList->getContainer()->getPage();

    if ($page !== $currentPage) {
        $currentPage = $page;
        echo '- ' . $page->getTitle() . "\n";
    }

    if ($newsList !== $currentNewsList) {
        $currentNewsList = $newsList;
        echo '    - ' . $newsList->getTitle() . "\n";
    }

    echo '        - ' . $newsItem->getTitle() . "\n";
}

这会打印出这样的东西:

- Page A
    - News list A
        - Found news item
    - News list C
        - Another found news item
- Page B
    - News list B
        - Yet another found news item

【讨论】:

  • +1 = +125 ;) 非常感谢您的帮助和努力。非常感谢,正是我所需要的。
【解决方案2】:

只需将连接恢复为RIGHT JOININNER JOIN 并选择Items 而不是Pages

$query = $this->getEntityManager()->createQuery('
    SELECT p, c, n, i
    FROM VendorNameBundle:Items i
    JOIN i.news n
    JOIN n.container c
    JOIN c.page p
    WHERE i.title LIKE :title
    GROUP BY i.id
');

【讨论】:

    【解决方案3】:

    我相信你可以简单地搜索你想要的items

    $query = $em->createQuery("SELECT i FROM Vendor:Item i WHERE i.title LIKE :title");

    这将为您提供您想要的特定items 的集合。

    然后,在查询返回的水合 Item 实体上,您应该具有运行关系的方法,例如:

    $items = $query->getResult();
    $pages = array();
    foreach ($items as $index => $item) {
      $pages[$index] = $item->getNews()->getContainer()->getPage();
    }
    

    这假设您的关系是直接的(一个项目正好属于一个列表,一个列表正好属于一个容器,一个容器正好属于一个页面)。 如果不是这样,但您不在乎弹出哪个页面,您可以这样做:

    $pages[$index] = $item->getNews()[0]->getContainers()[0]->getPages()[0];
    

    这里的概念是,例如,您的 Container 实体声明了类似这样的内容:

    /**
     * @ORM\ManyToOne(targetEntity="Page", inversedBy="containers")
    */
    private $page;
    //this returns an instance of Page entity
    public function getPage() { return $this->page; }
    

    您的Page 实体应该具有我们在这里使用的关系的反面,定义如下:

    /**
     * @ORM\OneToMany(targetEntity="Container", mappedBy="page")
    */
    private $containers;
    //this returns an instance of PersistentCollection which is iterable, can be
    // ArrayAccess'd, well, it's a glorified array
    public function getContainers() { return $this->containers; }
    

    这里只是一个附注:当您尝试访问相关实体时,它们可能(取决于您的 Doctrine 配置)尚未加载,因此 Doctrine 将在后台处理数据库,因此从性能的角度来看它很快就会变得很重。

    希望这会有所帮助!

    【讨论】:

    • 我之前根本没想到。我已经有了双向关系。但我没想到,我也可以走另一条路。
    【解决方案4】:

    我认为最简单的方法是在您的实体 (http://doctrine-orm.readthedocs.org/en/latest/reference/association-mapping.html#one-to-many-bidirectional) 之间建立双向关系,然后选择以下新闻项目:

    $query = $this->getEntityManager()->createQuery('
        SELECT i
        FROM VendorNameBundle:Items i
        WHERE i.title LIKE :title
        GROUP BY i.id
    ');
    

    通过这种方式,您可以获得您想要的新闻条目,并通过新闻列表和容器访问页面。例如:

    $item->getNewsList()->getContainer()->getPage();
    

    Doctrine 使物体水化。如果新闻列表在某些页面中(如您在帖子注释中所说),您将需要访问任何容器,然后访问页面。

    【讨论】:

    • 我之前根本没想到。我已经有了双向关系。但我没想到,我也可以走另一条路。
    猜你喜欢
    • 2011-03-28
    • 2013-11-30
    • 2014-04-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多