【发布时间】:2019-06-24 05:23:03
【问题描述】:
我正在尝试对博客文章列表进行分页,并根据它们在 SQLite 数据库中可能具有的标签列表过滤这些文章。
帖子和标签具有 n 对 n 关系,因此我创建了一个 PostTag 关系表。
CREATE TABLE "Post" (
"Id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
"Title" TEXT
);
CREATE TABLE "Tag" (
"Id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
"Label" TEXT
);
CREATE TABLE "PostTag" (
"Id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
"PostId" INTEGER,
"TagId" INTEGER,
FOREIGN KEY("PostId") REFERENCES "Post"("Id"),
FOREIGN KEY("TagId") REFERENCES "Tag"("Id")
);
给定以下数据
INSERT INTO Post (Title) VALUES ('Post title 1'), ('Post title 2'), ('Post title 3');
INSERT INTO Tag (Label) VALUES ('news'), ('funny'), ('review');
INSERT INTO PostTag (PostId, TagId) VALUES (1, 1), (1, 2), (2, 3), (3, 2), (3, 3);
我正在尝试选择 10 个同时具有标签“新闻”和“有趣”的帖子,所以我希望只返回“帖子标题 1”(编辑澄清:我需要发布1 在这里被返回两次,一次带有“新闻”标签,一次带有“有趣”标签)。
我使用 DENSE_RANK 实际上在结果中有 10 个不同的帖子,即使连接可能返回超过 10 行。
我遇到的问题是如何管理标签值上的“AND”运算符,即不返回只有一个标签的帖子。所以在这里我不希望帖子 3 被退回,因为它只有“有趣”标签,而不是“新闻”标签。
这是迄今为止我最好的查询(在下面更新),它将返回包含“新闻”或“有趣”的帖子,这不是我想要的:
SELECT * FROM (
SELECT p.*, t.*, DENSE_RANK() OVER(order by p.id desc) rnk
FROM Post p
JOIN PostTag pt ON p.Id = pt.PostId
JOIN Tag t ON pt.TagId = t.Id AND t.Label IN ('news', 'funny')
ORDER BY p.id desc
) ranked
WHERE rnk <= 10
请注意,我之后会使用 dapper 对结果进行重复数据删除和重新分组,因此每个帖子出现多次并不是真正的问题(请阅读下面的更新以获取更多详细信息)。
更新:
查询必须返回与其关联标签数量相同的匹配帖子(即使这些标签可能不在查询的标签中),例如:
Id Title Id:2 Label rnk
1 'Post Title 1' 1 'news' 1
1 'Post Title 1' 2 'funny' 1
如果稍后,有人像这样在帖子 1 中添加标签:
INSERT INTO Tag (Label) VALUES ('tech'); -- id is 4
INSERT INTO PostTag (PostId, TagId) VALUES (1, 4);
查询的结果应该是
Id Title Id:2 Label rnk
1 'Post Title 1' 1 'news' 1
1 'Post Title 1' 2 'funny' 1
1 'Post Title 1' 4 'tech' 1
所以我可以显示匹配的帖子及其所有标签,即使标签不在查询中。
我终于有了一些工作,但它是可怕的嵌套,我真诚地想知道为什么这个问题最终会变得如此复杂。没有办法直接计算排名吗?
select * from (
select *, dense_rank() over(order by p.id desc) rnk
from Post p
join PostTag pt on p.Id = pt.PostId
join Tag t on pt.TagId = t.Id
and postId in (
select postId from (
select dense_rank() over(order by pt2.PostId) rnk2,
from PostTag pt2
join Tag t2 on pt2.TagId = t2.Id
where t2.Label in ('news', 'funny')
)
group by rnk2
having count(rnk2) == 2 -- 2 being the number of tags requested
) order by p.id desc
)
ranked where rnk <= 10
【问题讨论】:
-
也许我的方法不好,也许我不应该尝试在单独的帖子行中返回标签...