AVG and COUNT functions are not giving proper result in SQL query - php

I have following query and I am trying to display members in the order where members with highest average rating will be displayed first, if more than one members have same average rating then highest number of rating will be considered.
Here,
Member A has been rated by 3 visitors and average rating value is 5 while
Member B has been rated by 2 visitors and average rating value is 5
So according to below query, Member A should display first because he has 5 average rating and rated by 3 persons while Member B should display on second position.
But Member B is displaying first and Member A is displaying second so this is problem. Please let me know what wrong I am doing in query.
SELECT m.*,mc.*
FROM t_member m
LEFT JOIN tr_member_category mc ON m.memberpkid=mc.memberpkid
LEFT JOIN tr_comment c
ON m.memberpkid=c.memberpkid
AND c.approved='YES' AND c.visible='YES'
WHERE m.visible='YES' AND m.approved='YES'
AND m.gender='FEMALE' AND mc.archivecatpkid=1
GROUP BY m.memberpkid
ORDER BY avg(c.ratingvalue) DESC, COUNT(c.ratingvalue) DESC
Thank you very much in advance,
KRA

Move the AVG and COUNT functions out of the ORDER clause, into the SELECT clause. Give them a good name and then order on those.
i.e.
SELECT m.*, mc.*, AVG(c.ratingvalue) AS average_rating, COUNT(c.ratingvalue) AS number_of_ratings
FROM t_member m
LEFT JOIN tr_member_category mc ON m.memberpkid=mc.memberpkid
LEFT JOIN tr_comment c
ON m.memberpkid=c.memberpkid
AND c.approved='YES' AND c.visible='YES'
WHERE m.visible='YES' AND m.approved='YES'
AND m.gender='FEMALE' AND mc.archivecatpkid=1
GROUP BY m.memberpkid
ORDER BY average_rating DESC, number_of_ratings DESC
EDIT: remember that an aggregate function returns a value. If you put them in an ORDER BY clause, it reads "order by this value", as opposed to "order by this column". Keep that in mind whilst writing SQL.

Related

CodeIgniter: Join more than 3 tables

I have five tables:
Bill: no(pk),date, time, total
BillOrderRelation: no(fk), order_id(fk)
Order: order_id(pk), menu_id(fk), quantities, total
Menu: menu_id(pk), category_id(fk), menu_name, stock, price
Category: category_id(pk), category_name, colour
In my case, I have to retrieve which menu that has a highest sales in one day range, 7 days range, and 30 days range.
I've already succeed retrieve those information, but i think it's too complicated. First I have to retrieve the date on Bill, and then find the order in BillOrderRelation, and then find the Menu, and find the Category name. It includes a lot of queries and complex way to do the summing stuff for the same menu.
My question is, is that possible to query all those table in one query to retrieve just menu.menu_name, order.quantities, order.total, category.name and it's included the sum stuff for the same menu retrieved?
I've already succeed make a query for three table without using time range like this..
SELECT
menu.menu_name as top_item,
SUM(order.quantities) AS count_sold,
SUM(order.total) AS amount,
category.nama AS categories
FROM
menu, order, category
WHERE
menu.mennu_id = bill.menu_id
AND category.category_id = menu.category_id
GROUP BY
bill.menu_id, menu.menu_name, category.category_name
ORDER BY
count_sold DESC
Is there any tricky way for the case above?
Update for PostgreSQL
I believe you want something like this:
SELECT
m.menu_name AS top_item
, c.name AS category
, SUM(o.quantities) AS sum_quantity
, SUM(o.total) AS sum_total
FROM
menu m
JOIN
category c
ON c.category_id=m.category_id
JOIN
order o
ON o.menu_id=m.menu_id
JOIN
billorderrelation bor
ON bor.order_id=o.order_id
JOIN
bill b
ON b.no=bor.no
WHERE
b.date >= (CURRENT_DATE - INTERVAL '7 days')
GROUP BY
m.menu_id
ORDER BY
sum_quantity DESC
;
CURRENT_DATE allows you to get the current date (according to the timezone specified in your database). Read more:
https://www.postgresql.org/docs/8.2/static/functions-datetime.html#FUNCTIONS-DATETIME-CURRENT

