Creating a SQL view with personal best records - mysql

I have the following SQL Database structure:
Users are the registered users. Maps are like circuits or race tracks. When a user is driving a time a new time record will be created including the userId, mapId and the time needed to finish the racetrack.
I wish to create a view where all the users personal bests on all maps are listed.
I tried creating the view like this:
CREATE VIEW map_pb AS
SELECT MID, UID, TID
FROM times
WHERE score IN (SELECT MIN(score) FROM times)
ORDER BY registered
This does not lead to the wished result.
Thank you for your help!

I hope that you have 'times' table created as the above diagram and 'score' column in the table that you use to measure the best record.
(MIN(score) is the best record).
You can simply create a view to have the personal best records using sub-queries like this.
CREATE VIEW map_pb AS
SELECT a.MID, a.UID, a.TID
FROM times a
INNER JOIN (
SELECT TID, UID, MIN(score) score
FROM times
GROUP BY UID
) b ON a.UID = b.UID AND a.score= b.score
-- if you have 'registered' column in the 'times' table to order the result
ORDER BY registered
I hope this may work.

You probably need to use a query that will first return the minimum score for each user on each map. Something like this:
SELECT UID,
MID,
MIN(score) AS best_time
FROM times
GROUP BY UID, MID
Note: I used MIN(score) as this is what is shown in your example query, but perhaps it should be MIN(time) instead?
Then just use the subquery JOINed to your other tables to get the output:
SELECT *
FROM (
SELECT UID,
MID,
MIN(score) AS best_time
FROM times
GROUP BY UID, MID
) a
INNER JOIN users u ON u.UID = a.UID
INNER JOIN maps m ON m.MID = a.MID
Of course, replace SELECT * with the columns you actually want.
Note: code untested but does give an idea as to a solution.

Start with a subquery to determine each user's minimum score on each map
SELECT UID, TID, MIN(time) time
FROM times
GROUP BY UID, TID
Then join that subquery into a main query.
SELECT times.UID, times.TID,
mintimes.time
FROM times
JOIN (
) mintimes ON times.TID = mintimes.TID
AND times.UID = mintimes.UID
AND times.time = mintimes.time
JOIN maps ON times.MID = maps.MID
JOIN users ON times.UID = users.UID
This query pattern uses a GROUP BY function to find the outlying (MIN in this case) value for each combination. It then uses that subquery to find the detail record for each outlying value.

Related

MySQL: get average of value from query

I have 3 table.
Users --> UID, Name, Lat, Longg, Pic
Profile --> pid, uid,City, State, About
Feedback--> fid, uid, ratingby, txnid, rating, feedback_type
Feedback_type can be 1-4 from same txnid. So, if user is giving feedback for all the questions then there will be 4 records for same.
Now i need to show the user details along with the average feedback.
Below is the query i have written so far.
SELECT
a.name,
a.uid,
b.city,
a.pic,
b.state,
b.about
FROM
users AS a
INNER JOIN profile AS b
ON
a.uid = b.uid
I am not sure how can i get the average value from feedback table.
I need show user average feedback and to be more specific. Average of all 4 feedback separately.
Also advise if my approach is good or is there any other best practice that i need to follow.
Edit
I can fetch the single record from feedback.
SELECT uid, avg(rating) FROM `feedback` WHERE uid= 8
But not sure how can i get the average for different feedback_type.
You probably need to create a sub-table to find the average ratings of each type from the Feedbackback table (you can categorize feedback_type for calculating average by using group by f.uid, f.feedback_type). After that, you just need to join the resulting query table with the Users and Profile table to get additional data such as Name, City, etc.
SELECT u1.Name, ar.uid, ar.average_rating, ar.feedback_type, p.City, u1.Pic, p.State, p.About
FROM (
SELECT f.uid, f.feedback_type, AVG(f.rating) AS average_rating
FROM Feedback AS f
WHERE f.uid=8
GROUP BY f.uid, f.feedback_type
) AS ar
INNER JOIN Users AS u1 ON ar.uid=u1.UID
INNER JOIN Profile AS p ON ar.uid=p.uid;
Update: If alias is not working, an alternative approach would be to create a temporary table to calculate user's average rating and use the table to join with Users and Profile tables like above
CREATE TEMPORARY TABLE ar
SELECT uid, feedback_type, AVG(rating) AS average_rating
FROM Feedback
WHERE uid=8
GROUP BY uid, feedback_type;
SELECT Users.Name, ar.uid, ar.average_rating, ar.feedback_type, Profile.City, Users.Pic, Profile.State, Profile.About
FROM ar
INNER JOIN Users ON ar.uid=Users.UID
INNER JOIN Profile ON ar.uid=Profile.uid;
Update: If you need to put the records of 4 feedback types in different columns, you only need to group by uid in ar table and use CASE in AVG to filter out the feedback_type to calculate the average in each column
CREATE TEMPORARY TABLE ar
SELECT
uid,
AVG(CASE WHEN feedback_type = 1 THEN rating END) AS average_rating_1,
AVG(CASE WHEN feedback_type = 2 THEN rating END) AS average_rating_2,
AVG(CASE WHEN feedback_type = 3 THEN rating END) AS average_rating_3,
AVG(CASE WHEN feedback_type = 4 THEN rating END) AS average_rating_4
FROM Feedback
WHERE uid=8
GROUP BY uid;
SELECT
Users.Name,
ar.uid,
ar.average_rating_1,
ar.average_rating_2,
ar.average_rating_3,
ar.average_rating_4,
ar.feedback_type,
Profile.City,
Users.Pic,
Profile.State,
Profile.About
FROM ar
INNER JOIN Users ON ar.uid=Users.UID
INNER JOIN Profile ON ar.uid=Profile.uid;
You can get the avg value using this query SELECT AVG(column_name) FROM table_name WHERE condition;.
To get more columns you should try
SELECT AVG(column_name_1) AS a, AVG(column_name_2) AS b, AVG() AS c, AVG() AS d FROM table_name WHERE condition
I hope i was helpful.

