Writing a LIMIT subquery within my SQL query - mysql

So I have the following query which fetches active competitions for an organisation, but also aims to fetch the user that is in the lead - for each competition fetched.
The query currently works in that it fetches the competitions, however it currently fetches all the users and I would like to LIMIT 1 on the users fetched, using the SUM(activity_weight) you can see below.
The results come out like this (removed some results to make it easy to see) and in my case, I only want to fetch John and Sally, as they are the leaders of the competitions.
competitionId compName start_date end_date name totalPoints
------------------------------------------------------------
123 First Comp 13-09-09 13-10-09 John 100
123 First Comp 13-09-09 13-10-09 Bob 50
431 Second Comp 13-05-04 13-10-05 Sally 500
431 Second Comp 13-05-04 13-10-05 Jessica 50
I understand that I must use some form of subquery to use the LIMIT, but having a problem nailing the syntax of it.
Any help is much appreciated! THANK YOU
SELECT c.competitionId, c.name, c.start_date, c.end_date, a.userid, u.name,
u.profilePic ,
SUM(activity_weight) AS totalPoints
FROM activity_entries a INNER JOIN users1 u ON u.id = a.userid
INNER JOIN competitions c ON c.competitionId = a.competitionId
WHERE c.organisationId = '$organisation' AND c.start_date < now() AND c.end_date > now()
GROUP BY a.userid, c.competitionId ORDER BY c.id DESC, totalPoints DESC

