Subquery on JOIN to pulling all ID/Names - mysql

* user
user_id
name
* client
client_id
name
* user_client
user_client_id
user_id
client_id
* message
message_id
client_id
description
Sample Table Rows
user_id
1
2
3
client_id name
10 John
11 James
12 David
13 Richard
14 Bob
user_client
user_id client_id
1 11
1 13
3 14
3 10
message
message_id client_id message
1 11 Hello Word
2 12 MySQL is awesome
3 14 I like StackOverflow
4 13 This is very cool
What it's not working is when I use that query as a subquery on a LEFT JOIN to pull the messages only for those clients pertinent to the user.
Any ideas?
Thanks!

The DDL to set up the example (in MySQL) and the query I believe you are looking for are shown below.
/*
-- DDL TO SET UP EXAMPLE
create schema example;
use example;
create table user (
user_id int,
name varchar(64)
);
create table client(
client_id int,
name varchar(64)
);
create table user_client (
user_client_id int,
user_id int,
client_id int
);
create table message(
message_id int,
client_id int,
message varchar(64)
);
insert into user values (1, 'Peter');
insert into user values (2, 'Paul');
insert into user values (3, 'Mary');
insert into client values (10, 'John');
insert into client values (11, 'James');
insert into client values (12, 'David');
insert into client values (13, 'Richard');
insert into client values (14, 'Bob');
insert into user_client values (1, 1, 11);
insert into user_client values (2, 1, 11);
insert into user_client values (3, 3, 14);
insert into user_client values (4, 3, 10);
insert into message values (1, 11, 'Hello World');
insert into message values (2, 12, 'MySQL is awesome');
insert into message values (3, 14, 'I like StackOverflow');
insert into message values (4, 13, 'This is very cool');
*/
-- query to get all messages for all clients of a given user
select
*
from
user_client uc
join user u on uc.user_id = u.user_id
join client c on uc.client_id = c.client_id
join message m on m.client_id = c.client_id
where
u.user_id = 1;
-- query to get all messages for a given client
select
*
from
user_client uc
join user u on uc.user_id = u.user_id
join client c on uc.client_id = c.client_id
join message m on m.client_id = c.client_id
where
c.client_id = 11;

This should really be done as three separate queries as three different questions are being asked based on the comments:
So you want a query that will return messages from related clients if
there is a record in client, and all messages if there is no record in
client for a give user? – John
...
Hi #John that is correct. And that's why I was using the subquery,
because it pulls exactly that, but for some reason the client_id and
name are coming as NULL for all but one. – Kitara
The first question (query) is find all of the messages for all of the clients of a given user.
The second question (query) is: Does a user have "authorization" to view all messages. If the user has no clients then that user is "authorized" to view all messages.
The third question (query) is: If the user is authorized to view all messages get all of the messages.
These are very simple straight forward queries to write, execute, and understand. Trying to conflate all of this into a single query will add complexity and represents poor separation of concerns. If executing three very simple queries represents a performance issue the architecture of the application needs to be reconsidered.

There was a mistake in my original ddl in one of the inserts (fixed below). In the sql below I've also added a user with no messages. I believe the query at the end of what is posted below is what you are looking for.
-- DDL TO SET UP EXAMPLE
drop schema example;
create schema example;
use example;
create table user (
user_id int,
name varchar(64)
);
create table client(
client_id int,
name varchar(64)
);
create table user_client (
user_client_id int,
user_id int,
client_id int
);
create table message(
message_id int,
client_id int,
message varchar(64)
);
insert into user values (1, 'Peter');
insert into user values (2, 'Paul');
insert into user values (3, 'Mary');
insert into client values (10, 'John');
insert into client values (11, 'James');
insert into client values (12, 'David');
insert into client values (13, 'Richard');
insert into client values (14, 'Bob');
insert into client values (15, 'Quiet Client');
insert into user_client values (1, 1, 11);
insert into user_client values (2, 1, 13);
insert into user_client values (3, 3, 14);
insert into user_client values (4, 3, 10);
insert into user_client values (5, 1, 15);
insert into message values (1, 11, 'Hello World');
insert into message values (2, 12, 'MySQL is awesome');
insert into message values (4, 13, 'This is very cool');
insert into message values (3, 14, 'I like StackOverflow');
-- query to get all messages for all clients of a given user
select
u.user_id,
u.name user_name,
c.client_id,
c.name client_name,
m.message
from
user_client uc
join user u on uc.user_id = u.user_id
join client c on uc.client_id = c.client_id
left outer join message m on m.client_id = c.client_id
where
u.user_id = 1;
Output:
+ ------------ + -------------- + -------------- + ---------------- + ------------ +
| user_id | user_name | client_id | client_name | message |
+ ------------ + -------------- + -------------- + ---------------- + ------------ +
| 1 | Peter | 11 | James | Hello World |
| 1 | Peter | 13 | Richard | This is very cool |
| 1 | Peter | 15 | Quiet Client | |
+ ------------ + -------------- + -------------- + ---------------- + ------------ +
3 rows

