【问题标题】:Querying a many to many relationship with filter and pagination in SQLite在 SQLite 中使用过滤器和分页查询多对多关系
【发布时间】: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

【问题讨论】:

  • 也许我的方法不好,也许我不应该尝试在单独的帖子行中返回标签...

标签: sqlite join nested


【解决方案1】:

只是给你一个想法。

select * from (
    select p.*, t.*, dense_rank() over(order by p.id desc) rnk
    from Post p
    join PostTag on p.Id = PostId
    join Tag t on TagId = t.Id
    and postid in (select postid
                     from posttag join tag t on tagid = t.id
                     where label in ('news', 'funny')
                     group by postid having count(distinct tagid) > 1)
    order by p.id desc
) ranked
where rnk <= 10;

【讨论】:

  • 这行得通,谢谢!正如我刚刚对 forpas 所说,我还需要返回与帖子 1 相关联的潜在其他标签,这适用于您的查询。不过,老实说,我很难理解它,哈哈。
  • count(*) 替换为 count(distinct postid),以防 PostTag 条目允许重复元组。我最初认为它们是不允许的,但表结构允许它们。
  • 抱歉,我不得不不接受您的回答,因为 IN 子查询永远不会返回比请求的不同标签数量更多的 postId。巧合的是,这在我非常简单的数据集上有效,但是一旦有 2 个帖子与两个标签都匹配,则只会返回其中一个。
  • 不应该将&gt; 1 改为== {number of tags queried} 吗?
  • 或大于1的if标签数。无论哪种方式。
【解决方案2】:

你可以通过一个简单的群发帖子来做到这一点,就像这样:

select p.id, p.title
from posttag pt
inner join post p on p.id = pt.postid
inner join tag t on t.id = pt.tagid
where t.label in ('news', 'funny')
group by p.id, p.title
having count(distinct t.id) = 2
order by p.id limit 10

请参阅demo

【讨论】:

  • 这几乎没问题,但我需要检索与帖子关联的所有标签(并在之后进行重复数据删除)。顺便说一句,如果帖子 1 附加了另一个标签,比如“2019”,它也应该在结果中。 @tonypdmtr 的解决方案适用于该约束。
  • 我正在尝试选择 10 个同时具有“新闻”和“有趣”标签的帖子这是你问的,这就是这段代码的作用。
  • 好吧,我的错,我没有明确提到我需要在结果中返回每个帖子的单独标签。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-04-16
  • 2021-12-03
  • 2013-09-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多