SQL tricky left join of three tables - all different keys - sql

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

Related

How to order names in a column based on an index sequence in another table?

So I have 3 tables Friends and Students and they both have an ID column. But in the Friends table, there is also a column known as Friend_ID. I want to reorder the Name column in Friends in the way the Friend_ID is ordered. Is it possible?
This is for the problem - https://www.hackerrank.com/challenges/placements/problem?isFullScreen=false
Note: I want to use this without the ORDER BY clause as this will be a subquery.
My query in SQL Server.
WITH
CTE (NID,N_Name,N_Salary,FID) AS
(SELECT S.ID AS NID, S.Name AS N_Name, P.Salary AS N_Salary, F.Friend_ID AS FID
FROM Students S JOIN Friends F
ON S.ID = F.ID JOIN Packages P
ON S.ID = P.ID),
--SELECT L.NID,L.N_Name,L.N_Salary,L.FID,R.FN_Sal
SELECT
IIF(L.N_Salary > R.FN_Sal,
IIF(L.FID IN (SELECT ID FROM Students WHERE Students.ID=R.N_ID),L.N_Name,"No val")
,NULL)
FROM CTE L
JOIN
(SELECT F.ID AS N_ID, P.Salary AS FN_Sal, F.Friend_ID AS F_ID
FROM Friends F JOIN Packages P ON F.Friend_ID = P.ID) R
ON L.NID = R.N_ID
I guess there is something wrong with my query. This is definitely not an elegant solution but I am just trying out an idea. Any input would be really helpful, thanks in advance.
You must join Students to Friends and to 2 copies of Packages.
The 1st copy of Packages will return the student's salary and the 2nd copy will return the friend's salary:
SELECT s.Name
FROM Students s
INNER JOIN Friends f ON f.ID = s.ID
INNER JOIN Packages ps ON ps.ID = s.ID
INNER JOIN Packages pf ON pf.ID = f.Friend_ID
WHERE pf.Salary > ps.Salary
ORDER BY pf.Salary
See the demo.
Results:
> | Name |
> | :------- |
> | Samantha |
> | Julia |
> | Scarlet |
This is one answer to the question:
DECLARE #Friends TABLE (ID int, Friend_ID INT);
DECLARE #Students TABLE (ID int, Name VARCHAR(100));
DECLARE #Packages TABLE (ID int, Salary DECIMAL(4,2));
INSERT INTO #Friends SELECT ID,Friend_ID FROM (VALUES(1,2),(2,3),(3,4),(4,1)) Friends(ID,Friend_ID);
INSERT INTO #Students SELECT ID,[Name] FROM (VALUES(1,'Ashley'),(2,'Samantha'),(3,'Julia'),(4,'Scarlet')) Students(ID,[Name]);
INSERT INTO #Packages SELECT ID,Salary FROM (VALUES (1,15.20),(2,10.06),(3,11.55),(4,12.12)) Packages(ID,Salary);
WITH StudentPackage AS (
SELECT
Student.ID,
Student.[Name] StudentName,
Packages.Salary,
BestFriend.Friend_ID
FROM #Students Student
JOIN #Packages Packages
ON Student.ID = Packages.ID
JOIN #Friends BestFriend
ON Student.ID = BestFriend.ID
)
SELECT
Student.StudentName
FROM StudentPackage Student
JOIN StudentPackage BestFriend
ON Student.Friend_ID = BestFriend.ID AND BestFriend.Salary > Student.Salary
ORDER BY BestFriend.Salary
Have you considered using the Order by clause. That should work.
Thank you for all your inputs! They are correct and I see how unnecessarily I have complicated such a simple question.
This is the solution I was trying to achieve, according to my logic:
WITH
CTE (N_ID,N_Name,N_Salary,FID) AS
(SELECT S.ID AS N_ID, S.Name AS N_Name, P.Salary AS N_Salary, F.Friend_ID AS FID
FROM Students S JOIN Friends F
ON S.ID = F.ID JOIN Packages P
ON S.ID = P.ID)
SELECT L.N_Name
FROM CTE L
JOIN
(SELECT F.ID AS N_ID, P.Salary AS FN_Sal, F.Friend_ID AS F_ID
FROM Friends F JOIN Packages P ON F.Friend_ID = P.ID) R
ON L.N_ID = R.N_ID
WHERE R.FN_Sal > L.N_Salary
ORDER BY R.FN_Sal;

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

SQL explicit join filter condition

Using this relational schema, patient ID and staff ID are foreign keys with id.staff and pid.patient being unique keys:
staff(ID, fname, lname, role)
patient(pID, pFname, pLname, bdate, address, phone)
appointment(aptID, patientID, staffID, aptDate, aptTime)
procedures(procNum, pName, price)
aptDetail(aptID, procNo)
So say if I wanted to list the names of patients with appointments with a specific staff member, i.e John Smith, how would I do that explicitly?
I've managed implicitly, but I know this is kind of frowned upon, but I can't reach it without using WHERE statements.
Any help would be appreciated, any time I try and use INNER JOIN I seem to hit a wall if it's not a simple join.
Is this the type of query you're looking for?
select distinct pFname, pLname
from patient p
join appointment a on p.pID = a.patientID
join staff s on a.staffID = s.ID
where s.fname = 'John' and s.lname = 'Smith'
You can use inner join
select
a.pID
, a.pFname
, a.pLname
, a.bdate
, a.address
, a.phone
, b.aptDate
, b.aptTime
, c.fname
, c.lname
, c.role
from patient a
INNER JOIN appointment b on b.patientID = a.pID
INNER JOIN staff c on b.staffID = c.ID on concat(fname, ' ', lname ) ='John Smith'
Something like the following should work fine:
SELECT p.*
FROM appointment AS a
INNER JOIN staff AS s ON a.staffID = s.pID
INNER JOIN patient AS p ON a.patientID = p.pID
WHERE s.ID = <yourstaffid>;
select staff.fname, staff.role,
patient.pfname, plname, appoitment.aotdate
from staff, patient, appointment
where patient.pid=appointment.patientId
and staff.id=appointment.staffid

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

