Postgresql join with Condition - sql

I have three table as follows.
CREATE TABLE users (
id uuid NOT NULL PRIMARY KEY,
password character varying(128) NOT NULL,
username character varying(15) NOT NULL,
email character varying(100) NULL,
gender character varying(1) NOT NULL
);
CREATE TABLE followers (
id bigserial NOT NULL PRIMARY KEY,
followed_at timestamp with time zone NOT NULL,
follower_id uuid REFERENCES users(id),
following_id uuid REFERENCES users(id)
);
CREATE TABLE profile_picture (
id uuid NOT NULL PRIMARY KEY ,
profile_pic character varying(100) NOT NULL,
owner_id uuid NOT NULL REFERENCES users(id),
is_active boolean NOT NULL
);
I want the query for selecting all the follower with the fields : id, username and active_profile_pic.
is_active will be true only for one profile pic of the user but he can upload as many profile photo as he want.
I have tried the without profile_pic, which is not the wanted result.
select users.id , username from users inner join followers on follower_id = users.id where followers.following_id = user_id;
I want the query for selecting all the followers with id, username and active_profile_pic who are following the user with given user_id.
User may have no profile pic and can have more than one so only the active_profile pic should be returned.
Query will have to contain the follower with no profile picture too.
Tried this, but it is not returning follower which have no profile picture. I want to return that too.
SELECT u.id, username, p.profile_pic FROM followers INNER JOIN users AS u ON u.id = follower_id
LEFT OUTER JOIN profile_picture AS p ON u.id = p.owner_id
where followers.following_id = '' and p.is_active = true
In the above query user_id signifies variable , you can specify id of the user there.
Please visit DBFIDDLE
Please suggest me the right query.

You have profile_picture.is_active test in WHERE condition, therefore your result will display only users with profile picture set. If you want all users, regardless of the profile picture, you should include profile_picture.is_active column in query results and delete the condition p.is_active = true from WHERE statement.

With the help of #random_user,
This works for me.
select id , username, profile_pic from (SELECT u.id, username, is_active, profile_pic FROM users as u Full Outer JOIN followers ON u.id = follower_id
Full Outer JOIN profile_picture AS p ON u.id = p.owner_id
where followers.following_id = '2582f93d-68c3-48e2-98ba-a401402c7b62') as profile_table where (is_active = true) or (is_active is null)
May be someone can suggest, better answer, but this works for me.
Check it at DBFIDDLE

Related

How to do an INNER JOIN without getting duplicate rows?

I have two tables. A jobs table (with job.user_id) and a users table with user.id
I am trying to select all jobs and include username from users table, but I'm getting double the number of results I should be:
async getAll(context: any) {
const all = await db.queryEntries(`
SELECT j.*, u.username FROM jobs as j, users as u
INNER JOIN jobs ON jobs.user_id = u.id ORDER BY j.created_at DESC
`);
context.response.body = all;
}
I get two of everything. How do I just add 'username' where job.user_id = user.id?
users:
CREATE TABLE users (
id string primary key,
email string unique,
hashed_password string,
created_at TEXT,
updated_at TEXT,
username TEXT,
contactme INTEGER DEFAULT 1,
phone TEXT
);
jobs:
CREATE TABLE jobs (
id STRING PRIMARY KEY,
user_id STRING NOT NULL,
title STRING NOT NULL,
description STRING NOT NULL,
pay STRING NOT NULL,
type STRING NOT NULL,
created_at TEXT,
updated_at TEXT,
contact TEXT
);
I had to remove the "FROM users":
SELECT j.*, u.username FROM jobs as j
INNER JOIN users u ON j.user_id = u.id ORDER BY j.created_at DESC
This produces only one result now.

Find top 5 famous people