Try this query
select * from
(select
#rn:=if(#prv=competitionId , #rn+1, 1) as rId,
#prv:=competitionId as competitionId ,
totalPoints,
your_other_columns
from (select * from ...)subquery
join
(select #prv:=0, #rn:=0)tmp
order by
competitionId , totalPoints desc) a
-- only top 2 ordered by points for every competition
where rid<=2
output:
rID competitionId compName start_date end_date name totalPoints
------------------------------------------------------------
1 123 First Comp 13-09-09 13-10-09 John 100
2 123 First Comp 13-09-09 13-10-09 Bob 50
1 431 Second Comp 13-05-04 13-10-05 Sally 500
2 431 Second Comp 13-05-04 13-10-05 Jessica 50
change the last part to where rid<=1 to select top 1

Related

multiple LEFT JOIN using LIMIT on one of the joins

I looked around for answers to this question but all the ones I tried simply didn't work. The other answer suggestions all threw errors for me. Maybe it's because I'm using using MariaDB ?.
SELECT * FROM 'view_winners'
I need top 3 in column 'class'
table view_winners is multiple left joins and I could not figure out how to limit 3 of the left join on table allClasses.
view_winners is:
`$view = "view_winners";
$db->query("DROP $view");
$db->query("CREATE VIEW $view AS
SELECT *
FROM thw22 evnt
LEFT JOIN allUsers usr
ON usr.user_id = evnt.e_owner_id
LEFT JOIN hw_vehicles veh
ON veh.vehicle_id = evnt.e_vehicle_id
LEFT JOIN hw_m_vehicle_class mcls
ON mcls.v_class_id = evnt.e_class_id
LEFT JOIN allClasses cls
ON mcls.cvm_id = cls.class_id
LEFT JOIN hw_v_scores sco
ON sco.v_score_id = evnt.e_score_id
WHERE (cls.class_name <> '' OR cls.class_name IS NOT NULL)
AND (sco.total <> '' OR sco.total IS NOT NULL)
ORDER BY cls.vehicle_type ASC, cls.class_name ASC, sco.total DESC
");`
It's probably best if I could LIMIT 3 on LEFT JOIN allClasses but I can't figure that out. So I figured I would loop through the result and unset rows over 3 in class in PHP. But again I could not figure out how to compare rows as looping through.
I need help with the LIMIT 3 on the JOIN or how to compare the results unsetting rows.
entry
class
score
786
sally
99
234
sally
90
456
bob
45
621
joe
90
964
joe
80
548
joe
66
346
joe
22
900
frank
89
700
frank
86
800
frank
72
123
frank
70
860
frank
50
333
frank
45
Desired results:
entry
class
score
786
sally
99
234
sally
90
456
bob
45
621
joe
90
964
joe
80
548
joe
66
900
frank
89
700
frank
86
800
frank
72
Might this answer help
And to clarify, it appears that for example, you want AT MOST, 3 entries per class (person name per sample data). If one class has only a single entry, get it. However, if someone else has 8 classes you want only the first 3 based on some pre-determined priority ordering, such as top 3 scores.
In your case, the OVER is partitioned by the "class", and the order by will be the score DESC (descending). So having the view give you this extra computed column (per class), you can then filter WHERE finalColumnNameYouAssign <= 3
I just answered similar question yesterday. See How to limit SQL query with JOIN
Here's my solution based on 2 linked tables, users and history for each user. There is also another solution there depending on your MySQL version.
SELECT *
FROM
`users` AS u
LEFT JOIN `history` AS h ON u.id = h.user_id
WHERE
FIND_IN_SET(h.id, (SELECT `list` FROM
(SELECT user_id, SUBSTRING_INDEX(GROUP_CONCAT(id SEPARATOR ','), ',', 3) AS `list` FROM
(SELECT h.user_id, h.id
FROM
`users` AS u
LEFT JOIN `history` AS h ON u.id = h.user_id
) AS `a`
GROUP BY user_id
) AS `b`
WHERE b.user_id = u.id
) )
Instead of having so many joins and confusing myself badly, I made a couple different views that I will need anyway for other statistics. Then by simply ordering by class ASC, score DESC I have a very simple master list of all classes in order then scores highest to lowest (along with all the other joined data). After that I can compare each row and limit 3 as follows:
SELECT * FROM
(SELECT *,
#rn := IF(#prev = class_name,
#rn + 1, 1)
AS rn,
#prev := class_name
FROM view_allScores
JOIN (SELECT #prev := NULL, #rn := 0)
AS vars
) AS T1
WHERE T1.rn <= 3
The confusing thing was that I was trying to add a LIMIT to a join and that kept confusing me.

SQL nested query under WHERE

One of the test questions came by with following schemas, to look for the best doctor in terms of:
Best scored;
The most times/attempts;
For each medical procedures (in terms of name)
[doctor] table
id
first_name
last_name
age
1
Phillip
Singleton
50
2
Heidi
Elliott
34
3
Beulah
Townsend
35
4
Gary
Pena
36
5
Doug
Lowe
45
[medical_procedure] table
id
doctor_id
name
score
1
3
colonoscopy
44
2
1
colonoscopy
37
3
4
ulcer surgery
98
4
2
angiography
79
5
3
angiography
84
6
3
embolization
87
and list goes on...
Given solution as follow:
WITH cte AS(
SELECT
name,
first_name,
last_name,
COUNT(*) AS procedure_count,
RANK() OVER(
PARTITION BY name
ORDER BY COUNT(*) DESC) AS place
FROM
medical_procedure p JOIN doctor d
ON p.doctor_id = d.id
WHERE
score >= (
SELECT AVG(score)
FROM medical_procedure pp
WHERE pp.name = p.name)
GROUP BY
name,
first_name,
last_name
)
SELECT
name,
first_name,
last_name
FROM cte
WHERE place = 1;
It'll mean a lot to be clarified on/explain on how the WHERE clause worked out under the subquery:
How it worked out in general
Why must we match the two pp.name and p.name for it to reflect the correct rows...
...
WHERE
score >= (
SELECT AVG(score)
FROM medical_procedure pp
WHERE pp.name = p.name)
...
Thanks a heap!
Above is join with doctor and medical procedure and group by procedure name and you need doctor names with most attempt and best scored.
Subquery will join by procedure avg score and those who have better score than avg will be filtered.
Now there can be multiple doctor better than avg so taken rank by procedure count so most attempted will come first and then you taken first to pick top one

SQL left join two times

The user table looks like this:
user_id
name
surname
1
a
aa
2
b
bb
3
c
cc
The book's table looks like this:
user_id
book_name
1
book1
1
book2
1
book3
2
book1
The expenses table looks like this:
user_id
amount_spent
date
1
10
2020-02-03
1
30
2020-02-02
1
10
2020-02-01
1
15
2020-01-31
1
13
2020-01-15
2
15
2020-02-01
3
20
2020-02-01
The result which I want:
CountUsers
amount_spent
2
65
Explanation: I want to count how many users have book1 and how much total they spend on a date between 2020-02-01 - 2020-02-03.
Now how the query should look like?
I am using MySQL version 8.
I have tried:
SELECT
count(*), sum(amount_spend) as total_amount_spend
FROM
(select sum(amount_spend) as amount_spend
FROM expanses
LEFT JOIN books ON books.user_id = expanses.user_id WHERE books.book_name ='book1 GROUP BY expanses.user_id) src'
And the result is wrong because I am getting a higher amount_spend than in my table result above. I think while joining the table there are some duplicates but I do not know how to fix them.
I want to count how many users have book1 and how much total they spend on a date between 2020-02-01 - 2020-02-03.
I am thinking:
select count(*), sum(e.amount_spent)
from user_books ub join
expenses e
on ub.user_id = e.user_id
where book_name = 'book1';
Note: This assumes that user_books doesn't have duplicate rows.
FIDDLE
You miss the date part in your code.
SELECT
count(*), sum(amount_spent) as total_amount_spend
FROM
(select sum(amount_spent) as amount_spent
FROM expanses
LEFT JOIN books ON books.user_id = expanses.user_id
WHERE books.book_name ='book1'
and expanses.date between '2020-02-01' and '2020-02-03'
GROUP BY expanses.user_id) src;
will do a job.
Please note that you don't need to have left join here (unless you're sure that it may happen that no expenses at all for given user will be), and you don't need to have grouping in subquery. So your query could look like:
select count(distinct expanses.user_id), sum(amount_spent) as amount_spent
from expanses
inner join books on books.user_id = expanses.user_id
where books.book_name ='book1'
and expanses.date between '2020-02-01' and '2020-02-03';

MySQL: Find the average value per entry for the last x records

I'm trying to figure out how to grab the average rating for each salesperson over their last 100 ratings if they are currently employed, and if they have an average rating less than 3 (out of 5).
I have the following tables (leaving out information that isn't needed in the query):
users
id name employed
-----------------------
1 John 1
2 Sue 1
3 Bob 0
...
sales
id users_id
------------------
100 3
101 2
102 3
103 1
...
ratings
sales_id rating
-----------------
100 4
101 5
102 5
103 2
...
The current query I have searches everything and returns the average for all orders ever but I want it to only grab the most recent 100 ratings (or less if the salesperson hasn't sold that many items), still excluding anyone that is no longer employed or has a rating for their last 100 orders greater than 3. This is the current query:
SELECT u.name, avg(r.rating) as avg_rating, count(r.rating)
FROM users AS u
JOIN sales AS s ON s.users_id = u.id
JOIN ratings AS r ON r.sales_id = s.id
WHERE u.employed = 1
GROUP BY u.id
HAVING avg_rating <= 3;
Any help would be great! Thanks! :D
You can use my sql variables to keep track of the number of ratings so that you can get only recent 100 ratings , ordering by sales_id so you get recent ratings.
SQL FIDDLE DEMO
SELECT T.name, avg(T.rating) as avg_rating, count(T.rating)
FROM
(
SELECT u.name, r.rating, #num := if (#name = name, #num+1, 1) as rn,
#name:= name as var_name
FROM users AS u
JOIN sales AS s ON s.users_id = u.id
JOIN ratings AS r ON r.sales_id = s.id
AND u.employed = 1
JOIN ( select #name :='' , #num :=1) var
order by sales_id desc
)T
where T.rn <=100
GROUP BY T.name
HAVING avg_rating <= 3

SQL Server - To find member with the highest fine charged (based on below scenario)

i have a scenario where there are 2 tables
tblMember with 2 columns MemberID & MemberName
100 Aakash
101 Seema
tblLoanHistory with columns LoanID, MemberID, FineCharged
1 100 30
2 100 60
3 101 30
I want to find the member whose total fine charged is highest.
output should be
100 Aakash 90 (60+30=90)
can anybody please help
thanks in advance.
SELECT TOP 1
M.MemberID, M.MemberName,
SUM(LH.FineCharged)
FROM
tblMember M
JOIN
tblLoanHistory LH ON LH.MemberID=M.MemberID
GROUP BY
M.MemberID, M.MemberName
ORDER BY
SUM(LH.FineCharged) DESC
Update... Oops! Added JOIN condition
SELECT M.MemberID ,M.MemberName,SUM(LH.FineCharged) FineCharged
FROM tblMember M
INNER JOIN tblLoanHistory LH ON LH.MemberID=M.MemberID
GROUP BY M.MemberID, M.MemberName