Conditional JOIN different tables

I want to know if a user has an entry in any of 2 related tables.
Tables
USER (user_id)
EMPLOYEE (id, user_id)
STUDENT (id, user_id)
A User may have an employee and/or student entry. How can I get that info in one query?
I tried:
select * from [user] u
inner join employee e
on e.user_id = case when e.user_id is not NULL
then u.user_id
else null
end
inner join student s
on s.user_id = case when s.user_id is not NULL
then u.user_id
else null
end
But it will return only users with entries in both tables.
SQL Fiddle example
You could use an outer join:
select *
from USER u
left outer join EMPLOYEE e ON u.user_id = e.user_id
left outer join STUDENT s ON u.user_id = s.user_id
where s.user_id is not null or e.user_id is not null
alternatively (if you're not interested in the data from the EMPLOYEE or STUDENT table)
select *
from USER u
where exists (select 1 from EMPLOYEE e where e.user_id = u.user_id)
or exists (select 1 from STUDENT s where s.user_id = u.user_id)
If you want to get all user data together You might have:
SELECT
user_id
,'Employee' AS Source
FROM
employee
UNION
SELECT
user_id
,'Student' AS Source
FROM
student
http://sqlfiddle.com/#!3/90216/22
Which can also be done with a full join and a CASE statement:
SELECT
ISNULL(e.user_id,s.user_id) AS user_id
,CASE WHEN e.user_id IS NULL THEN 'Student'
ELSE 'Employee'
END AS SOURCE
FROM
employee AS e
FULL JOIN student AS s
ON s.user_id = e.user_id
http://sqlfiddle.com/#!3/90216/29
the latter will combine people who are both students adn employees into one row and call them and employee. compare:
http://sqlfiddle.com/#!3/2aa3e/1
and
http://sqlfiddle.com/#!3/2aa3e/2
where I have made user 1 a student and a employee
Such solution also can help you.
SELECT S.*, P.*
,CASE
WHEN S.ShipmentType = 'import' THEN SP.SupplierName
WHEN S.ShipmentType = 'export' THEN C.CustomerName
END AS ShipmentDesination
FROM tblShippments S
INNER JOIN tblProducts P ON S.productId = P.productID
LEFT OUTER JOIN tblCustomers C ON S.companyId = C.customerId AND S.ShipmentType = 'export'
LEFT OUTER JOIN tblSuppliers SP ON S.companyId = SP.supplierId AND S.ShipmentType = 'import'
If you look at employee and student tables as one, you can use left join:
select *
from user u
left join
(
select 'Employee' as UserType,
id,
user_id
from employee e
union all
select 'Student',
id,
user_id
from student s
) r
ON u.user_id = r.user_id
How about UNION which you write as 2 separate SELECT statements
For example:
SELECT * FROM User U
JOIN Employee E ON E.User_Id = U.User_Id
UNION
SELECT * FROM User U
JOIN student S ON S.User_Id = U.User_Id
I couldn't see why you needed the CASE statement it looked superfluous. If you wanted all Users and to show nulls then use LEFT OUTER JOIN.
work for mysql v5.6+
schema:
CREATE TABLE USER (user_id INT);
CREATE TABLE employee (id INT, user_id INT);
CREATE TABLE student (id INT, user_id INT);
INSERT INTO USER SELECT 1;
INSERT INTO USER SELECT 2;
INSERT INTO USER SELECT 3;
INSERT INTO USER SELECT 4;
INSERT INTO employee SELECT 1, 1;
INSERT INTO employee SELECT 2, 4;
INSERT INTO student SELECT 1, 2;
first solution
,full outer join does not work for mysql v5.6+:
SELECT
IF(e.user_id IS NULL,s.user_id,e.user_id) AS user_id
,CASE WHEN e.user_id IS NULL THEN 'Student'
ELSE 'Employee'
END AS SOURCE
FROM
employee AS e
LEFT JOIN student AS s
ON (s.user_id = e.user_id)
UNION
SELECT
IF(e.user_id IS NULL,s.user_id,e.user_id) AS user_id
,CASE WHEN e.user_id IS NULL THEN 'Student'
ELSE 'Employee'
END AS SOURCE
FROM
employee AS e
RIGHT JOIN student AS s
ON (s.user_id = e.user_id)
alternative solution:
SELECT IF(e.user_id IS NULL,s.user_id,e.user_id) AS user_id,
CASE WHEN e.user_id IS NULL THEN 'Student'
ELSE 'Employee'
END AS SOURCE
FROM USER AS u
LEFT OUTER JOIN employee e ON (u.user_id = e.user_id)
LEFT OUTER JOIN student s ON (u.user_id = s.user_id)
WHERE s.user_id IS NOT NULL OR e.user_id IS NOT NULL
other, alternative solution:
SELECT u.user_id,SOURCE
FROM USER u
INNER JOIN
(
SELECT 'Employee' AS SOURCE,id,user_id FROM employee e
UNION ALL
SELECT 'Student',id,user_id FROM student s
) r ON u.user_id = r.user_id
SELECT OrderID, Quantity, O.ProductID , ProductName,
CASE
WHEN Quantity > 3 THEN 'The quantity is More than 3'
WHEN Quantity = 3 THEN 'The quantity is Equal to 3'
ELSE 'The quantity is Less than 3'
END AS QuantityText
FROM tb_OrderDetail O
INNER JOIN tb_Product P ON O.ProductID = P.ProductID