2017-02-15 6 views
1

Postgres verwenden Ich habe 3 Tabellen:in viele zu viele Anfragen

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); 

So hat post 1 Tags a, b und post 2 hat a als Tag.

die Frage: Wie alle Beiträge wählen, die das Tag nicht b haben, was bedeutet, es nur die post 2 wählen sollte.

Diese Abfrage hier ist nicht gut, weil es beide Beiträge gegeben wählen wird, dass post 1 2 Tags a & b hat: es

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'; 

Diese Abfrage unten funktioniert, aber ist falsch, weil, wenn es einen Tag aaaaaaab dann passt auch dazu:

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, %'; 

Ich bin auf der Suche nach einem "richtigen" und effizienten Ansatz dazu.

EDIT: Die Abfrage sollte auch Beiträge passen, die überhaupt keine Tags haben.

Antwort

2

Sie können Beiträge mit aggregierten Tags wählen Sie die Abfrage mit:

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)  

Bei entsprechenden Änderungen können Sie die Abfrage verwenden, um Ihre Daten zu filtern, zum Beispiel

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

Vielen Dank! Wie wäre es mit Beiträgen, die keine Tags haben? Logisch sollte das auch im Ergebnis liegen. Tut mir leid, ich habe noch nicht darüber nachgedacht. –

+0

Siehe die bearbeitete Antwort. – klin

0

Eine Lösung, die Unterabfragen, ist dies:

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' 
    ) 
    ) 
); 

Ich bin aber nicht sicher, wie effizient diese Abfrage ist aber, es ist definitiv nicht die lesbar.