我有两个实体,故事和标签,它们在许多关系中相互联系。
我正在尝试获取不包含一个或多个标记(或查询、排除过滤器)的所有故事。因此,如果一个故事有标签a、b和c,我排除了标签b和e,那么这个故事就不应该被显示。当然,如果它同时有b和e两个标签,也不会有。
将版本缩减到下面的相关文件。
故事实体
<?php
declare(strict_types=1);
namespace App\Entity;
use App\Repository\StoryRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity(repositoryClass=StoryRepository::class)
*/
class Story
{
/**
* @ORM\Id()
* @ORM\GeneratedValue()
* @ORM\Column(type="integer")
*/
private int $id;
/**
* @ORM\ManyToMany(targetEntity="Tag", inversedBy="stories")
*/
private Collection $tags;
public function __construct()
{
$this->tags = new ArrayCollection();
}
public function getId(): int
{
return $this->id;
}
public function getTags()
{
return $this->tags;
}
public function addTag(Tag $tag): void
{
$this->tags->add($tag);
}
/**
* Setting tags, clearing existing ones.
*/
public function setTags($tags): void
{
$this->tags->clear();
foreach ($tags as $tag) {
if (is_a($tag, Tag::class)) {
$this->tags->add($tag);
}
}
}
public function removeTag(Tag $tag): void
{
if ($this->tags->contains($tag)) {
$this->tags->removeElement($tag);
}
}
}
标记实体
<?php
declare(strict_types=1);
namespace App\Entity;
use App\Repository\TagRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity(repositoryClass=TagRepository::class)
*/
class Tag
{
/**
* @ORM\Id()
* @ORM\GeneratedValue()
* @ORM\Column(type="integer")
*/
private int $id;
/**
* @ORM\ManyToMany(targetEntity="Story", mappedBy="tags")
*/
private Collection $stories;
public function __construct()
{
$this->stories = new ArrayCollection();
}
public function getId(): int
{
return $this->id;
}
}
因此,故事实体是拥有方。我正在使用查询生成器查询storyrepository类中的故事。
有一个有效的include特性,但它的逻辑稍有不同,所有给定的标记都必须存在以显示故事。我用这段代码实现了这一点,在哪里 $includeTags
是标记实体的数组:
$qb = $this->createQueryBuilder('s');
$qb->leftJoin('s.tags', 't');
$qb->join('s.tags', 'it', Expr\Join::WITH, 'it IN (:includeTags)');
$qb->setParameter('includeTags', $includeTags);
现在我天真地尝试了这样的方法:
$qb->leftJoin('s.tags', 'et', Expr\Join::ON, 'et NOT IN (:excludeTags)');
$qb->setParameter('excludeTags', $excludeTags);
导致 Error: Expected end of string, got 'ON'
symfony中的dql查询异常显示以下dql输出: SELECT s FROM App\Entity\Story s LEFT JOIN s.tags t LEFT JOIN s.tags et ON et NOT IN (:excludeTags)
另一次尝试是使用
$qb->andWhere($qb->expr()->notIn('s.tags', array_map(function($tag) { return $tag->getId(); }, $excludeTags)));
返回 [Semantical Error] line 0, col X near 'tags NOT IN(1)': Error: Invalid PathExpression. StateFieldPathExpression or SingleValuedAssociationField expected.
直接输入实体会在表达式处理上产生错误,将其简化为如下所示:
$qb->andWhere('s.tags NOT IN (:excludeTags)');
$qb->setParameter('excludeTags', $excludeTags);
带来与上面相同的语义错误,只是 (:excludeTags)
而不是 (1)
.
我很确定我遗漏了一些明显的东西,但我找不到任何答案,因为搜索术语时通常会将列表或实体数组(甚至只是ID数组)从另一个实体中排除,而另一个实体在这样的原则中有很多链接。我还专门搜索了标签,因为我肯定以前肯定有人做过,但我发现没有一个标签能满足我的需要。
迫不及待,我可以用sql来实现,我知道我可以排除那些相对简单的方法,但我不知道如何以“条令方式”(tm)来实现这一点,以便仍然能够从查询生成器和所有其他好处中获益。非常感谢您的帮助。
更新:我注意到我换了一行。因此,我尝试使用以下行,当然还有参数。
$qb->leftJoin('s.tags', 'et', Expr\Join::WITH, 'et NOT IN (:excludeTags)');
这会导致查询正常运行,它什么都不做,它仍然显示应该过滤掉的故事。从中生成sql,我可以清楚地看出原因:
... LEFT JOIN story_tag s5_ ON s0_.id = s5_.story_id LEFT JOIN tag t4_ ON t4_.id = s5_.tag_id AND (t4_.id NOT IN (?)) ...
非常感谢您的投入。
更新2
此带有纯sql子查询的查询按预期返回正确的数据:
SELECT id FROM story WHERE id NOT IN (SELECT story_id FROM story_tag WHERE tag_id IN (1))
我非常希望避免像使用include-tags查询那样必须执行子查询,但是如果不可能,那么我将咬紧牙关地执行这个查询。
暂无答案!
目前还没有任何答案,快来回答吧!