Запрос отношения «многие ко многим» с фильтром и разбиением на страницы в SQLite - PullRequest
1 голос
/ 23 июня 2019

Я пытаюсь разбить на страницы списка сообщений блога и отфильтровать их по списку тегов, которые они могут иметь в базе данных SQLite.

Сообщения и теги имеют отношение n-to-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 сообщений, имеющих оба тега 'news' и «смешно», поэтому я хотел бы, чтобы только «Заголовок сообщения 1» был возвращен (отредактируйте для уточнения: мне нужно, чтобы сообщение 1 было возвращено дважды здесь, один раз с тегом «новости» и один раз с тегом «смешно»).

Я использую DENSE_RANK, чтобы на самом деле иметь 10 разных сообщений в результатах, даже если объединение может вернуть более 10 строк.

У меня есть проблема, как управлять оператором 'AND' для теговзначения, т.е. не возвращать сообщения, которые имеют только один из тегов.Поэтому здесь я не хотел бы, чтобы пост 3 возвращался, поскольку в нем есть только тег «забавный», а не тег «новости».

Вот мой лучший запрос (обновлен ниже), который будет возвращать сообщенияс 'news' или 'funny', что не то, что я хочу:

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

Ответы [ 2 ]

1 голос
/ 23 июня 2019

Просто чтобы дать вам одну идею.

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;
0 голосов
/ 23 июня 2019

Вы можете сделать это с помощью простой группы по почте, например:

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

См. Демоверсию .

...