How to use MAX and COUNT function in MYSQL with 2 tables

I am a newbie in MYSQL and had a question regarding the use of MAX and COUNT functions together in MYSQL. I have 2 tables worker and assignment and the primary key of worker is a foreign key in assignment table.
I need to show the employees name and id and the total assignment assigned to him, and only show the person with the most assignment that is the employee with the most assignment.
my code is
SELECT worker.Wrk_ID, worker.Wrk_LastName, MAX(a.count_id)
FROM worker,
(SELECT COUNT(assignment.Wrk_ID) as count_ID
FROM worker, assignment
WHERE worker.Wrk_ID = assignment.Wrk_ID
GROUP BY worker.Wrk_ID)as a
GROUP BY worker.Wrk_ID;
The code is giving an error no. #1054.
Please can anyone help me.
Thanking you in anticipation.
Try something like this:
SELECT worker.Wrk_ID, worker.Wrk_LastName, S.Count
FROM worker
JOIN
(SELECT Wrk_ID, COUNT(*) AS Count FROM Assignments
GROUP BY Wrk_Id ORDER BY COUNT(*) DESC LIMIT 1) S
ON worker.Wrk_ID = S.Wrk_ID
If you want a list of employees sorted by their total assignments:
SELECT w.WrkID, w.Wrk_LastName, COUNT(*) AS Assignments
FROM work w left join Assignments a
ON w.WrkID=a.WrkID
GROUP BY w.WrkID
ORDER BY COUNT(*) DESC;
To allow multiple winners:
SELECT s.*, w.Wrk_Lastname FROM
(
SELECT wrk_id , COUNT(*) AS tot_assignments
FROM Assignments
GROUP BY wrk_id
HAVING COUNT(*) =
(
SELECT MAX(tot) FROM
(
SELECT COUNT(*) AS TOT FROM Assignments GROUP BY wrk_id
) counts
)
) winners
INNER JOIN worker w ON s.wrk_id = w.wrk_id;
It can be slow since it does multiple GROUP BY. Doing it in separated steps in a procedure can be better.

MySQL - 3 tables, is this complex join even possible?

