Joining over three table with sum() subquery - mysql

I'm having trouble getting a query to work where data is pulled from two tables, with a 3rd table in the middle. Let me show you what I mean.
*Table companies*
id int PK
name text
*Table projects*
id int PK
company_id int FK
project_name text
*Table hours_worked*
id int pk
user_id int FK
project_id int FK
hours float
date_worked datetime
*Table users*
id int PK
user_name text
Basically, what I need is a query that pulls the total amount of hours worked per company based on a user ID.
Note that the table_hours worked can have multiple submissions per day per project. For example, a few rows might look like
id project_id user_id hours date_worked
1 1 1 2 20-08-2012
2 1 1 1.5 20-08-2012
3 2 1 3 21-08-2012
4 2 2 12 22-08-2012
My desired result would be a query that returns something like this:
company_name total_hours
Bobs Kitchens 25
Mikes Bikes 67
Which returns the total number of hours worked per company (not project) for say, a user with the user ID of 1.
Here is the following query I've tried with no avail:
SELECT DISTINCT companies.name as company_name,
companies.id as company_id,
(
SELECT SUM(hours_worked.hours) FROM hours_worked
WHERE projects.id = hours_worked.project_id
AND projects.company_id = company_id
) as total_hours
FROM hoursworked, companies, projects
WHERE projects.company_id = company_id
AND projects.company_id = projects.company_id
AND hours_worked.user_id = 1
GROUP BY companies.id
This is giving me an odd result where a really weird number appears to be displaying for every total_hours field. 75 is not the correct total hours for any company!
company_name total_hours
Mikes Kitchen 75
Charlies Bikes 75
..... 75
Any help would be greatly appreciated!

Try this:
SELECT c.name company_name, u.user_name, SUM(h.hours) total_hours
FROM projects p
INNER JOIN companies c ON p.company_id = c.id
INNER JOIN hours_worked h ON p.id = h.project_id
INNER JOIN users u ON h.user_id = u.id
GROUP BY c.id, u.id

I was such a fool..it was actually a lot easier than I expected. Hurray for over thinking.
The query I've used is:
SELECT SUM(hours_worked.hours) as 'total_hours',
companies.name
FROM companies, hours_worked, projects
WHERE companies.id = projects.company_id
AND hours_worked.project_id = projects.id
AND hours_worked.uid = 1
GROUP BY companies.id ORDER BY companies.name DESC

Related

Joining multiple columns into one with union, exclude results with same id

I want to join columns from multiple tables to one column, in my case column 'battery_value' and 'technical_value' into column 'value'. I want to fetch data for only given category_ids, but because of UNION, I get data from other tables as well.
I have 4 tables:
Table: car
car_id model_name
1 e6
Table: battery
battery_category_id car_id battery_value
1 1 125 kW
Table: technical_data
technical_category_id car_id technical_value
1 1 5
3 1 2008
Table: categories
category_id category_name category_type
1 engine power battery
1 seats technical
3 release year technical
From searching, people are suggesting that I use union to join these columns. My query now looks like this:
SELECT CARS.car_id
category_id,
CATEGORIES.category_name,
value,
FROM CARS
left join (SELECT BATTERY.battery_category_id AS category_id,
BATTERY.car_id AS car_id,
BATTERY.value AS value
FROM BATTERY
WHERE `BATTERY`.`battery_category_id` IN (1)
UNION
SELECT TECHNICAL_DATA.technical_category_id AS category_id,
TECHNICAL_DATA.car_id AS car_id,
TECHNICAL_DATA.value AS value
FROM TECHNICAL_DATA
WHERE `TECHNICAL_DATA`.`technical_category_id` IN (3))
tt
ON CARS.car_id = tt.car_id
left join CATEGORIES
ON category_id = CATEGORIES.id
So the result I want is this, because I only want to get the data where category_id 1 is in battery table:
car_id category_id category_name technical_value
1 1 engine power 125 kW
1 3 release year 2008
but with the query above I get this, category_id 1 from technical table is included which is not something I want:
car_id category_id category_name value
1 1 engine power 125 kW
1 1 seats 125 kW
1 3 release year 2008
How can get exclude the 'seats' row?
For the results you want, I don't see why the cars table is needed. Then, you seem to need an additional key for the join to categories based on which table it is referring to.
So, I suggest:
SELECT tt.*, c.category_name
FROM ((SELECT b.battery_category_id AS category_id,
b.car_id AS car_id, b.value AS value,
'battery' as which
FROM BATTERY b
WHERE b.battery_category_id IN (1)
) UNION ALL
(SELECT td.technical_category_id AS category_id,
td.car_id AS car_id, td.value AS value,
'technical' as which
FROM TECHNICAL_DATA td
WHERE td.technical_category_id IN (3)
)
) tt LEFT JOIN
CATEGORIES c
ON c.id = tt.category_id AND
c.category_type = tt.which;
That said, you seem to have a problem with your data model, if the join to categories requires "hidden" data such as the type. However, that is outside the scope of the question.