I have a case in hand where I need to find the top 5 people with most likes on their posts overall.
Here's the schema:
CREATE TABLE users (
ID SERIAL PRIMARY KEY,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
username VARCHAR(30) NOT NULL,
);
CREATE TABLE posts (
id SERIAL PRIMARY KEY,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
url VARCHAR(300) NOT NULL,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
);
CREATE TABLE likes (
id SERIAL PRIMARY KEY,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
contents VARCHAR(240) NOT NULL,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
post_id INTEGER REFERENCES posts(id) ON DELETE CASCADE,
comment_id INTEGER REFERENCES comments(id) ON DELETE CASCADE,
-- 👉 either associated with post or comment 👈 --
CHECK(
COALESCE((post_id)::boolean::integer, 0) +
COALESCE((comment_id)::boolean::integer, 0) = 1
),
-- user can like post/comment once --
UNIQUE (user_id, post_id, comment_id)
);
My Attempts
Both are giving different outputs, not sure which one is correct. Also, I would appreciate an ideal (scalable) solution for this:
1.
WITH FAMOUS AS (
SELECT likes.id, users.username AS username, users.id AS user_id
FROM likes
JOIN posts ON posts.user_id = likes.post_id
JOIN users ON users.id = likes.user_id
WHERE likes.comment_id IS null
)
SELECT COUNT(*) AS num, username FROM FAMOUS
GROUP BY username
ORDER BY num DESC LIMIT 5;
2.
WITH LIKES_DATA AS (
SELECT post_id, COUNT(*) AS num_likes_per_post FROM likes
WHERE likes.comment_id IS NULL
GROUP BY post_id
)
SELECT users.username, SUM(num_likes_per_post) as num_likes
FROM LIKES_DATA
JOIN posts ON posts.id = LIKES_DATA.post_id
JOIN users ON users.id = posts.user_id
GROUP BY users.username
ORDER BY num_likes DESC LIMIT 5;
I simply do not understand the thought process for the second query.
Based on your description, I think just using JOINs and GROUP BY is sufficient:
SELECT u.username AS username, u.id AS user_id, COUNT(*)
FROM likes l JOIN
posts p
ON p.user_id = l.post_id JOIN
users u
ON u.id = l.user_id
WHERE likes.comment_id IS NULL -- don't know what this is for
GROUP BY u.username, u.id
ORDER BY COUNT(*) DESC
LIMIT 5;

User with multiple roles and multiple teams database design

I am unable to get my head around this
I want to allow users to have multiple roles and multiple teams and maybe in the future add something else.
What is the most optimal way to design this?
Users
----------
user_id
first name
last name
roles
--------
role_id
user_id (fk)
role_name
teams
-----
team_id
user_id (fk)
team name
something_else
------
selse_id
user_id (fk)
selse_name
and then group everything in one table like this?
user_profile
---------------------
user_id - username - role_name - team_name - selse_name
I don't know if this should be fine or not, because I am thinking it might get complicated when I pass the data to a form on the client side.
Your schema design is similar to how I would do this. Where your question falls apart is in the last bit about grouping everything together.
The schema should be:
create table users (
user_id serial primary key,
first_name text not null,
last_name text not null,
-- other columns here
);
create table roles (
role_id serial primary key,
role_name text not null unique,
-- other columns here
);
create table user_role (
id serial primary key,
user_id int not null references users(user_id),
role_id int not null references roles(role_id),
unique(user_id, role_id)
);
Make the lookup and join tables for teams and something_else similarly.
The tricky part is pulling this data for use in a UI. You will need to aggregate the properties or else you will have multiple rows for each user. For example, if a user has three roles, belongs to two teams, and has four something_else rows, then joining will net you twenty-four rows for that user.
Assuming your UI will be web or some modern toolkit, the best way to retrieve this data would be as json. PostgreSQL can build that json for you:
with aggs as (
select u.user_id, u.first_name, u.last_name,
jsonb_agg(DISTINCT to_jsonb(r)) as roles,
jsonb_agg(DISTINCT to_jsonb(t)) as teams,
jsonb_agg(DISTINCT to_jsonb(s)) as something_else
from users u
left join user_role ur on ur.user_id = u.user_id
left join roles r on r.role_id = ur.role_id
left join user_team ut on ut.user_id = u.user_id
left join teams t on t.team_id = ut.team_id
left join user_something_else us on us.user_id = u.user_id
left join something_else s on s.something_else_id = us.something_else_id
group by u.user_id, u.first_name, u.last_name
)
select to_jsonb(aggs)
from aggs
where user_id = ?
;