I have three tables: users, groups and relation.
Table users with fields: usrID, usrName, usrPass, usrPts
Table groups with fields: grpID, grpName, grpMinPts
Table relation with fields: uID, gID
User can be placed in group in two ways:
if collect group minimal number of points (users.usrPts > group.grpMinPts ORDER BY group.grpMinPts DSC LIMIT 1)
if his relation to the group is manually added in relation tables (user ID provided as uID, as well as group ID provided as gID in table named relation)
Can I create one single query, to determine for every user (or one specific), which group he belongs, but, manual relation (using relation table) should have higher priority than usrPts compared to grpMinPts? Also, I do not want to have one user shown twice (to show his real group by points, but related group also)...
Thanks in advance! :) I tried:
SELECT * FROM users LEFT JOIN (relation LEFT JOIN groups ON (relation.gID = groups.grpID) ON users.usrID = relation.uID
Using this I managed to extract specified relations (from relation table), but, I have no idea how to include user points, respecting above mentioned priority (specified first). I know how to do this in a few separated queries in php, that is simple, but I am curious, can it be done using one single query?
EDIT TO ADD:
Thanks to really educational technique using coalesce #GordonLinoff provided, I managed to make this query to work as I expected. So, here it goes:
SELECT o.usrID, o.usrName, o.usrPass, o.usrPts, t.grpID, t.grpName
FROM (
SELECT u.*, COALESCE(relationgroupid,groupid) AS thegroupid
FROM (
SELECT u.*, (
SELECT grpID
FROM groups g
WHERE u.usrPts > g.grpMinPts
ORDER BY g.grpMinPts DESC
LIMIT 1
) AS groupid, (
SELECT grpUID
FROM relation r
WHERE r.userUID = u.usrID
) AS relationgroupid
FROM users u
)u
)o
JOIN groups t ON t.grpID = o.thegroupid
Also, if you are wondering, like I did, is this approach faster or slower than doing three queries and processing in php, the answer is that this is slightly faster way. Average time of this query execution and showing results on a webpage is 14 ms. Three simple queries, processing in php and showing results on a webpage took 21 ms. Average is based on 10 cases, average execution time was, really, a constant time.
Here is an approach that uses correlated subqueries to get each of the values. It then chooses the appropriate one using the precedence rule that if the relations exist use that one, otherwise use the one from the groups table:
select u.*,
coalesce(relationgroupid, groupid) as thegroupid
from (select u.*,
(select grpid from groups g where u.usrPts > g.grpMinPts order by g.grpMinPts desc limit 1
) as groupid,
(select gid from relations r where r.userId = u.userId
) as relationgroupid
from users u
) u
Try something like this
select user.name, group.name
from group
join relation on relation.gid = group.gid
join user on user.uid = relation.uid
union
select user.name, g1.name
from group g1
join group g2 on g2.minpts > g1.minpts
join user on user.pts between g1.minpts and g2.minpts

MySQL is not using INDEX in subquery

I have these tables and queries as defined in sqlfiddle.
First my problem was to group people showing LEFT JOINed visits rows with the newest year. That I solved using subquery.
Now my problem is that that subquery is not using INDEX defined on visits table. That is causing my query to run nearly indefinitely on tables with approx 15000 rows each.
Here's the query. The goal is to list every person once with his newest (by year) record in visits table.
Unfortunately on large tables it gets real sloooow because it's not using INDEX in subquery.
SELECT *
FROM people
LEFT JOIN (
SELECT *
FROM visits
ORDER BY visits.year DESC
) AS visits
ON people.id = visits.id_people
GROUP BY people.id
Does anyone know how to force MySQL to use INDEX already defined on visits table?
Your query:
SELECT *
FROM people
LEFT JOIN (
SELECT *
FROM visits
ORDER BY visits.year DESC
) AS visits
ON people.id = visits.id_people
GROUP BY people.id;
First, is using non-standard SQL syntax (items appear in the SELECT list that are not part of the GROUP BY clause, are not aggregate functions and do not sepend on the grouping items). This can give indeterminate (semi-random) results.
Second, ( to avoid the indeterminate results) you have added an ORDER BY inside a subquery which (non-standard or not) is not documented anywhere in MySQL documentation that it should work as expected. So, it may be working now but it may not work in the not so distant future, when you upgrade to MySQL version X (where the optimizer will be clever enough to understand that ORDER BY inside a derived table is redundant and can be eliminated).
Try using this query:
SELECT
p.*, v.*
FROM
people AS p
LEFT JOIN
( SELECT
id_people
, MAX(year) AS year
FROM
visits
GROUP BY
id_people
) AS vm
JOIN
visits AS v
ON v.id_people = vm.id_people
AND v.year = vm.year
ON v.id_people = p.id;
The: SQL-fiddle
A compound index on (id_people, year) would help efficiency.
A different approach. It works fine if you limit the persons to a sensible limit (say 30) first and then join to the visits table:
SELECT
p.*, v.*
FROM
( SELECT *
FROM people
ORDER BY name
LIMIT 30
) AS p
LEFT JOIN
visits AS v
ON v.id_people = p.id
AND v.year =
( SELECT
year
FROM
visits
WHERE
id_people = p.id
ORDER BY
year DESC
LIMIT 1
)
ORDER BY name ;
Why do you have a subquery when all you need is a table name for joining?
It is also not obvious to me why your query has a GROUP BY clause in it. GROUP BY is ordinarily used with aggregate functions like MAX or COUNT, but you don't have those.
How about this? It may solve your problem.
SELECT people.id, people.name, MAX(visits.year) year
FROM people
JOIN visits ON people.id = visits.id_people
GROUP BY people.id, people.name
If you need to show the person, the most recent visit, and the note from the most recent visit, you're going to have to explicitly join the visits table again to the summary query (virtual table) like so.
SELECT a.id, a.name, a.year, v.note
FROM (
SELECT people.id, people.name, MAX(visits.year) year
FROM people
JOIN visits ON people.id = visits.id_people
GROUP BY people.id, people.name
)a
JOIN visits v ON (a.id = v.id_people and a.year = v.year)
Go fiddle: http://www.sqlfiddle.com/#!2/d67fc/20/0
If you need to show something for people that have never had a visit, you should try switching the JOIN items in my statement with LEFT JOIN.
As someone else wrote, an ORDER BY clause in a subquery is not standard, and generates unpredictable results. In your case it baffled the optimizer.
Edit: GROUP BY is a big hammer. Don't use it unless you need it. And, don't use it unless you use an aggregate function in the query.
Notice that if you have more than one row in visits for a person and the most recent year, this query will generate multiple rows for that person, one for each visit in that year. If you want just one row per person, and you DON'T need the note for the visit, then the first query will do the trick. If you have more than one visit for a person in a year, and you only need the latest one, you have to identify which row IS the latest one. Usually it will be the one with the highest ID number, but only you know that for sure. I added another person to your fiddle with that situation. http://www.sqlfiddle.com/#!2/4f644/2/0
This is complicated. But: if your visits.id numbers are automatically assigned and they are always in time order, you can simply report the highest visit id, and be guaranteed that you'll have the latest year. This will be a very efficient query.
SELECT p.id, p.name, v.year, v.note
FROM (
SELECT id_people, max(id) id
FROM visits
GROUP BY id_people
)m
JOIN people p ON (p.id = m.id_people)
JOIN visits v ON (m.id = v.id)
http://www.sqlfiddle.com/#!2/4f644/1/0 But this is not the way your example is set up. So you need another way to disambiguate your latest visit, so you just get one row per person. The only trick we have at our disposal is to use the largest id number.
So, we need to get a list of the visit.id numbers that are the latest ones, by this definition, from your tables. This query does that, with a MAX(year)...GROUP BY(id_people) nested inside a MAX(id)...GROUP BY(id_people) query.
SELECT v.id_people,
MAX(v.id) id
FROM (
SELECT id_people,
MAX(year) year
FROM visits
GROUP BY id_people
)p
JOIN visits v ON (p.id_people = v.id_people AND p.year = v.year)
GROUP BY v.id_people
The overall query (http://www.sqlfiddle.com/#!2/c2da2/1/0) is this.
SELECT p.id, p.name, v.year, v.note
FROM (
SELECT v.id_people,
MAX(v.id) id
FROM (
SELECT id_people,
MAX(year) year
FROM visits
GROUP BY id_people
)p
JOIN visits v ON ( p.id_people = v.id_people
AND p.year = v.year)
GROUP BY v.id_people
)m
JOIN people p ON (m.id_people = p.id)
JOIN visits v ON (m.id = v.id)
Disambiguation in SQL is a tricky business to learn, because it takes some time to wrap your head around the idea that there's no inherent order to rows in a DBMS.

mysql count one value display multiple columns

I couldnt find this, i'm sure its simple.
Table (196 rows)
tech, name, area, manager
------------------------------
Joe,mary,pa,sally
Kim,brad,ga,tim
kelly,Joe,pa,sally
Joe,jose,pa,sally
a tech is assigned to multiple name/area/managers. I want to do a report that shows all the rows of the table with a column showing the number of assignments for the tech.
My desired results
tech, name, area, manager, (count of number of tech assignments)
Joe,mary,pa,sally,2
Kim,brad,ga,tim,1
kelly,Joe,pa,sally,1
Joe,jose,pa,sally,2
I'm guessing you want a subquery on the SELECT clause:
SELECT
name,
area,
manager,
(SELECT COUNT(*) FROM tablename WHERE tech = x.tech) AS assignments
FROM tablename x
And here is a possibly more efficient way to do the same thing:
SELECT
t.name,
t.area,
t.manager,
sub.assignments
FROM tablename t
INNER JOIN (
SELECT tech, COUNT(*) AS assignments
FROM tablename
GROUP BY tech
) sub
ON sub.tech = t.tech
select
a.tech, a.name, a.area, a.manager,
b.cnt
from table a, (
select count(*) cnt, tech
from table
group by tech) b
where a.tech=b.tech;
Is this what you want?
SELECT tech, count(name) AS total_records FROM db_table GROUP BY tech
Not sure this is what you're looking for though.
Oh yeah, looks like you really need to use subquery.
Thanks for the examples, that helped.
SELECT tech, name, area, manager, assignments
FROM table
INNER JOIN (
SELECT tech, COUNT(*) AS assignments
FROM table
GROUP BY tech
) t
USING (tech)
Explanation:
Select all columns from table and join them with table containing tech column and count of rows from table having particular tech. Keyword USING says how those two table merge.