How to get sorted result within INNER JOIN on joined table

I'm trying to get results from a join of 2 tables. Typically, not an issue, but I'm banging my head on this one. Basically, table1 (sales_dealers) is joined with table2 (sales_dealerEvents). Normally, easy-peasy. The problem is that I want to only get records from table2 if there are only 2 events (why I added the HAVING). But I need to get the value of the second event. With my query now, I can only get the value of the first event. It's like I need an ORDER BY EventID DESC on the joined table. It giving me ASC by default with this query.
Here is my query:
SELECT e.EventAction
, d.DealerID
, d.AgentID
, d.DealerName
, d.Email
, SUBSTRING_INDEX(d.PrimaryContactName, ' ', 1) AS FirstName
, d.State
, COUNT(e.EventID) AS EventCount
FROM sales_dealers d
JOIN sales_dealerEvents e
ON d.DealerID = e.DealerID
WHERE d.AgentID = 1
AND d.StatusID = 3
AND d.Email REGEXP '^[a-zA-Z0-9][a-zA-Z0-9._-]*#[a-zA-Z0-9][a-zA-Z0-9._-]*\\.[a-zA-Z]{2,4}$'
GROUP
BY d.DealerID
HAVING COUNT(e.EventID) = 2
ORDER
BY e.EventID DESC
Here is my Schema for the two tables (mysql version 5.7.27 )
sales_dealers
------------------
DealerID (int PK)
StatusID (int)
AgentID (int)
DealerName (varchar)
Email (varchar)
PrimaryContactName (varchar)
State (varchar)
sales_dealerEvents
------------------
EventID (int PK)
DealerID (FK)
AgentID (FK)
EventDateTime (datetime)
EventAction (longtext)
Here is sample data. Currently, the above query returns this:
Prospect Entry 784 1 Dealer Name One sales#dealer.com CA 2
Prospect Entry 782 1 Some other dealer hello#dealer.com MT 2
Prospect Entry 781 1 Dealer Store contact#dealer.com OK 2
I would like it to return this:
Initial Contact 784 1 Dealer One sales#dealer.com CA 2
Initial Contact 782 1 Some other dealer hello#dealer.com MT 2
Initial Contact 781 1 Dealer Store contact#dealer.com OK 2
Here is the same sample data of but showing the relationship of the one to many in sales_dealerEvents
sales_dealer
784 Dealer Name One
sales_dealerEvents
1000 784 1 Prospect Entry 2020-06-02 01:00:00
1010 784 1 Initial Contact 2020-07-03 01:00:00
I'm feeling I might have to do this in a subquery to sort the event so the second event is first. Not sure. I might also need to match criteria of that second event too.
UPDATE - Here is my SQL fiddle
http://sqlfiddle.com/#!9/ef6f2c/2
As you can see with the fiddle.. all event records returned are 'prospect entry'.. which were the first entries for 'events' for those. Each have 2. The second is 'Initial Contact'. Their date and EventID is after the 'prospect entry'.
Hope the below query helps and you can see the demo here: http://sqlfiddle.com/#!9/ef6f2c/37/0
SELECT
EventAction,
DealerID,
AgentID,
DealerName,
Email,
FirstName,
State,
EventID
from
(
SELECT
e.EventAction,
d.DealerID,
d.AgentID,
d.DealerName,
d.Email,
SUBSTRING_INDEX(d.PrimaryContactName, ' ', 1) AS FirstName,
d.State,
e.EventID as EventID
FROM
sales_dealers d
JOIN
sales_dealerEvents e
ON d.DealerID = e.DealerID
WHERE
d.AgentID = 1
AND d.StatusID = 3
AND d.Email REGEXP '^[a-zA-Z0-9][a-zA-Z0-9._-]*#[a-zA-Z0-9][a-zA-Z0-9._-]*\\.[a-zA-Z]{2,4}$'
ORDER BY
e.EventID DESC
)
a
GROUP BY
DealerID
HAVING
COUNT(EventID) = 2
This sounds like you want window functions:
select . . .
from sales_dealers d join
(select e.*,
row_number() over (partition by e.DealerID order by e.EventDateTime) as seqnum
from sales_dealerEvents e
) e
on e.DealerID = d.DealerID and e.seqnum = 2
where . . .
Note this gets data from the second event even if there are more than two events. That seems consistent with the question, but it is easy enough to use count(*) as a window function to do further filtering.

