I have something like Posts in FB/instagram. When a user searches for something, the input query is used to either find Posts that either have this query in their content, OR – one or more of post’s hashtags match this query.
CREATE TABLE posts (
id int UNIQUE NOT NULL generated always as identity,
content text,
content_tokens tsvector,
PRIMARY KEY (id)
);
CREATE TABLE tags (
id int UNIQUE NOT NULL generated always as identity,
tag_title text,
PRIMARY KEY (id)
);
CREATE TABLE post_tags(
post_id int,
tag_id int,
PRIMARY KEY (post_id, tag_id),
FOREIGN KEY (post_id) references posts (id) ON DELETE CASCADE,
FOREIGN KEY (tag_id) references tags (id) ON DELETE CASCADE
);
-- query to find all "relevant" posts :
SELECT posts.id, posts.title, posts.description, tags.id as tag_id, tags.title as tag_title
FROM posts
JOIN post_tags
ON posts.id = post_tags.post_id
JOIN tags
ON tags.id = post_tags.tag_id
WHERE tags.title ilike 'user_query'
OR posts.content_tokens @@ plainto_tsquery('user_query:*');
The problem :
I can’t figure out how to have Posts returned with a nested array of all its pertinent Tags.
So for example Post -> {post_id: 123 , tags : [{id: 1, title: a}, {id: 2, title : b}, ...]}
Also, it results in duplicate records since I can’t GROUP BY only by one column (post_id).
Examples:
posts tables has:
id content
------------------------
1 this is a test
2 this is a test 2
3 this is user_query
tags tables has:
id title
---------------
1 user_query
2 something
3 hey
post_tags has:
post_id tag_id
------------------
1 1
1 2
2 2
3 2
3 3
So in this example, post_id = 1 has the “user_query” inside one of its tags, and post_id = 3 has the “user_query” inside its content. So, both of them should be returned.
[
{"post_id" : 1, "tags" : [{"id" : 1, "title": "user_query"}, {"id" : 2, "title" : "something"}]},
{"post_id" : 3, "tags" : [{"id" : 3, "title": "hey"}]}
]
Advertisement
Answer
Do you want JSON aggrgation?
SELECT p.id, p.title, p.description,
JSONB_AGG(JSONB_BUILD_OBJECT('id', t.id, 'title', t.title)) as tags
FROM posts p
INNER JOIN post_tags pt ON p.id = pt.post_id
INNER JOIN tags t ON t.id = pt.tag_id
GROUP BY p.id