Related

How do you insert a new record or update an existing if id already exists with inner join?

The goal is to insert a new record or update an existing if the id column exists. The ON DUPLICATE KEY UPDATE function seems to be a good fit but the problem I have is I want to make sure that the ID matches an ID in another table. This is for permissions. An example is:
INSERT INTO `testtable` (`id`, `user_id`, `order`, `active`)
VALUES
(1, 24, 3, 0),
(2, 24, 1, 1),
(NULL, 24, 2, 0) AS newupdate
ON DUPLICATE KEY UPDATE
order = newupdate.order,
active = newupdate.active
This works but I want to add a check where the testtable.user_id from above matches users.id from another table. I've been using this SELECT statement:
SELECT testtable.id, testtable.order, testtable.active
FROM testtable
INNER JOIN users on testtable.user_id = users.id
WHERE users.username = bob
So I can't figure out how to utilize the ON DUPLICATE KEY UPDATE with an INNER JOIN or perhaps I'm going about this all the wrong way. I search Stackoverflow and saw some suggestions to use a SELECT statement with the ON DUPLICATE KEY UPDATE but I don't understand how that passes the values. Anyway, is this possible? Thanks in advance.
UPDATE: I was able to get it working but it seems like there should be an easier/better way? Anyone have any other suggestions? This example below I'm inserting a row with id 24 which exists so the update runs and then I'm inserting a row with id NULL so it gets inserted as is.
INSERT INTO `testtable` (`id`, `user_id`, `order`, `active`)
SELECT `id`, `user_id`, `order`, `active`
FROM (SELECT
24 AS id,
(SELECT user_id FROM testtable INNER JOIN users on testtable.user_id = users.id WHERE users.username = 'bob' LIMIT 1) AS user_id,
6 AS order,
1 AS active
UNION ALL
SELECT
NULL AS id,
(SELECT user_id FROM testtable INNER JOIN users on testtable.user_id = users.id WHERE users.username = 'bob' LIMIT 1) AS user_id,
7 AS order,
1 AS active
)
AS newtable
ON DUPLICATE KEY UPDATE
order = newtable.order,
active = newtable.active
Here we have a foreign key to users and a trigger to block changing user_id.
create table users(
id int primary key,
user varchar(25));
insert into users values
(12,'Andrew'),
(24,'Bernard');
✓
✓
create table testtable (
id int ,
user_id int unique,
order_ int,
active int,
foreign key fk_user_id (user_id)
references users(id));
✓
INSERT INTO `testtable` (`id`, `user_id`, `order_`, `active`)
VALUES
(1, 24, 3, 0),
(2, 24, 1, 1),
(NULL, 24, 2, 0) AS newupdate
ON DUPLICATE KEY UPDATE
order_ = newupdate.order_,
active = newupdate.active
✓
/*delimiter $$ */
create trigger user_id_update
before update on testtable
for each row
begin
if (old.user_id <>new.user_id) then
signal SQLSTATE VALUE '45000'
SET MESSAGE_TEXT = 'user_id change forbidden';
end if ;
end ;
/* delimiter ; */
✓
✓
select * from testtable;
id | user_id | order_ | active
-: | ------: | -----: | -----:
1 | 24 | 2 | 0
update testtable
set user_id = 12 where user_id = 24;
user_id change forbidden
select * from testtable;
id | user_id | order_ | active
-: | ------: | -----: | -----:
1 | 24 | 2 | 0
db<>fiddle here

MySQL Update a table by min of another table

