Ranking multiple entries sql - mysql

I have a table in which I log the the visits each user has on it's profile page.
Given the fact that the table entries are like
id user time ip
is it possible to display the ranking of a certain user's visits using a sigle query?
Example: Your ranking: #400 - meaning the user ranks 400 on the number of page hits.

Here is one method. The innermost subquery counts the number of visits for each user. The next subquery calculates a variable called #uservisits which stores the value for the given user. The outer query then uses this to calculate the rank:
select count(*) as rank
from (select user, numvisits, #uservisits := if(user = $user, numvisits, #uservisits)
from (select user, count(*) as numvisits
from table t
group by user
) t cross join
(select #uservisits := 0) init
) t
where numvisits >= #uservisits

This sort of thing?
SELECT #rank := #rank+1 AS rank, province, cityCount
FROM
( SELECT #rank := 0 ) z
JOIN
( SELECT province, COUNT(*) AS cityCount
FROM Canada
GROUP BY province
) x
ORDER BY cityCount DESC;
+------+---------------------------+------------+
| rank | province | cityCount |
+------+---------------------------+------------+
| 1 | Quebec | 1045 |
| 2 | Ontario | 891 |
| 3 | British Columbia | 716 |
| 4 | Saskatchewan | 573 |
| 5 | Alberta | 573 |
| 6 | Newfoundland and Labrador | 474 |
| 7 | Nova Scotia | 331 |
| 8 | Manitoba | 299 |
| 9 | New Brunswick | 210 |
| 10 | Yukon | 114 |
| 11 | Nunavut | 107 |
| 12 | Northwest Territories | 94 |
| 13 | Prince Edward Island | 57 |
+------+---------------------------+------------+

Related

Counting "subcolumns" with mySQL

Guys let me make myself clear. I'm studying MYSQL and practicing the function "count()". I have a table called "City", where I have ID, name, CountryCode, district, and Population. My first idea was to know how many cities I have by country
SELECT *, Count(name) as "total" FROM world.city GROUP BY countrycode;
It worked, an extra column was created with the number of cities by each country. I would like to know how many countries I have by counting the number of distinct rows (I know that a have this information on the bottom of the WorkBench, but I would like to know to make this information appear on my query). I tried to add a Count(CountryCode), but it didn't work as I was expecting, a number 4079 appeared, which is the total number of cities that I have. I figured out that my "Count()" is calculating the number of rows inside each Country, not counting the number of codes that I have for each country. Is that possible to get this information?
(A mini-lesson for a Novice.)
The first thing to learn is that COUNT(*) is the usual way to use COUNT. And you get the number of rows. In contrast, COUNT(name) counts the number of rows with non-NULL name values.
Then comes the way to use DISTINCT. It is not a function. So COUNT(DISTINCT a,b) counts the number of different combinations of a and b. And COUNT(DISTINCT(a)) though it works 'fine' and 'correctly', the parens are redundant. So use COUNT(DISTINCT a).
Don't use * with GROUP BY. That is, SELECT *, ... GROUP BY ... is improper. The usual way to say something like your query is
SELECT countrycode, COUNT(*) AS "total"
FROM world.city
GROUP BY countrycode;
For provinces in Canada (which I happen to have a table of):
SELECT province, COUNT(*) AS "total" FROM world.canada GROUP BY province;
+---------------------------+-------+
| province | total |
+---------------------------+-------+
| Alberta | 573 |
| British Columbia | 716 |
| Manitoba | 299 |
| New Brunswick | 210 |
| Newfoundland and Labrador | 474 |
| Northwest Territories | 94 |
| Nova Scotia | 331 |
| Nunavut | 107 |
| Ontario | 891 |
| Prince Edward Island | 57 |
| Quebec | 1045 |
| Saskatchewan | 573 |
| Yukon | 114 |
+---------------------------+-------+
Note that a few cities show up in multiple provinces:
SELECT COUNT(DISTINCT city), COUNT(*) FROM world.canada;
+----------------------+----------+
| COUNT(DISTINCT city) | COUNT(*) |
+----------------------+----------+
| 5248 | 5484 |
+----------------------+----------+
Munch on this; there are some more lessons to learn:
SELECT city, COUNT(*) AS ct, GROUP_CONCAT(DISTINCT state)
FROM world.us
GROUP BY city
ORDER BY COUNT(*)
DESC LIMIT 11;
+-------------+----+----------------------------------+
| city | ct | GROUP_CONCAT(DISTINCT state) |
+-------------+----+----------------------------------+
| Springfield | 11 | FL,IL,MA,MO,NJ,OH,OR,PA,TN,VA,VT |
| Clinton | 10 | CT,IA,MA,MD,MO,MS,OK,SC,TN,UT |
| Madison | 8 | AL,CT,IN,ME,MS,NJ,SD,WI |
| Lebanon | 8 | IN,ME,MO,NH,OH,OR,PA,TN |
| Auburn | 7 | AL,CA,IN,ME,NH,NY,WA |
| Burlington | 7 | IA,MA,NC,NJ,VT,WA,WI |
| Washington | 7 | DC,IL,IN,MO,NC,PA,UT |
| Farmington | 7 | ME,MI,MN,MO,NH,NM,UT |
| Canton | 6 | GA,IL,MA,MI,MS,OH |
| Monroe | 6 | GA,LA,MI,NC,WA,WI |
| Lancaster | 6 | CA,NY,OH,PA,SC,TX |
+-------------+----+----------------------------------+
As for the number of cities in a country, that belongs in a the table Countries, not in the table Cities. Then use a JOIN when you want to put them together.

How to select one row per user conditionally?

I have the following query:
select count(1) num, business_id, user_id FROM `pos_transactions`
group by user_id, business_id
order by user_id
It returns this:
+--------+-------------+---------+
| num | business_id | user_id |
+--------+-------------+---------+
| 3 | 503 | 12 |
| 7 | 33 | 12 |
| 1 | 771 | 13 |
| 2 | 86 | 13 |
| 1 | 772 | 13 |
| 4 | 652 | 14 |
| 4 | 567 | 14 |
+--------+-------------+---------+
I need to select only one row per user_id, the one which has a bigger num value. If all num values for a user are identical, then just one of them should be selected randomly (i.e. user #14). So, here is the expected result:
+--------+-------------+---------+
| num | business_id | user_id |
+--------+-------------+---------+
| 7 | 33 | 12 |
| 2 | 86 | 13 |
| 4 | 567 | 14 |
+--------+-------------+---------+
Any idea how can I do that?
I guess the solution will be something related to limit 1 per user. But I have no idea how I should write the query.
All I want to do is making the table unique per user_id, and the logic is selecting rows that have bigger num.
Use MAX() and FIRST_VALUE() window functions:
SELECT DISTINCT
MAX(COUNT(*)) OVER (PARTITION BY user_id) num,
FIRST_VALUE(business_id) OVER (PARTITION BY user_id ORDER BY COUNT(*) DESC) business_id,
user_id
FROM pos_transactions
GROUP BY user_id, business_id
ORDER BY user_id

I need to select a group total as well as the individual rows?

I have the following result set...
Name | Team | Score
A | 1 | 10
B | 1 | 11
C | 2 | 9
D | 2 | 15
and I want to add an extra column to the results set for the team score so I can sort on it and end up with the following data set...
Name | Team | Score | TeamScore
D | 2 | 15 | 24
C | 2 | 9 | 24
B | 1 | 11 | 21
A | 1 | 10 | 21
So I end up with the top team first with the members in order.
My actual data is way more complicated than this and pulls in data from several tables but if you can solve this one I can solve my bigger issue!
Join the table to a query that returns the total for each team:
select t.*, s.teamscore
from tablename t
inner join (
select team, sum(score) teamscore
from tablename
group by team
) s on s.team = t.team
order by s.teamscore desc, t.team, t.score desc
See the demo.
Results:
| Name | Team | Score | teamscore |
| ---- | ---- | ----- | --------- |
| D | 2 | 15 | 24 |
| C | 2 | 9 | 24 |
| B | 1 | 11 | 21 |
| A | 1 | 10 | 21 |
In MySQL 8+, we can simplify and just use SUM as an analytic function:
SELECT
Name,
Team,
Score,
SUM(Score) OVER (PARTITION BY Team) AS TeamScore
FROM yourTable
ORDER BY
TeamScore DESC,
Score;

Retrieving a variable number of rows using a table join

This is an addition layer of complexity on another question I asked here: Using GROUP BY and ORDER BY in same MySQL query
Same table structure and problem, except this time imagine that the past_election table is now set up as...
| election_ID | Date | jurisdiction | Race | Seats |
|-------------|------------|----------------|---------------|-------|
| 1 | 2016-11-08 | federal | president | 1 |
| 2 | 2016-11-08 | state_district | state senator | 2 |
(last record has seats set as 2 instead of 1.)
I want to use the Seats number to grab different numbers of records, ordered by the number of votes, for each group. So in this case with the following additional tables...
candidates
| Candidate_ID | FirstName | LastName | MiddleName |
|--------------|-----------|----------|------------|
| 1 | Aladdin | Arabia | A. |
| 2 | Long | Silver | John |
| 3 | Thor | Odinson | NULL |
| 4 | Baba | Yaga | NULL |
| 5 | Robin | Hood | Locksley |
| 6 | Sherlock | Holmes | J. |
| 7 | King | Kong | Null |
past_elections-candidates
| ID | PastElection | Candidate | Votes |
|----|--------------|-----------|-------|
| 1 | 1 | 1 | 200 |
| 2 | 1 | 2 | 100 |
| 3 | 1 | 6 | 50 |
| 4 | 2 | 3 | 75 |
| 5 | 2 | 4 | 25 |
| 6 | 2 | 5 | 150 |
| 7 | 2 | 7 | 100 |
I would expect the following output:
| election_ID | FirstName | LastName | votes | percent |
|-------------|-----------|----------|-------|---------|
| 1 | Aladdin | Arabia | 200 | 0.5714 |
| 2 | Robin | Hood | 150 | 0.4286 |
| 2 | King | Kong | 100 | 0.2857 |
I've tried setting a variable and using that with a LIMIT statement but variables don't work in limits. I've also tried using ROW_NUMBER() (I'm not using MySQL 8.0 so this won't work but I'd be willing to upgrade if it did) or a related workaround like #row_number := IF ... and then filtering based on the row number but nothing has worked.
Last tried query:
SELECT pe.election_ID as elec,
pe.Seats as s,
pecs.row_num,
c.FirstName,
c.LastName,
pecs.max_votes AS votes,
pecs.max_votes / pecs.total_votes AS percent
FROM past_elections pe
JOIN `past_elections-candidates` pec ON pec.PastElection = pe.election_ID
JOIN (SELECT PastElection,
Candidate,
#row_num := IF(PastElection = #current_election, #current_election + 1, 1) as row_num,
MAX(Votes) AS max_votes,
SUM(Votes) AS total_votes,
#current_election := PastElection
FROM `past_elections-candidates`
GROUP BY PastElection) pecs ON pecs.PastElection = pec.PastElection AND pecs.row_num <= pe.Seats
JOIN candidates c ON c.Candidate_ID = pec.Candidate
Use MySQL 8 regardless ;)
Use ROW_NUMBER to order the past elections:
SELECT *, ROW_NUMBER() OVER(PARTITION BY pastelection ORDER BY votes DESC) as rown
FROM `past_elections-candidates`
Join this to past_elections as a subquery (this is just the bit you're stuck on with the "using pe.seats to vary the number of rows returned per election" and doesn't include the percent bits:
SELECT *
FROM
past_elections pe
INNER JOIN
(
SELECT *, ROW_NUMBER() OVER(PARTITION BY pastelection ORDER BY votes DESC) as rown
FROM `past_elections-candidates`
) pecr
ON pecr.pastelection = pe.electionid AND
pecr.rown <= pe.seats
If you want to test things out on 8 before you upgrade, loads of the db fiddle sites support v8
ps; percent-y stuff can be done at the same time as the ROW_NUMBER with eg:
votes/SUM(votes) OVER(PARTITION BY past_election)
eg for election ID 1 that sum will be 200+100+50, giving 200/350 = ~57%
SELECT *, votes/SUM(votes) OVER(PARTITION BY past_election) as pcnt, ROW_NUMBER() OVER(PARTITION BY pastelection ORDER BY votes DESC) as rown
FROM `past_elections-candidates`
You need to calc it before filtering
I don't have the right fields listed but this is as close as I'll probably get tonight... I've gotten the rows I need but need to join the candidate table to get the name out...
Using Dense_Rank seems to work for this...
SELECT * FROM (
SELECT pec.PastElection,
c.FirstName,
c.LastName,
pec.Votes,
pecs.totalVotes,
pe.Seats as s,
DENSE_RANK() OVER(PARTITION BY PastElection ORDER BY Votes DESC) as rank_votes
FROM `past_elections-candidates` pec
JOIN (SELECT PastElection,
Max(Votes) as maxVotes,
Sum(Votes) as totalVotes
FROM `past_elections-candidates`
GROUP BY PastElection) pecs ON pecs.PastElection = pec.PastElection
JOIN `past_elections` pe ON pec.PastElection = pe.election_ID
JOIN candidates c ON c.Candidate_ID = pec.Candidate
) t WHERE rank_votes <= s;
This results in
| PastElection | FirstName | LastName | Votes | totalVotes | s | rank_votes |
|--------------|-----------|----------|-------|------------|---|------------|
| 1 | Aladdin | Arabia | 200 | 350 | 1 | 1 |
| 2 | Robin | Hood | 150 | 350 | 2 | 1 |
| 2 | King | Kong | 100 | 350 | 2 | 2 |
I guess it's just kind of messy having the rank_votes and s columns in the data, but that's honestly fine with me if it gets the results I need.

MYSQL Ranking Issue

I am stuck on how to modify my query to get the ranking results that I am looking for. I have been looking at the questions and queries on SO but can not get the results. I can get the query to do the calculation and return data but the "rank" is not correct. Any nudge would be fantastic!
Table 1 contains school data:
+-----+------------+---------------+
| SID | schoolName | schoolCountry |
+-----+------------+---------------+
| 1 | ASD | UAE |
| 2 | ASIJ | Japan |
| 3 | ASP | France |
+-----+------------+---------------+
Table 2 contains review data (my query has more columns, but this is how it works).
+-----+----------+--------+----+----+----+----+----+
| RID | schoolID | active | Q1 | Q2 | Q3 | Q4 | Q5 |
+-----+----------+--------+----+----+----+----+----+
| 1 | 1 | 1 | 8 | 9 | 5 | 1 | 9 |
| 2 | 2 | 1 | 7 | 6 | 6 | 7 | 9 |
| 3 | 1 | 0 | 1 | 4 | 7 | 8 | 5 |
| 4 | 3 | 1 | 2 | 10 | 6 | 7 | 5 |
+-----+----------+--------+----+----+----+----+----+
I am trying to create different ranks (Country, Region, Overall) by averaging the overall review score for a school. I am currently working on the country ranking and my query so far is.
SELECT SID, schoolName, rank, average
FROM (
SELECT (#rank := #rank + 1) AS rank,schools.SID, schools.schoolName,
ROUND(AVG(IF(reviews.active = 1, ((Q1+Q2+Q3+Q4+Q5+Q6+Q7+Q8+Q9+Q10+Q11+Q12+Q13+Q14+Q15+Q16+Q17+Q18+Q19+Q20+Q21+Q22+Q23+Q24+Q25+Q26+Q27+Q28+Q29+Q30+Q31+Q32+Q33+Q34+Q35+Q36+Q37+Q38+Q39+Q40+Q41+Q42+Q43+Q44+Q45+Q46+Q47+Q48+Q49+Q50+Q51+Q52)/(52*10)*10), NULL)) ,1) AS average
FROM schools
RIGHT JOIN reviews ON reviews.schoolID = schools.SID
CROSS JOIN (SELECT #rank := 0) AS vars
WHERE schools.schoolCountry = 'United Arab Emirates'
GROUP BY schools.SID
) as order_ranked
ORDER BY `order_ranked`.`average` DESC
My output comes back as:
+-----+----------------------------------------+------+---------+--+
| SID | schoolName | rank | average | |
+-----+----------------------------------------+------+---------+--+
| 568 | GEMS Wellington Primary School | 3 | 8.3 | |
| 1 | American School of Dubai | 1 | 8.1 | |
| 561 | Dubai American Academy | 4 | 7.9 | |
| 560 | Deira International School | 11 | 7.7 | |
| 569 | GEMS World Academy Dubai | 10 | 7.0 | |
| 570 | Greenfield Community School | 8 | 6.7 | |
| 565 | GEMS American Academy Abu Dhabi | 6 | 6.0 | |
| 584 | Universal American School | 5 | 5.9 | |
| 558 | American Academy In Al Mizhar | 7 | 5.5 | |
| 579 | The Cambridge High School Abu Dhabi | 9 | 4.8 | |
| 576 | Ras Al Khaimah English Speaking School | 2 | 4.3 | |
+-----+----------------------------------------+------+---------+--+
As you can see it ranks, but not correctly. I just can't figure out why.
I'm not sure why you would be using right join for this. In MySQL, you often have to sort in the subquery before using the variables.
SELECT SID, schoolName, (#rank := #rank + 1) AS rank, average
FROM (SELECT s.SID, s.schoolName,
ROUND(AVG(Q1+Q2+Q3+Q4+Q5+Q6+Q7+Q8+Q9+Q10+Q11+Q12+Q13+Q14+Q15+Q16+Q17+Q18+Q19+Q20+Q21+Q22+Q23+Q24+Q25+Q26+Q27+Q28+Q29+Q30+Q31+Q32+Q33+Q34+Q35+Q36+Q37+Q38+Q39+Q40+Q41+Q42+Q43+Q44+Q45+Q46+Q47+Q48+Q49+Q50+Q51+Q52)/(52*10)*10)) AS average
FROM schools s JOIN
reviews r
ON r.schoolID = s.SID
WHERE s.schoolCountry = 'United Arab Emirates' AND r.isactive = 1
GROUP BY schools.SID
ORDER BY average DESC
) sr CROSS JOIN
(SELECT #rank := 0) AS vars
ORDER BY average DESC
Rank should depend on the ordering of average..but there is no order by in the inner query. First compute the average and calculate rank thereafter. Also, i moved the where condition to join because the way you have it is equivalent to an inner join.
SELECT SID, schoolName, #rank := #rank + 1 AS rank, average
FROM (
SELECT schools.SID, schools.schoolName,
ROUND(AVG(IF(reviews.active = 1, ((Q1+Q2+Q3+Q4+Q5+Q6+Q7+Q8+Q9+Q10+Q11+Q12+Q13+Q14+Q15+Q16+Q17+Q18+Q19+Q20+Q21+Q22+Q23+Q24+Q25+Q26+Q27+Q28+Q29+Q30+Q31+Q32+Q33+Q34+Q35+Q36+Q37+Q38+Q39+Q40+Q41+Q42+Q43+Q44+Q45+Q46+Q47+Q48+Q49+Q50+Q51+Q52)/(52*10)*10), NULL)) ,1) AS average
FROM schools
RIGHT JOIN reviews ON reviews.schoolID = schools.SID AND schools.schoolCountry = 'United Arab Emirates'
GROUP BY schools.SID,schools.schoolName
) as order_ranked
CROSS JOIN (SELECT #rank := 0) AS vars
ORDER BY `average` DESC