Need a query to select specific comments with the name of the writer

I have a database contains 3 tables, as following:
CREATE TABLE users
(
id SERIAL PRIMARY KEY,
name VARCHAR(30) NOT NULL,
password VARCHAR(60) NOT NULL,
phone VARCHAR(10) NOT NULL,
email VARCHAR(30) UNIQUE
);
CREATE TABLE posts
(
id SERIAL PRIMARY KEY,
body TEXT NOT NULL,
user_id INTEGER REFERENCES users(id)
);
CREATE TABLE comments
(
id SERIAL PRIMARY KEY,
body TEXT NOT NULL,
post_id INTEGER REFERENCES posts(id),
user_id INTEGER REFERENCES users(id)
-- parent_id INTEGER REFERENCES comments(id)
);
I need a query to select all the comments for one specific post using the id of this post. What made me struggling is how to select the name of the user who wrote the comment!
This is what I tried :
select
c.body as cbody, p.body as pbody, c.user_id as user_id
from
users u
inner join
posts p on u.id = p.user_id
inner join
comments c on c.post_id = p.id
where
p.id=($1)
Any help??
I would use the Comments table as the first table in the FROM clause, since that's your main table for your query data. (Just personal preference, not a requirement.)
After that, I would join the Users table on Comments.user_id to get the comments' authors.
You probably do not want the post's body included with every comment, so I would leave that out.
So my query would look something like this:
SELECT c.body AS cbody, c.user_id AS user_id, u.name AS uname
FROM Comments c LEFT JOIN Users u ON u.id = c.user_id
WHERE c.post_id=($1);
An INNER JOIN might be valid too, but you need to be sure that Comments.user_id is always filled. When Comments.user_id is NULL, the comment will not be included in the queries result when INNER JOIN is used.

Multiple selects on joined tables with group by?

I have three tables with the structures outlined below:
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
username VARCHAR(255) UNIQUE
);
CREATE TABLE posts (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT REFERENCES users(id) NOT NULL,
category BIGINT REFERENCES categories(id) NOT NULL,
text TEXT NOT NULL
);
CREATE TABLE posts_votes (
user_id BIGINT REFERENCES users(id) NOT NULL,
post_id BIGINT REFERENCES posts(id) NOT NULL
value SMALLINT NOT NULL,
PRIMARY KEY(user_id, post_id)
);
I was able to compose a query that gets each post with its user and its total value using the below query:
SELECT p.id, p.text, u.username, COALESCE(SUM(v.value), 0) AS vote_value
FROM posts p
LEFT JOIN posts_votes v ON p.id=t.post_id
JOIN users u ON p.user_id=u.id
WHERE posts.category=1337
GROUP BY p.id, p.text, u.username
But now I want to also return a column that returns the result of SELECT COALESCE((SELECT value FROM posts_votes WHERE user_id=1234 AND post_id=n), 0) for each post_id n in the above query. What would be the best way to do this?
I think an additional LEFT JOIN is a reasonable approach:
SELECT p.id, p.text, u.username, COALESCE(SUM(v.value), 0) AS vote_value,
COALESCE(pv.value, 0)
FROM posts p JOIN
users u
ON p.user_id=u.id LEFT JOIN
topics_votes v
ON p.id = t.post_id LEFT JOIN
post_votes pv
ON pv.user_id = 1234 AND pv.post_id = p.id
WHERE p.category = 1337
GROUP BY p.id, p.text, u.username, pv.value;