I would like to update Table Lease from Table History
CREATE TABLE Lease
(`LeaseID` int, `Name` varchar(3), `Users` varchar(15), `WhoSignID` int, `NoteDate` date)
;
INSERT INTO Lease
(`LeaseID`, `Name`, `Users`, `WhoSignID`, `NoteDate`)
VALUES
(1, 'AAA', '1000,1001', NULL, NULL),
(2, 'BBB', '1002', NULL, NULL),
(3, 'CCC', '1003,1004', NULL, NULL),
(4, 'DDD', '1005,1006, 1007', NULL, NULL)
;
CREATE TABLE History
(`HistoryID` int, `LeaseID` int, `User` int, `SignDate` date)
;
INSERT INTO History
(`HistoryID`, `LeaseID`, `User`, `SignDate`)
VALUES
(1, 1, 1000, '2020-01-05'),
(2, 1, 1001, '2020-01-04'),
(3, 1, 1001, '2020-01-02'),
(4, 1, 1000, '2020-01-03'),
(6, 2, 1002, '2020-05-01'),
(7, 2, 1002, '2020-05-03')
;
I looking of a Mysql Update to update Table Lease :
NoteDate and WhoSignID based on SignDate and User
where Minimum of SignDate of User
Table Lease After Update
LeaseID | Name | Users | WhoSignID | NoteDate
1 | AAA | 1000,1001 | 1001 | 2020-01-02
2 | BBB | 1002 | 1002 | 2020-05-01
...
I appreciate any assist
Your Lease table has a serious design problem, because it is storing users as a CSV list. Instead, you should have each user value on a separate record. That being said, it appears that the CSV user list is immaterial to your current problem, which only required finding the earliest date for each lease. If so, then a simple update join should suffice:
UPDATE Lease l
INNER JOIN
(
SELECT h1.LeaseID, h1.User, h2.MinSignDate
FROM History h1
INNER JOIN
(
SELECT LeaseID, MIN(SignDate) AS MinSignDate
FROM History
GROUP BY LeaseID
) h2
ON h2.LeaseID = h1.LeaseID AND
h2.MinSignDate = h1.SignDate
) h
ON h.LeaseID = l.LeaseID
SET
WhoSignID = h.User,
NoteDate = h.MinSignDate;

How to join two mysql table and retrieve latest results from joined table?

I have two tables. I need to join these two tables and retrieve latest status from execution table. How can I retrieve?
My schema and data:
CREATE TABLE test
(`id` serial primary key, `ref_id` int, `ref_name` varchar(7))
;
INSERT INTO test
(`id`, `ref_id`, `ref_name`)
VALUES
(1, 1, 'trial'),
(2, 3, 'test'),
(3, 7, 'testing')
;
CREATE TABLE execution
(`id` serial primary key, `ref_id` int, `status` varchar(11))
;
INSERT INTO execution
(`id`, `ref_id`, `status`)
VALUES
(1, 1, 'Completed'),
(2, 2, 'Completed'),
(3, 1, 'Completed'),
(4, 3, 'In progress'),
(5, 3, 'To do'),
(6, 2, 'In progress'),
(7, 1, 'Completed'),
(7, 1, 'To do')
;
Expected result is here below.
ref_id | ref_name | status |
3 | testing | In progress |
2 | test | To do |
1 | trial | To do |
I have tried with below query:
SELECT
ref_id,
ref_name,
status
FROM
test
JOIN execution ON test.ref_id = execution.ref_id
GROUP BY `ref_id`
ORDER BY `ref_id` DESC;
This query retrieves the status, but the retrieved status is not a latest one. How can retrieve the latest status by joining these two tables.
you can use below query
select T2.ref_id,T2.ref_name,OE.status from
(
select t1.ref_id,t1.ref_name,e.id from test t1 inner join
(select max(id) as id,ref_id from execution group by ref_id) as e
on
t1.ref_id=e.ref_id
) as T2
inner join execution OE on T2.id=OE.id
https://www.db-fiddle.com/f/rvnm8APX27dmW9a84JkCsS/1
It seems you have given in-correct data as an example as ref_id 7 not found in
execution table. However this might help you
SELECT b.ref_id,
b.ref_name,
a.status
FROM execution a
JOIN (SELECT MAX(id) id ,ref_id
FROM execution
GROUP BY ref_id) a1
USING(id,ref_id)
JOIN test b ON a.ref_id = b.ref_id ORDER BY ref_id DESC;

MySQL self join question