Complex SQL Union

Any SQL Guru's out there I could use some help! I am creating a stored procedure that I believe needs a Union so that all the results are brought back with 1 SELECT statement.
I have simplified my problem to the tables below:
user
user_id username name DOB
------------------------------------------------------
1 JohnSmith1 John Smith 01/01/1990
2 LisaGreen17 Lisa Green 03/07/1986
3 BarneyB Barney Brown 09/12/1960
user_team
user_team_id user_id team_id total_score
-------------------------------------------------------------
1 1 1 29
2 2 7 37
3 3 2 15
private_league
priv_league_id league_name host_user league_password
-------------------------------------------------------------------------
1 Lisa's League 2 CSUASH429d9
2 Barney's Bonanza 3 Jkap89f5I01
user_team_private_league_M2M
id priv_league_id user_team_id
----------------------------------------
1 1 1
2 1 2
3 1 3
4 2 1
5 2 3
I would like to run a stored procedure with an input of a user_id which will bring back all leagues entered by the user, the host of each of those leagues, how many total players have entered in each league and what position the user is in for each of those leagues(sorted by total score).
At the moment I have:
CREATE DEFINER=`root`#`localhost` PROCEDURE `user_private_leagues`(IN v_user_id INT)
BEGIN
DECLARE userteamid INT;
# Retrieve user team from a user_id
SELECT user_team_id INTO userteamid
FROM user_team
WHERE user_id = v_user_id;
# Retrieve private league name and host user (for a userteam)
SELECT private_league.league_name, private_league.host_user
FROM user_team_private_league_M2M
INNER JOIN privateleague
ON user_team_private_league_M2M.priv_league_id=private_league.priv_league_id
WHERE user_team_id = userteamid;
END
This query does not include the total number of players for each league and the current position of the user
I have created a query to bring back the total users for each private league, with no user filter like so:
SELECT private_league_id, COUNT(*) AS total_users
FROM classicseasonmodel_classicseasonuserteamprivateleague
GROUP BY private_league_id;
A query for the user's current position can be worked out by using the answer to this question and using total_score.
I am extremely stuck with this at the moment - the perfect result from the SP will be as follows:
CALL user_private_leagues(3); (user id of BarneyB)
priv_league_name current_position total_users host_user
-----------------------------------------------------------------------
Lisa's League 3 3 LisaGreen17
Barney's Bonanza 2 2 BarneyB
Thanks!
Sorry but I didn't create the DB to test the SQL below. But you can start from there. No need for UNION. I didn't understand the business rule to compute the user's position in the league, since it may come from the team or the user.
select priv_league_id, league_name, host_user_name, count(*) as total_users
from (
select A.priv_league_id, A.league_name, D.name as host_user_name, B.user_team_id, C.user_id, D.
from private_league A
join user_team_private_league_M2M B
on A.priv_league_id = B. priv_league_id
join user_team C
on B.user_team_id = C. user_team_id
join user D
on A.host_user = D.user_id
) D
group by priv_league_id
Let take it step by step....
First ID, Count for pleague
SELECT private_league_id, COUNT(*) AS total_users
FROM classicseasonmodel_classicseasonuserteamprivateleague
GROUP BY private_league_id;
Now add in Name and host user
SELECT PL.league_name, LC.uCNT, PL.host_user
FROM (SELECT private_league_id AS pID, COUNT(*) AS uCNT
FROM classicseasonmodel_classicseasonuserteamprivateleague
GROUP BY private_league_id ) AS LC
LEFT JOIN private_league PL ON PL.priv_league_id = LC.pID
Now add in host user name
SELECT PL.league_name, LC.uCNT as total_users, hu.name as host_user
FROM (SELECT private_league_id AS pID, COUNT(*) AS uCNT
FROM classicseasonmodel_classicseasonuserteamprivateleague
GROUP BY private_league_id ) AS LC
LEFT JOIN private_league PL ON PL.priv_league_id = LC.pID
LEFT JOIN user hu ON PL.host_user = hu.user_id
Don't know where current position is.
This query will give users and position for each team, join to this and limit by user id to get one users position for each team:
select UT.user_id,
UT.team_id,
ROW_NUMBER() OVER (PARTITION BY team_id ORDER BY total_score DESC) AS team_position
from private_league L
join user_team_private_league_M2M LJ ON L.priv_league_id = LJ.priv_league_id
join user_team UT ON LJ.user_team_id = UT.user_team_id

