Join the same table based on a same column - sql

I have two tables users and articles. The modified_by column in the Books table holds the corresponding users id who modified it. The user_id column in the Books table is the author. Both user_id and modified_by of the books table look to the id in the users table.
create table USERS (id int, name varchar(55));
insert into USERS values
( 1, 'person1'),
( 2, 'person2');
create table BOOKS (id int, user_id int, modified_by int);
insert into BOOKS values
(1, 2, 2),
(2, 2, 2),
(3, 2, 2),
(4, 1, 2);
My task is to display the books with name of the user in both the modified_by and user_id columns after joining. So far I have joined and got the name in the user_id column. How can I do the same for the modified_by column as it currently just shows the id and not the name
Current query
SELECT
books.id,
books.user_id,
users.name,
FROM
books
INNER JOIN users ON
books.user_id = users.id
Result required:
(1, person2, person2),
(2, person2, person2),
(3, person2, person2),
(4, person1, person2);

You can use join the users table twice as
SELECT b.id, u1.name AS user_id, u2.name AS modified_by
FROM books b
JOIN users u1
ON b.user_id = u1.id
JOIN users u2
ON b.modified_by = u2.id
Demo
Indeed, it's better to use LEFT OUTER JOIN instead of INNER JOIN if existence of non-matching values among tables is the case

You can join the users table twice :
SELECT
b.id,
u1.name as `user`,
u2.name as modified_by
FROM
BOOKS b
INNER JOIN USERS u1 ON b.user_id = u1.id
INNER JOIN USERS u2 ON b.modified_by = u2.id;
Try it here : https://dbfiddle.uk/I1KzRNSs

I think what you're looking for would be this. I haven't done SQL in a while so there could be some syntax errors :), but it should be in the ballpark.
SELECT
b.id,
u1.name,
u2.name
FROM
books as b
INNER JOIN users as u1 ON
b.user_id = u1.id
INNER JOIN users as u2 ON
b.modified_by = u2.id

Related

Figure out the total number of people in an overlapping er database

I am trying to find:
the total number of doctors which aren't patients
the total number of patients which aren't doctors
the total number of people who are both patients and doctors
I can't seem to get the correct answer.
SQL:
CREATE TABLE persons (
id integer primary key,
name text
);
CREATE TABLE doctors (
id integer primary key,
type text,
FOREIGN KEY (id) REFERENCES persons(id)
);
CREATE TABLE patients (
id integer primary key,
suffering_from text,
FOREIGN KEY (id) REFERENCES persons(id)
);
INSERT INTO persons (id, name) VALUES
(1, 'bob'), (2, 'james'), (3, 'bill'), (4, 'mark'), (5, 'chloe');
INSERT INTO doctors (id, type) VALUES
(2, 'family doctor'), (3, 'eye doctor'), (5, 'family doctor');
INSERT INTO patients (id, suffering_from) VALUES
(1, 'flu'), (2, 'diabetes');
Select statement:
select count(d.id) as total_doctors, count(pa.id) as total_patients, count(d.id) + count(pa.id) as both_doctor_and_patient
from persons p
JOIN doctors d
ON p.id = d.id
JOIN patients pa
ON p.id = pa.id;
http://www.sqlfiddle.com/#!17/98ae9/2
One option uses left joins from persons and conditional aggrgation:
select
count(dr.id) filter(where pa.id is null) cnt_doctor_not_patient,
count(pa.id) filter(where dr.id is null) cnt_patient_not_doctor,
count(pa.id) filter(where do.id is not null) cnt_patient_and_doctor,
count(*) filter(where dr.id is null and pa.id is null) cnt_persons_not_dotor_nor_patient
from persons pe
left join doctors dr on dr.id = pe.id
left join patients pa on pa.id = pe.id
As a bonus, this gives you an opportunity to count the persons that are neither patient nor doctor. If you don't need that information, then a full join is simpler, and does not require bringing the persons table:
select
count(dr.id) filter(where pa.id is null) cnt_doctor_not_patient,
count(pa.id) filter(where dr.id is null) cnt_patient_not_doctor,
count(pa.id) filter(where dr.id is not null) cnt_patient_and_doctor
from doctors dr
full join patients pa using (id)
You can simply solve this using LEFT JOIN like:
--Aren't doctors:
SELECT count(*) from persons as A left join doctors as B on A.id=B.id where B.id is null
--Aren't patients:
SELECT count(*) from persons as A left join patients as B on A.id=B.id where B.id is null
--Both:
SELECT
(SELECT count(*) from persons as A left join patients as B on A.id=B.id where B.id is not null) +
(SELECT count(*) from persons as A left join doctors as B on A.id=B.id where B.id is not null)
AS summ
Here a CTE alternative:
with doc_not_pat
as(
select count(*) as Doc_Not_Pat
from doctors d
where not exists (select 1 from patients p where p.id = d.id)
),
pat_not_doc as(
select count(*) as Pat_Not_Doc
from patients p
where not exists ( select 1 from doctors d where d.id = p.id)
),
pat_and_doc as(
select count(*) as Pat_And_Doc
from patients p
where exists (select 1 from doctors d where d.id = p.id)
)
select (select Doc_Not_Pat
from doc_not_pat dcp) as Doc_Not_Pat,
(select Pat_Not_Doc
from pat_not_doc) as Pat_Not_Doc,
(select Pat_And_Doc
from pat_and_doc) as Pat_And_Doc