MYSQL: Total values except highest and lowest then multiply

I have stumbled upon a problem that I can't get my head around.
I have the following topic related tables:
dive(ID,Type,Diver_id,Difficulty,Contest_id)
results(ID,Points,Judge_Id,Dive_id)
divers(ID,Name,Country)
My SELECT statement is meant to list the diver.Name, diver.Country and "total points"
The total points is supposed to be (total += ((dive_total -min -max)*difficulty))
Let's say a diver did 3 dives. Every dive has been rated by ~4 judges and every dive has it's own difficulty. The lowest/highest score per dive is supposed to be removed. Then added together, multiplied with the difficulty and then added to the persons total points.
I have got a list that shows diver.Name, diver.Country and Total.
But the total is only the actual total without the subtraction and multiplication.
Query:
SELECT
divers.Name,
divers.Country,
SUM(results.Points) AS Total
FROM results
INNER JOIN dive
ON results.Dive_id = dive.ID
INNER JOIN divers
ON dive.Diver_id = divers.ID
WHERE dive.Contest_id = '1'
GROUP BY divers.Name
ORDER BY Total DESC
Don't mind the contest_id. It's always '1'.
Please ask for more information if needed. Maybe the question is very silly. I am not a advanced "database guy". Sorry for bad English in advance.
(It is later printed in php/html. Maybe a language combination between mysql and php is a better way to move forward?)
I think what you want is this
SELECT
divers.Name,
divers.Country,
((SUM(results.Points) - MAX(results.Points) - MIN(results.Points)) * dive.Difficulty) AS Total
FROM results
INNER JOIN dive
ON results.Dive_id = dive.ID
INNER JOIN divers
ON dive.Diver_id = divers.ID
WHERE dive.Contest_id = '1'
GROUP BY divers.Name
ORDER BY Total DESC
Otherwise I would just change the SELECT to include:
MIN(results.Points) as maxPoints,
MAX(results.Points) as minPoints,
SUM(results.Points) as totalPoints
and then do the math side of it using PHP
Thank you ode2k! Very helpful. The end result with your code + some GROUP BY
SELECT
divers.Name,
divers.Country,
SUM(results.Points * dive.Difficulty) - MAX(results.Points * dive.Difficulty) - MIN(results.Points * dive.Difficulty) AS Total
FROM results
INNER JOIN dive
ON results.Dive_id = dive.ID
INNER JOIN divers
ON dive.Diver_id = divers.ID
WHERE dive.Contest_id = '1'
GROUP BY divers.Name,
dive.ID,
dive.Difficulty,
results.Dive_id
ORDER BY Total DESC
And then I added the the scores together in PHP to get every name just once.

ORDER BY with two columns

I have two tables in the mysql database:
trips(id,name,desc,createdat)
trip_ratings(id,ratingvalue,tripid,userid)
I want to get 100 most recent trips sorted by higher average ratings.
I have tried following mysql query:
SELECT AVG(ratingvalue),tripid,tripcreatedat FROM trip_ratings
INNER JOIN trips on trip_ratings.tripid = trips.id
GROUP BY trip_ratings.tripid
ORDER BY AVG(ratingvalue) DESC, tripcreatedat DESC
LIMIT 100
But as it sorts by rating value first I only get trips sorted by higher ratings.
Is it possible within a single query? can anyone hint me what should I do?
EDIT: example:
I have data in trip_ratings table like this:
and from my tried query I can get results like this.
But my problem is: Get 100 most recent trips sorted by higher average ratings.
Instead of downvoting and close votes, can anyone have any solution for that or can anyone give me a hint that is it possible within a single query? thanks.
One option is to use an inline view, to first get the "100 most recent trips".
Then join that to trip_ratings, to calculate the "average rating" and order by that result.
SELECT m.id AS tripid
, AVG(r.ratingvalue) AS average_rating
, m.tripcreatedat
FROM ( SELECT t.id
, t.tripcreatedat
FROM trips t
ORDER BY t.tripcreatedat DESC
LIMIT 100
) m
LEFT
JOIN trip_ratings r
ON r.tripid = m.id
GROUP BY m.id, m.tripcreatedat
ORDER BY AVG(r.ratingvalue) DESC
If there are multiple trips that have the same average rating, it's indeterminiate what order those will be returned in. You can add other expressions to the order by to make it more deterministic.
ORDER BY AVG(r.ratingvalue) DESC, m.tripcreatedat DESC, m.id DESC
This isn't the only way to do it. There are other approaches that will achieve an equivalent result.