Calculate round(avg) from same table and join

I have two tables,
users
userid fname usertype
1 Steve vendor
2 Peter vendor
3 John normaluser
4 Mark normaluser
5 Kevin vendor
6 Alan vendor
7 Sean vendor
vendor_rating
id userid rating
1 1 4
2 2 3
3 2 2
4 2 4
5 1 3
6 5 2
7 5 2
userid is foreign key.
i want to show all vendors (only usertype vendor) from user table by descending/ascending average rating even if Vendor's rating is not available on table it should show, its information should display at last in descending, at first in ascending.
I want to fetch all users info from first table so i m using left join :
SELECT
users.name,
users.userid,
users.usertype
FROM users
LEFT JOIN (SELECT
ROUND(AVG(rating)) AS rating_avg,
userid
FROM vendor_rating
ORDER BY rating_avg DESC) ven
ON users.usertype = 'vendor'
AND users.userid = ven.userid
ORDER BY ven.rating_avg ASC;
Please help where am i going wrong.
EDIT:
I get this
userid ROUND(AVG(vr.ratings))
28 5
27 4
16 3
26 2
25 0
NULL NULL
NULL NULL
NULL NULL
NULL NULL
if i use
SELECT vr.userid, ROUND(AVG(vr.ratings)) FROM vendor_rating vr
RIGHT JOIN (SELECT users.fname, users.userid, users.usertype FROM users) u
ON u.id = vr.vendor_id WHERE u.usertype = 'vendor' GROUP BY vr.userid,u.fname
ORDER BY round(avg(vr.ratings)) ASC
i get NULL values from users table whose rating is not available in vendor_rating table those should display userids
Try to this
SELECT
vr.userid,
u.fname,
ROUND(AVG(vr.rating))
FROM vendor_rating vr
INNER JOIN users u
ON u.userid = vr.userid
WHERE u.usertype = 'vendor'
GROUP BY vr.userid,
u.fname
ORDER BY round(avg(vr.rating)) ASC
finally i got it
SELECT users.fname, users.userid,users.usertype
FROM users
LEFT JOIN (select ROUND(AVG (ratings)) AS rating_avg,userid FROM
vendor_rating group by userid order by rating_avg desc ) ven
ON users.id=userid
WHERE users.usertype='vendor'
order by rating_avg desc
Thank you all, for sharing views to get idea to solve my problem.

Weird calculation

Here are my two tables
users
------------------------
id username name surname
2 Foo f b
4 Bar b f
orders
---------------------
id user_id price qty
1 2 3.2 1
2 4 6 4
etc ...
And here is how my query looks like
SELECT
u.name,
u.surname,
COUNT(r.user_id) as total,
SUM(r.price) as total_price,
FROM orders r
LEFT JOIN users u on u.id = r.user_id
WHERE order_id = 4
GROUP BY user_id
Thus I get total of money that user is spent.
The problem is that the calculation is wrong because the user can be bought more than one item of product.
I can't figure out how to do so calculation to include and quanity.
You can try to multiply and sum, like :-
sum(r.price * r.qty) as total_price