SQL tricky left join of three tables - all different keys

Originally I've been trying to merge users_role_change_log table with users_schedule_log. In the first I've got only user id, and in second there is only full name. So no clear hit.
I've tried to introduce third table which could tie those two together - users table. There is users id, but full name is splitted in two columns first_name and second_name. So that further complicates things. Not my design.
Was hoping that I could improvise in concating ad hoc first_name and last_name from main users table and passing it as a relevant parameter for left join.
SELECT r.*,CONCAT(first_name, ' ', last_name) AS u.name
FROM `users_role_change_log` r
LEFT JOIN users u ON r.id = u.id
LEFT JOIN users_schedule_log e ON u.name = e.name
But this doesn't work.
Any recommendation?
Edit:
#1 added table examples:
a) users_role_change_log
id user_id role time_changed
34 60 19 '2020-07-05 05:30:00'
b) users_schedule_log
id name shift_start shift_end
9 'John Doe' '2020-07-05 07:00:00' '2020-07-05 15:00:00'
c) users
id first_name last_name
60 'John' 'Doe'
d)what I want to end up with
a)user_id b)name a)role a)time_changed b)shift_start b)shift_end
60 'John Doe' 19 ...
You must start the joins from users and the left join users_role_change_log and users_schedule_log on the correct columns:
SELECT u.id, CONCAT(u.first_name, ' ', u.last_name) name,
r.role, r.time_changed, s.shift_start, s.shift_end
FROM users u
LEFT JOIN users_role_change_log r ON r.user_id = u.id
LEFT JOIN users_schedule_log s ON s.name = CONCAT(u.first_name, ' ', u.last_name)
See the demo.
You could try it this way:
SELECT *
FROM `users_role_change_log` r
LEFT JOIN users u ON r.id = u.id
LEFT JOIN users_schedule_log e ON CONCAT(u.first_name, ' ', u.last_name) = e.name
or this way:
WITH user_connector as (select CONCAT(first_name, ' ', last_name) as name, id from users)
SELECT *
FROM `users_role_change_log` r
LEFT JOIN user_connector u ON r.id = u.id
LEFT JOIN users_schedule_log e ON u.name = e.name
Of course you shoud change the asterisk to the fields you want to select.
You can use the subquery for this. Hope to help, my friend :))
create table users_role(id int, user_id int, role int, time_changed datetime)
create table users_schedule(id int, name varchar(20), shift_start datetime, shift_end datetime)
create table users(id int, first_name varchar(20), last_name varchar(20))
insert into users_role
values(34, 60, 19, '2020-07-05 05:30:00')
insert into users_schedule
values(9, 'John Doe', '2020-07-05 07:00:00', '2020-07-05 15:00:00')
insert into users
values(60, 'John', 'Doe')
-----
Select *
FROM (SELECT r.*, CONCAT(u.first_name, ' ', u.last_name) AS Name
FROM users_role r
LEFT JOIN users u ON r.user_id = u.id )as t
LEFT JOIN users_schedule e ON t.Name = e.name
Find the answer in this fiddle. You need to construct a subquery for your users table where you CONCAT the columns and then proceed with the JOIN.
You might need to consider the database schema, since the table users_schedule_log can use the ID of the user rather than his name (same name problem).
SELECT r.user_id, u.name, r.role, r.time_Changed, u.shift_start, u.shift_end
FROM users_role_change_log AS r
LEFT JOIN (SELECT id, CONCAT(first_name,' ',last_name) AS name FROM users)A
ON r.user_id = A.id
LEFT JOIN users_schedule_log AS u
ON u.name = A.name

