2017-02-15 7 views
1

Использование Postgres У меня есть 3 таблицы:Исключить тег поста во многих многих запросов

CREATE TABLE post (id SERIAL, body TEXT); 
CREATE TABLE tag (id SERIAL, name TEXT); 
CREATE TABLE post_tag (post_id INT, tag_id INT); 

INSERT INTO post(body) values('post 1'); 
INSERT INTO post(body) values('post 2'); 
INSERT INTO tag(name) values('a'); 
INSERT INTO tag(name) values('b'); 
INSERT INTO post_tag values(1, 1); 
INSERT INTO post_tag values(1, 2); 
INSERT INTO post_tag values(2, 1); 

Таким образом post 1 имеет теги a, b и post 2 имеет a в качестве тега.

Вопрос: Как выбрать все сообщения, которые не имеют тег b, то есть он должен выбрать только post 2.

Этот запрос здесь не хорошо, потому что она будет выбирать обе должности, учитывая, что post 1 имеет 2 метки a & b:

SELECT post.* 
FROM post 
JOIN post_tag ON post_tag.post_id = post.id 
JOIN tag ON tag.id = post_tag.tag_id 
WHERE tag.name != 'b'; 

Этот запрос ниже работает, но не так, потому что, если есть тег aaaaaaab затем его будет соответствовать этому также:

SELECT post.id, post.body, string_agg(tag1.name, ', ') 
FROM post 
JOIN post_tag ON post_tag.post_id = post.id 
JOIN tag ON tag.id = post_tag.tag_id 
GROUP BY post.id, post.body 
HAVING string_agg(tag.name, ', ') not like '%b, %'; 

Я ищу «правильный» и эффективный подход к этому.

EDIT: Запрос также должен совпадать с сообщениями, у которых нет каких-либо тегов вообще.

ответ

2

Вы можете выбрать сообщения с агрегированными тегами с помощью запроса:

select p.id, p.body, array_agg(t.name) tags 
from post p 
left join post_tag pt on pt.post_id = p.id 
left join tag t on pt.tag_id = t.id 
group by 1, 2; 

id | body | tags 
----+--------+------- 
    1 | post 1 | {a,b} 
    2 | post 2 | {a} 
(2 rows)  

С адекватными изменениями вы можете использовать запрос для фильтрации данных, например,

select p.id, p.body 
from post p 
left join post_tag pt on pt.post_id = p.id 
left join tag t on pt.tag_id = t.id 
group by 1, 2 
having 'b' <> all(array_agg(t.name)); 
-- or to get also posts without tags: 
-- having 'b' <> all(array_agg(t.name)) or array_agg(t.name) = '{null}'; 

id | body 
----+-------- 
    2 | post 2 
(1 row) 
+0

Спасибо! Как насчет сообщений, у которых нет тегов? Логично также должно быть в результате. Извините, я не думал об этом раньше. –

+0

См. Отредактированный ответ. – klin

0

Одно из решений, с помощью подзапросов заключается в следующем:

SELECT * 
FROM post 
WHERE post.id IN (
    SELECT post_id 
    FROM post_tag 
    WHERE post_id != ALL (
    SELECT post_id 
    FROM post_tag 
    WHERE tag_id = (
     SELECT id 
     FROM tag 
     WHERE name = 'b' 
    ) 
    ) 
); 

Я не уверен, однако, насколько эффективно этот запрос, хотя, это определенно не самый читаемый.

Смежные вопросы