php - 排除多对多关系中标记故事中的标签列表
问题描述
我有两个实体,Story 和 Tag,它们以多对多关系链接。
我正在尝试获取所有不包含一个或多个标签(或查询、排除过滤器)的故事。因此,如果一个故事有标签 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;
}
}
所以 Story 实体是拥有方。我正在使用查询生成器来查询 StoryRepository 类中的故事。
有一个有效的包含功能,但逻辑略有不同,所有给定的标签都必须存在才能显示故事。我正在使用这段代码来实现这一点,其中$includeTags
是一个 Tag 实体数组:
$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 数组)从另一个像这样在 Doctrine 中链接的多对多实体. 还专门搜索了标签,因为我确定其他人之前一定已经这样做过,但我发现没有一个能与我在这里需要的东西相近。
按下,我可以只用 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))
我非常想避免做一个子查询,因为我可以使用包含标签查询,但如果不可能,那么我就咬紧牙关。
解决方案
好的,我也是在 Stackoverflow 上偶然偶然发现的,请参见此处:教义 2 中的子查询 notIN 函数
解决方案是以下几行,提醒一下,这$excludeTags
是一个标签实体数组。
$qb->andWhere(':excludeTags NOT MEMBER OF s.tags');
$qb->setParameter('excludeTags', $excludeTags);
现在这有效,但它也创建了一个子选择:
... AND NOT EXISTS (SELECT 1 FROM story_tag s4_ WHERE s4_.story_id = s0_.id AND s4_.tag_id IN (?)) ...
我现在会使用它,但如果有人有更好的解决方案来回避子查询,请告诉我,我很乐意选择你的答案。
推荐阅读
- reactjs - 使用 lerna 和 pm2 为 React / Next / express / typescript 项目设置 monorepo
- terraform - Terraform aws_lb_listener_rule 重定向规则:我可以重写 #{path} 变量吗?
- reactjs - npx create-react-app 在 big sur 上执行 yarnpkg 时失败
- python - worker 0 上的内存分配错误:std::bad_alloc: CUDA 错误
- python - 如何通过字典在熊猫中创建行
- javascript - 如何在 Wordpress 的 Ajax 回调中获取帖子 ID
- docker - 在 openthread/otbr docker 容器中启动 otbr-agent 失败
- python - Seaborn:如何在热图中设置 x 轴的最小值和最大值
- flutter - 即使您共享一个应用程序,它是否只有一个 SHA-1 密钥?
- go - 如何从gorm中的many2many表中获取对象