我在 SQL 查询中遇到了一个反复出现的问题,我无法通过原始 SQL 或 Django ORM 优雅地解决它,现在在 EntityFramework 中也面临同样的问题。这可能是一个常见的问题,有自己的名字,但我不知道它的名字。
假设我有两个表之间的简单外键关系,例如:
Book 1 <- * Tag
一本书有多个标签,每个标签只属于一本书,即Tag表有一个外键指向Book表。
现在,我想找到所有含有"Tag1"和"Tag2"的书。
原始SQL语句
我可以进行多次连接操作。
SELECT * FROM books
JOIN tags t1 on tags.book_id = books.id
JOIN tags t2 on tags.book_id = books.id
WHERE t1.tag = 'Tag1' AND t2.tag = 'Tag2'
很好,这可以实现,但看起来不太性能优越。
Django
在Django中,我可以进行类似的操作。
Book.objects.filter(tags__tag="Tag1").filter(tags__tag="Tag1")
EntityFramework LINQ
如果像这样更改过滤器,将会导致额外的连接,类似于原始的SQL版本中的情况。
我尝试链接 .Where()
类似于更改Django的 .filter()
,但是它没有相同的结果。 它将构建一个类似以下的查询,这当然会返回空,因为没有行的标签是两个不同的字符串。
SELECT * FROM books
JOIN tags t1 on tags.book_id = books.id
WHERE t1.tag = 'Tag1' AND t1.tag = 'Tag2'
总结
我想我可以使用数组聚合来将标签聚合成一个数组,然后进行比较,但是这似乎也很耗费资源。而且,聚合和分组也会影响到事物的排序,这就迫使我要使用子查询来得到我想要的顺序。
我绝对不是SQL方面的专家,正如你所看到的那样,但我希望能做到以下两点:
- 通过LINQ模仿上面愚蠢无意义的操作
- 找到一种更加优雅的方法,让我能够实现自己的需求,并与任何ORM兼容
额外的废话
当我需要查找拥有“所有”列表中标签的书籍时,这才是棘手的部分...如果是“任何一个”或“特定的一个”,那就简单了。
编辑:使用数组和重叠解决方案
在Postgres中,我们可以使用 array_agg
将所有相关标签聚合成一个数组,像这样:
SELECT * FROM books
JOIN tags t1 on tags.book_id = books.id
;
+--------+-------+------+
| BookId | Name | Tag |
+--------+-------+------+
| 1 | BookA | Tag1 |
| 1 | BookA | Tag2 |
| 1 | BookA | Tag3 |
| 2 | BookB | Tag1 |
| 2 | BookB | Tag3 |
+--------+-------+------+
SELECT books.BookId, Name, array_agg(t1.tags) as tags
FROM books
JOIN tags t1 on tags.book_id = books.id
GROUP BY BookId
ORDER BY BookId
;
+--------+-------+--------------------+
| BookId | Name | tags |
+--------+-------+--------------------+
| 1 | BookA | [Tag1, Tag2, Tag3} |
| 2 | BookB | {Tag1, Tag3} |
+--------+-------+--------------------+
有了这样,我就可以使用数组“包含”运算符来查找标记(tag
)与预期集合重叠的行:WHERE tags @> ('Tag1', 'Tag2')
。
这也是一个可行的选项。它进行聚合而不是过度连接。但不确定使用 LINQ 查询会看起来像什么。
tag_list=['demo', 'test']
也许是这个:Book.objects.filter(tags__tag__in=tag_list)
。 - Hemal Patel