Take a look at the following mySQL query:
SELECT fname,lname FROM users WHERE users.id IN (SELECT sub FROM friends WHERE friends.dom = 1 )
The above query first creates a set of ALL the friends.sub's via the inner query, and then the outer query selects a list of users where user ids are contained within the set created by the inner query (ie the union of the two sets).
And this works fine. But if you needed the inner set to contain not only the subs where dom = 1, but also the doms where sub = 1, like so:
Outer query remains same as above, pure pseudocode:
(SELECT sub FROM friends WHERE friends.dom = 1 )
***AND***
(SELECT dom FROM friends WHERE friends.sub = 1 )
Is it possible to make this sort of functionality with the inner query??
Any help or assistance appreciated guys;-D
Thanks a lot guys, my headache is gone now!
Try this:
SELECT u.fname, u.lname
FROM users u
INNER JOIN friends f
ON (u.id = f.sub AND f.dom = 1)
OR (u.id = f.dom AND f.sub = 1)
I'm not sure if I correctly understand what sub and dom represent, but it looks like you can use a UNION in there:
SELECT fname, lname
FROM users
WHERE users.id IN
(
SELECT sub FROM friends WHERE friends.dom = 1
UNION
SELECT dom FROM friends WHERE friends.sub = 1
);
Test case:
CREATE TABLE users (id int, fname varchar(10), lname varchar(10));
CREATE TABLE friends (dom int, sub int);
INSERT INTO users VALUES (1, 'Bob', 'Smith');
INSERT INTO users VALUES (2, 'Peter', 'Brown');
INSERT INTO users VALUES (3, 'Jack', 'Green');
INSERT INTO users VALUES (4, 'Kevin', 'Jackson');
INSERT INTO users VALUES (5, 'Steven', 'Black');
INSERT INTO friends VALUES (1, 2);
INSERT INTO friends VALUES (1, 3);
INSERT INTO friends VALUES (4, 1);
INSERT INTO friends VALUES (3, 4);
INSERT INTO friends VALUES (5, 2);
Result:
+-------+---------+
| fname | lname |
+-------+---------+
| Peter | Brown |
| Jack | Green |
| Kevin | Jackson |
+-------+---------+
3 rows in set (0.00 sec)
That said, #Alec's solution is probably more efficient.

MySQL multiple table query with average for each row

This is my setup:
Table "files": id (PK), filename, user_id, date, filesize
Table "scores": id(PK), file_id, user_id, score
Table "files" contains a list of files with details; table "scores" keeps track of 1-5 points scored per file. I need to get entries from the "files" table and in each row I need all the info for the file, as well as the average score. I can do another query for teh current file_id while I'm looping through the rows, but obviousely that's not very optimized. I tried something like below, but no success.
SELECT files.*, (SUM(scores.score)/(COUNT(scores.score))) AS total FROM files INNER JOIN scores ON files.id=scores.file_id;
Please point me in the right direction - thanks!
You may want to try the following:
SELECT f.id, f.filename, f.user_id, f.date, f.filesize,
(
SELECT AVG(s.score)
FROM scores s
WHERE s.file_id = f.id
) average_score
FROM files f;
Note that you can use the AVG() aggregate function. There is no need to divide the SUM() by the COUNT().
Test case:
CREATE TABLE files (id int, filename varchar(10));
CREATE TABLE scores (id int, file_id int, score int);
INSERT INTO files VALUES (1, 'f1.txt');
INSERT INTO files VALUES (2, 'f2.txt');
INSERT INTO files VALUES (3, 'f3.txt');
INSERT INTO files VALUES (4, 'f4.txt');
INSERT INTO scores VALUES (1, 1, 10);
INSERT INTO scores VALUES (2, 1, 15);
INSERT INTO scores VALUES (3, 1, 20);
INSERT INTO scores VALUES (4, 2, 5);
INSERT INTO scores VALUES (5, 2, 10);
INSERT INTO scores VALUES (6, 3, 20);
INSERT INTO scores VALUES (7, 3, 15);
INSERT INTO scores VALUES (8, 3, 15);
INSERT INTO scores VALUES (9, 4, 12);
Result:
SELECT f.id, f.filename,
(
SELECT AVG(s.score)
FROM scores s
WHERE s.file_id = f.id
) average_score
FROM files f;
+------+----------+---------------+
| id | filename | average_score |
+------+----------+---------------+
| 1 | f1.txt | 15.0000 |
| 2 | f2.txt | 7.5000 |
| 3 | f3.txt | 16.6667 |
| 4 | f4.txt | 12.0000 |
+------+----------+---------------+
4 rows in set (0.06 sec)
Note that #Ignacio's solution produces the same result, and is therefore another option.
Aggregate functions are not usually useful without aggregation.
SELECT f.*, AVG(s.score) AS total
FROM files AS f
INNER JOIN scores AS s
ON f.id=s.file_id
GROUP BY f.id