Я пытаюсь разбить на страницы списка сообщений блога и отфильтровать их по списку тегов, которые они могут иметь в базе данных 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