SQL Query with Aggregate function on Left Join of One-to-Many Relationship

I have a one-to-many relationship where each user has many tasks, which are rated by difficulty. I want to query for a list showing each user once along with their most difficult task.
users table: user_id, username
tasks table: user_id, task_id, taskname, difficulty
I've tried a query like
SELECT u.user_id, u.username, t.task_id, t.taskname, MAX(t.difficulty)
FROM users u
LEFT JOIN tasks t ON u.user_id = t.user_id
GROUP BY u.user_id
However, I get the not in GROUP BY clause error when running this.
Assuming one user doesn't have two tasks with the same max difficulty, you can do something like this. Although, this isn't very performant. It will work fine on a small dataset, but should be redesigned if your dataset is very large at all. Hopefully it will get you pointed in the right direction.
declare #users table (user_id int, username varchar(10))
declare #tasks table (task_id int, user_id int, taskname varchar(24), difficulty int)
insert into #users values
(1, 'John'),
(2, 'Sally'),
(3, 'Sam')
insert into #tasks values
(1, 1, 'prepare grocery list', 1),
(2, 1, 'do shopping', 2),
(3, 1, 'cook food', 3),
(4, 2, 'do shopping', 2),
(5, 2, 'prepare grocery list', 1),
(6, 3, 'cook food', 3)
select u.user_id, u.username, t.task_id, t.taskname, t.difficulty
from #users u
left join #tasks t on u.user_id = t.user_id
where t.difficulty = (
select max(x.difficulty)
from #tasks x
where t.user_id = x.user_id
)
This will be more performant:
select u.user_id, u.username, t.task_id, t.taskname, t.difficulty
from #users u
left join #tasks t on u.user_id = t.user_id
inner join (
select x.user_id, max(x.difficulty) as max_difficulty
from #tasks x
group by x.user_id
) as y on t.user_id = y.user_id and t.difficulty = y.max_difficulty
Both of these queries return the following dataset:
user_id username task_id taskname difficulty
----------- ---------- ----------- ------------------------ -----------
1 John 3 cook food 3
2 Sally 4 do shopping 2
3 Sam 6 cook food 3
If a user has two max tasks with the same difficulty, then the query will include two rows for that user.
Although, showing the query plan for this SQL says the 2nd query costs almost double what the 1st query costs. Having max() in the where clause seems to take be more efficient than placing the max() in the from clause. I would try it both ways on your real data, and see what the query plan/cost is for you.
Try
SELECT u.user_id, u.username, t.task_id, t.taskname, m.difficulty
FROM tasks t
RIGHT JOIN (SELECT user_id,
MAX(difficulty) as difficulty
FROM tasks
GROUP BY user_id) m ON t.user_id = m.user_id
AND t.difficulty = m.difficulty
LEFT JOIN users u ON t.user_id = u.user_id;
I Think you are looking for something like this
SELECT u.user_id, u.username, t.task_id, t.taskname,t.difficuilty
FROM users u LEFT JOIN tasks t ON u.user_id = t.user_id
INNER JOIN
(
SELECT user_Id,Max(difficuilty) D FROM tasks GROUP BY user_id
)Temp ON Temp.user_id = u.user_id ANDTemp.D = t.difficuilty
GO