MySQL select statement - How to calculate the current ranking

I have a table called user_rankings where votes (voted) are stored for each user. I want to display the current ranking of users (this week) that depends on how much votes the user got.
example to clarify:
RANK-NR, USERNAME, VOTED,
1, name1, 18 times
2, name1, 16 times
(my ranking here), myname, 13 times
In this example my ranking should be 3. If I'd have 17 votes, I would be number 2. If there would be five users above me, I would be number 8. I guess you get the point.
Now I can display the ranking number easily with an incrementing $i in PHP. But I only want to show a list limited to ten users (a top ten list) and directly after that my current ranking, if I'm not already in that top ten list. So I'm just wondering how to get my exact ranking number using MySQL.
I'm assuming to have hundreds of users in this list with a different amount of votes.
This is my statement at the moment:
SELECT
`voted`
FROM `users_ranking`
WHERE
`uid`='".$_SESSION['uid']."'
AND
WEEKOFYEAR(`date`)=WEEKOFYEAR(NOW())
LIMIT 1
I can't give you the exact code, but i think the following can give you some idea
select 'RANK-NR', 'USERNAME', 'VOTED' from
(
select 'RANK-NR', 'USERNAME', 'VOTED', rank() over (order by 'voted' desc) as rank
from users_ranking
where
uid='".$_SESSION['uid']."'
AND
WEEKOFYEAR(date)=WEEKOFYEAR(NOW())
) as abc
where
rank<11
i think rank() over (order by<>) should work
I just found out myself that this solution works:
SELECT *
FROM
(
SELECT #ranking:= #ranking + 1 rank,
a.`uid`
FROM `users_ranking` a, (SELECT #ranking := 0) b
ORDER BY a.`votes` DESC
) s
WHERE `uid`='".$_SESSION['uid']."'
AND
WEEKOFYEAR(`date`)=WEEKOFYEAR(NOW())
LIMIT 1
OK, example to go with my comment. What you have will often work, but there is nothing to force MySQL to do the sort before it applies the ranking.
As such using an extra level of sub query would give you this (not tested). The inner sub query is getting all the user ids for the relevant week in the right order, while the next outer sub query applies the ranking to this ordered result set. The outer query just gets the single returned row you require.
SELECT c.rank, c.uid
FROM
(
SELECT #ranking:= #ranking + 1 rank, a.uid
FROM
(
SELECT uid, votes
FROM `users_ranking`
WHERE WEEKOFYEAR(`date`) = WEEKOFYEAR(NOW())
ORDER BY votes DESC
) a,
(SELECT #ranking := 0) b
) c
WHERE c.uid = '".$_SESSION['uid']."'
LIMIT 1
Another possibility avoiding the sub query and also avoiding the need for a variable is to do a join. This is (mis)using HAVING to slim down the result to the single row you are interested in. Down side of this solution is that if multiple users have the same score they will each get the same ranking.
SELECT b.uid, COUNT(a.uid)
FROM users_ranking a
LEFT OUTER JOIN users_ranking b
ON WEEKOFYEAR(a.`date`) = WEEKOFYEAR(b.`date`)
AND a.votes >= b.votes
GROUP BY b.uid
HAVING b.uid = '".$_SESSION['uid']."'
EDIT
To give the top 10 rankings:-
SELECT b.uid, COUNT(a.uid) AS rank
FROM users_ranking a
LEFT OUTER JOIN users_ranking b
ON WEEKOFYEAR(a.`date`) = WEEKOFYEAR(b.`date`)
AND a.votes >= b.votes
GROUP BY b.uid
ORDER BY rank
LIMIT 10
Although in this case it might be quicker to use a sub query. You could then put the LIMIT clause in the sub query with the ORDER BY, hence it would only need to use the variables to add a rank to 10 rows.
I am not sure how to combine that with the query for a single user, mainly as I am not sure how you want to merge the 2 results together.

mysql calculate percentage between two sub queries

I am trying to work out the percentage of a number of students who meet certain criteria.
I have 3 separate tables that I need to get data from, and then I need to get the total from one table (student) as the total of students.
Then I need to use this total, to divide the COUNT of the no of students in the 2nd query.
So basically I am trying to get a count of ALL the students that are in the DB first.
Then count the no of students that appear in my main query (the one returning the data).
Then I need to perform the calculation that will take the noOfStudents (2) and divide by the main total (24) (no of students in DB) then *100 to give me the percentage of students who have met the criteria in the main query.
This is what I have so far:
SELECT * FROM (
(
SELECT s.firstname, s.lastname, s.RegistrationDate, s.Email, d.ReviewDate,(r.description) AS "Viva" , COUNT(*) AS "No of Students"
FROM student s
INNER JOIN dates d
ON s.id=d.student_identifier
INNER JOIN reviews r
ON d.review_Identifier=r.id
WHERE r.description = "Viva Date"
GROUP BY s.student_identifier
ORDER BY s.student_identifier)
) AS Completed
WHERE Completed.ReviewDate BETWEEN '2012-01-01' AND '2014-12-01'
;
I need to output the fields following the second SELECT and this data in turn will be displayed via PHP/HTML code on a page (the BETWEEN dates will be sent via '%s').
I wondered if I should be using 2 separate queries and then getting the value (24) from the first query to perform the calculation in the second query, but I have not been able to work out how to save as 2 separate queries and then reference the first query.
I am also not sure if it is possible to display an overall % total at the same time as outputting the individual rows that meet the criteria?
I am trying to teach myself SQL, so I apologise if I have made any glaring mistakes/assumptions in any of the above, and would appreciate any advice that's out there.
Thank you.
Could you do this?
SELECT COUNT(*) as TotalPopulation,
COUNT(d.student_identifier='student') as TotalStudents,
COUNT(d.student_identifier='student')/ count(*) *100 as Percentage of students
from students s
inner join dates d
on s.id = d.student_identifier
inner join reviews r
on r.id = d.review_Identifier
WHERE d.ReviewDate BETWEEN '2012-01-01' AND '2014-12-01' and r.description = 'Viva Date';
You do not need first name last name if you are just looking for counts, necessarily.
This get's the count(*) of table, then whatever flag you use to identify a student in the second count(), you just had it grouped by before, which could give you wrong results considering there's much else in your select before aggregation.
You could also try:
SELECT d.student_identifier, s.firstname, s.lastname,
s.RegistrationDate, s.Email, d.ReviewDate,(r.description) AS "Viva"
FROM student s
INNER JOIN dates d
ON s.id=d.student_identifier
INNER JOIN reviews r
ON d.review_Identifier=r.id
WHERE r.description = "Viva Date" and d.ReviewDate BETWEEN '2012-01-01' AND '2014-12-01'
ORDER BY s.student_identifier
Now, if you want to return a list, that's the second one, if you want to return a count, you would use the first query and adjust to your student_identifier.

Categories