Complicated Join Query, Join 3 tables with multiple group bys - sql

I have 3 tables:
Tweets:
CREATE TABLE tweets (
text_content VARCHAR(280) not null,
username VARCHAR(50) not null,
timestamp TIMESTAMP not null DEFAULT current_timestamp,
id UUID not null DEFAULT uuid_generate_v4(),
CONSTRAINT tweets_pk PRIMARY KEY (id)
);
Likes:
CREATE TABLE likes (
username VARCHAR(50) not null,
timestamp TIMESTAMP not null default current_timestamp,
post_id UUID not null,
CONSTRAINT likes_pk PRIMARY KEY (username, post_id),
CONSTRAINT likes_post_id_fk FOREIGN KEY (post_id) REFERENCES tweets(id)
);
And Retweets
CREATE TABLE retweets (
username VARCHAR(50) not null,
timestamp TIMESTAMP not null default current_timestamp,
post_id UUID not null,
CONSTRAINT retweets_pk PRIMARY KEY (username, post_id),
CONSTRAINT retweets_post_id_fk FOREIGN KEY (post_id) REFERENCES tweets(id)
);
I need a query, that would select all tweets, along with the amount of likes and retweets they have.
I did manage to write a working query, but I think I over-complicated it, and would love to hear simpler solutions!

You want to aggregate before joining. Assuming the join key is post_id:
select t.*, l.likes, r.retweets
from tweets t left join
(select post_id, count(*) as likes
from likes
group by post_id
) l
on l.post_id = t.id left join
(select post_id, count(*) as retweets
from retweets
group by post_id
) r
on r.post_id = t.id;

Related

SQLite: Get Output From Two Tables Using Common Reference ID

I am new in SQLite and i have been working on an issue for quite a long time.
Lets say we have 2 database table say tbl_expense and tbl_category. Please find below the following table structure.
tbl_category
CREATE TABLE IF NOT EXISTS tbl_category(
category_id INTEGER PRIMARY KEY AUTOINCREMENT,
category_name VARCHAR(20) DEFAULT NULL,
category_desc VARCHAR(500) DEFAULT NULL,
category_icon VARCHAR(100) DEFAULT NULL,
category_created timestamp default CURRENT_TIMESTAMP
)
tbl_expense
CREATE TABLE IF NOT EXISTS tbl_expense(
expense_id INTEGER PRIMARY KEY AUTOINCREMENT,
expense_name VARCHAR(20) DEFAULT NULL,
expense_desc VARCHAR(500) DEFAULT NULL,
expense_type VARCHAR(20) DEFAULT NULL,
expense_amt DECIMAL(6.3) DEFAULT NULL,
expense_date TIMESTAMP DEFAULT NULL,
expense_category INTEGER DEFAULT NULL,
expense_created_date timestamp DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (expense_category) REFERENCES tbl_category(category_id)
ON DELETE SET NULL
)
Assume we have data in the tables like this below.
Expected Output:
Assure we have category_id and expense_category as common fields. How can i create an SQL Query where i can list all categories and sum of their expense amount as follows.
Please help me on this issue.
You need an INNER join of the tables and aggregation:
SELECT c.category_name Category,
SUM(e.expense_amt) Amount
FROM tbl_category c INNER JOIN tbl_expense e
ON e.expense_category = c.category_id
GROUP BY c.category_id;
If you want all categories from the table tbl_category, even those that are not present in tbl_expense, use a LEFT join and TOTAL() aggregate function:
SELECT c.category_name Category,
TOTAL(e.expense_amt) Amount
FROM tbl_category c LEFT JOIN tbl_expense e
ON e.expense_category = c.category_id
GROUP BY c.category_id;

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;

is it possible in SQL to call two difrent table with difrent data at the same time

so basicly what i need is to get two tables at the same time and then use a condition that reside in the first table and i want to apply it to the second one
if it not possible do i need to call SELECT Twice ?
is this code right ?
to determine the average rating of all movies released in 2012
SELECT AVG(rating),year FROM ratings , movies
WHERE year = 2012;
//these are the tables that i have
CREATE TABLE movies (
id INTEGER,
title TEXT NOT NULL,
year NUMERIC,
PRIMARY KEY(id)
);
CREATE TABLE stars (
movie_id INTEGER NOT NULL,
person_id INTEGER NOT NULL,
FOREIGN KEY(movie_id) REFERENCES movies(id),
FOREIGN KEY(person_id) REFERENCES people(id)
);
CREATE TABLE directors (
movie_id INTEGER NOT NULL,
person_id INTEGER NOT NULL,
FOREIGN KEY(movie_id) REFERENCES movies(id),
FOREIGN KEY(person_id) REFERENCES people(id)
);
CREATE TABLE ratings (
movie_id INTEGER NOT NULL,
rating REAL NOT NULL,
votes INTEGER NOT NULL,
FOREIGN KEY(movie_id) REFERENCES movies(id)
);
CREATE TABLE people (
id INTEGER,
name TEXT NOT NULL,
birth NUMERIC,
PRIMARY KEY(id)
);
I think you want a JOIN and an aggregate function:
SELECT AVG(r.rating) avg_rating_2012
FROM ratings r
INNER JOIN movies m on m.id = r.movie_id
WHERE m.year = 2012;
If you want this for all years at once, then use GROUP BY:
SELECT m.year, AVG(r.rating) avg_rating
FROM ratings r
INNER JOIN movies m on m.id = r.movie_id
GROUP BY m.year;