writing a query that contains two constraints on the same table

I am aware that I am asking something that may look trivial but I could not find and answer or a duplicate. (I am new with this area).
So i am trying.
I have two tables
Tests table with the following columns
1. storyID , authorID , assigneeID
2. userID , userName
I would like to write a query that return storyID, UserName of author ID, UserName of assigneeID.
SELECT storyID, u1.userName AS authorName, u2.userName AS assigneeName
FROM storytable s
INNER JOIN usertable u1 ON s.authorID = u1.userID
INNER JOIN usertable u2 ON s.assigneeID = u2.userID
The above solution will only work in case you have relation between userID, assigneeID and authorID fields.
Select and create an alias for the second table.
SELECT a.storyID, b.userName, c.userName
FROM tablea a
INNER JOIN tableb b ON a.authorID = b.userID
INNER JOIN tableb c ON a.assigneeID= c.userID
Assuming the authorID = UserId...
select
1.storyId,
author.userName,
assignee.userName
from 1
inner join 2 as author
on 1.authorId = 2.userId
inner join 2 as assignee
on 1.assigneeID = 2.userID

SQL get duplicate records for all the users

I have these tables
CREATE TABLE subject
(
id int auto_increment primary key,
name varchar(20)
);
INSERT INTO subject
(name)
VALUES
('subject 1'),('subject 2'), ('subject 3');
CREATE TABLE course
(
id int auto_increment primary key,
name varchar(20),
subject_id int,
FOREIGN KEY (subject_id) REFERENCES subject(id)
);
INSERT INTO course
(subject_id, name)
VALUES
(1, 'course 1'),(1, 'course 2'), (2, 'course 3'), (3, 'course 4');
CREATE TABLE user
(
id int auto_increment primary key,
name varchar(20),
course_id int,
FOREIGN KEY (course_id) REFERENCES course(id)
);
INSERT INTO user
(course_id, name)
VALUES
(1, 'User 1'),(1, 'User 1'), (2, 'User 3'), (3, 'User 4');
I want to get a list of users who belong to the course which belongs to the same subject.
I ran this query to get the list
select u.name as user_name, s.name as subject_name from user u
inner join course c on u.course_id = c.id
inner join subject s on c.subject_id = s.id;
And this is the output
USER_NAME SUBJECT_NAME
User 1 subject 1
User 1 subject 1
User 3 subject 1
User 4 subject 2
Expected output
USER_NAME SUBJECT_NAME
User 1 subject 1
User 1 subject 1
how to I select only user 1 who has a course that belongs to the same subject ??
fiddle link
To get the List of users who have got more than 1 course enrolled, use below query in the fiddle:
select u.name as user_name, s.name as subject_name from user u
inner join course c on u.course_id = c.id
inner join subject s on c.subject_id = s.id
having count(1) > 1;
To Get only distinct users and courses, use below query :
select distinct u.name as user_name, s.name as subject_name from user u
inner join course c on u.course_id = c.id
inner join subject s on c.subject_id = s.id;
EDIT: I assume you need the number of distinct users who have taken the subject for which you can use below query:
select s.name as subject_name, count(distinct(u.name)) as no_of_users from user u
inner join course c on u.course_id = c.id
inner join subject s on c.subject_id = s.id
group by s.name;
By using just count(u.name) as no_of_users You would get the actual count rather than distinct count.
EDIT AGAIN
To get the required output, the below query works!
select tab_a.user_name, tab_a.subject_name from
(
select u.name as user_name, s.name as subject_name from user u
inner join course c on u.course_id = c.id
inner join subject s on c.subject_id = s.id
)tab_a
inner join
(
select u.name as user_name, s.name as subject_name, count(1) reccount from user u
inner join course c on u.course_id = c.id
inner join subject s on c.subject_id = s.id
group by u.name, s.name
) tab_b
on tab_a.user_name = tab_b.user_name
where tab_b.reccount >1