Is there a way to implement count in left join

I'm setting up a chat-Group application using a simple database. I want to know if there is a way for me to see the people that liked/disliked a certain post, which can have a picture or a video attached to it. The important information I want to know is the post ID, message of the post, date of the post, url of the picture I want to post, url of the video, and the id of the user posting said post. I'm using left joins to see all this information, but I'm missing if the posts have likes or dislikes.
I already tried using the aggregate count to see each like and dislike of the posts, but I don't know how to mix this query with the previous information.
This is my query to see the information listed above:
select P.post_ID, P.post_msg, P.post_date, F.photo_url,V.video_url, P.user_ID
from ((Post as P left join Photo as F on P.post_ID = F.post_ID ) left join Video as V
on P.post_ID = V.post_ID)
where P.chat_ID = 1
Here is the output of the Query:
Here is an example of the output I want:
The query above displays everything I'm looking for except each like and dislike of each post. Is there anyway to see this information mixed with what I want?
Here is the structure of the DataBase:
create table Login(login_ID serial primary key, login_Date DATE NOT NULL DEFAULT CURRENT_DATE, user_name varchar(20) NOT NULL);
create table Users(user_ID serial primary key, user_name varchar(20) NOT NULL, user_password varchar(20) NOT NULL);
create table Contact_List(contactlist_ID serial primary key, user_name varchar(20) NOT NULL,user_ID integer references Users(user_ID) on delete cascade);
create table Person(person_ID serial primary key, person_name varchar(20) NOT NULL, person_lastname varchar(30) NOT NULL, person_phone varchar(12), person_email varchar(50) NOT NULL, user_ID INTEGER references Users(user_ID) on delete cascade);
create table Admin(admin_ID serial primary key, user_ID INTEGER references Users(user_ID) on delete cascade);
create table Chat_Group(chat_ID serial primary key, chat_name varchar(50) NOT NULL, admin_Id INTEGER references Admin(admin_ID) on delete cascade);
create table Group_List(user_ID INTEGER references Users(user_ID) on delete cascade, chat_ID integer references Chatgroup(chat_id) on delete cascade, primary key(user_id, chat_id));
create table Post(post_ID serial primary key, post_msg varchar(280), post_date DATE NOT NULL DEFAULT CURRENT_DATE, user_ID INTEGER references Users(user_ID) on delete cascade, chat_ID INTEGER references Chatgroup(chat_ID) on delete cascade);
create table Video(video_ID serial primary key, video_url varchar(280) NOT NULL, post_ID INTEGER references Post(post_ID) on delete cascade);
create table Photo(photo_ID serial primary key, photo_url varchar(280) NOT NULL, post_ID INTEGER references Post(post_ID) on delete cascade);
create table Reply(reply_ID serial primary key, reply_msg varchar(280) NOT NULL, reply_Date DATE NOT NULL DEFAULT CURRENT_DATE, post_ID INTEGER references Post(post_ID) on delete cascade, user_ID INTEGER references Users(user_ID) on delete cascade);
create table HashTag(hashtag_ID serial primary key, hashtag_msg varchar(200) NOT NULL,post_ID INTEGER references Post(post_ID) on delete cascade NOT NULL, user_ID INTEGER references Users(user_ID) on delete cascade);
create table Reaction(reaction_ID serial primary key,reaction_date DATE NOT NULL DEFAULT CURRENT_DATE, reaction_like BOOLEAN, reaction_dislike BOOLEAN, post_ID INTEGER references Post(post_ID) on delete cascade, user_ID INTEGER references users(user_ID) on delete cascade);
create table React_to(reaction_ID INTEGER references Reaction(reaction_ID) on delete cascade, reply_ID INTEGER references Reply(reply_ID) on delete cascade, primary key(reaction_ID,reply_ID));
Excuse me for my bad english, english is not my main language.
One way of doing this would use correlated subquery.
SELECT p.post_id,
p.post_msg,
p.post_date,
f.photo_url,
v.video_url,
p.user_id,
(SELECT count(*)
FROM reaction r
WHERE r.post_id = p.post_id
AND r.reaction_like) likes,
(SELECT count(*)
FROM reaction r
WHERE r.post_id = p.post_id
AND r.reaction_dislike) dislikes
FROM post p
LEFT JOIN photo f
ON p.post_id = f.post_id
LEFT JOIN video v
ON p.post_id = v.post_id
WHERE p.chat_id = 1;
However I wonder if a reaction can be both a like and a dislike at once. At least your data model allowes such a thing...
I don't like the idea of going to the reactionss table twice. One way to avoid this is to use a lateral join:
select P.post_ID, P.post_msg, P.post_date, F.photo_url, V.video_url, P.user_ID,
r.num_likes, r.num_dislikes
from Post P left join
Photo F
on P.post_ID = F.post_ID left join
Video V
on P.post_ID = V.post_ID left join lateral
(select sum(r.reaction_like) as num_likes,
sum(r.reaction_dislike) as num_dislikes
from reactions r
where r.post_id = p.post_id
) r
on 1=1
where P.chat_ID = 1

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;