Advanced MySQL Query (ranking results based on 2 other tables) - mysql

I've been trying to make this work properly for a while now but just can't get my head around how to go about it, hopefully someone here can help me!
I have three tables:
Table A
Table B
Table C
I want to get the top 10 results from Table A based on a ranking that will depend on information from table B and table C. The ranking will be using the following formula:
Ranking = (COUNT(id)
from table C
WHERE c.a_id = a.id) as count_weight +
(COUNT(id)
FROM table B
WHERE b.a_id = a.id)*(count_weight*0.25) + a.views
In words I want the ranking to be equal to a point value determined by:
The count of records in Table C that correspond to the record in Table A that I'm interested in
If a record exists in Table B that corresponds to the record in Table A I'm interested in, I want an additional increase in the points by 25% (taking the points gained from #1 and multiply by 0.25) - In this case a record will exist or wont exist so it will always be 0 or 1
Points for each "view" for the record in Table A (a field of table A)
Hopefully I worded that in a understandable manner!
Thanks!

I think this is what you're after:
SELECT a.*, COUNT(c.id) * IF(COUNT(b.id),1.25,1) + a.views AS Ranking
FROM a LEFT JOIN c ON a.id = c.a_id LEFT JOIN b ON a.id = b.a_id
GROUP BY a.id
ORDER BY Ranking DESC
LIMIT 10
If you don't want to select the Ranking, you can put that column's formula directly into the ORDER BY clause.

Related

mysql understanding group by with a joined table

So I have the "main" table (A) with fields: id, order_number, order_name and table (B) with fields: id, fk.order_number, tracking_number
Table (A) is responsible for keeping track of each order, while table (B) stores all associated tracking information per each order.
What I am trying to accomplish is to query each order from table A and join table B to show the first tracking number that has been stored for each order, almost like a limit 1 (return only the first stored tracking number for each order).
How I am doing this currently is a join between table A and table B on the order_number field, but I am using the GROUP BY tableA.order_number at the end of the statement.
select tablea.order_number, tablea.order_name, tableb.tracking_number
from tablea
join table b
on tablea.order_number = tableb.order_number
group by tablea.order_number
I guess the question revolves around, what is the default group by ordering when you return multiple rows back from the joined table?
For example, in table A, there is only 1 row, while in tale B there are 2 rows (2 tracking numbers for the order). So, when I group by in this case, does that always take the first match from the joined table where the condition matches the group by? If I removed group by, 2 rows would be returned.
I realize what is happening because I have the group by condition on tableA, and so it only shows the first row because both rows returned from the join have the same order number, which appears to be what I want (limit 1 tracking number per order), but I'm not sure if programmatically I actually did this correctly or if it happens this way because that is how the group by clause works and how I used it here. I just want to limit 1 tracking number from the tableB based on the order_number of table A.
Updated (with example query)
SELECT m.message_id, m.message_date, m.message_order_number, m.message_purchase_order, m.message_vendor_invoice, ve.vendor_email_display, concat(c.customer_first_name, ' ', c.customer_last_name) as customer_name,
min(ti.tracking_information_id) as tracking_information_id, ti.tracking_information_tracking_number, ti.tracking_information_tracking_number_status
FROM email.message m
JOIN email.customer c
ON m.message_tagged_customer_first = c.customer_id and m.message_tagged_customer_last = c.customer_id
JOIN vendor_email ve
ON m.message_sender = ve.vendor_email_id
LEFT JOIN tracking_information ti
ON m.message_order_number = ti.tracking_information_order_number
group by m.message_order_number
In this case, I want to return all information in message table, and the first matching row in table tracking
Group byis for aggregation function as MIN() ,, MAX() , COUNT() . .. and is for define respect which column the aggreagtion function must be performed..
If you are looking for a specific ordered result you should use ORDER BY that work for the columns value as is
select tablea.order_number, tablea.order_name, tableb.tracking_number
from tablea
join table b on tablea.order_number = tableb.order_number
order by tablea.order_number

returning all rows in table 1, when join most recent record in table 2

I've the following code, which pulls out the most recent row from a table called wwlassessments.
It works, but what I'm trying to do is show all the rows matching the WHERE criteria in table, regardless of whether there's an entry in the wwlassessments table.
I've tried changing the 2nd JOIN to a LEFT JOIN, but this just provides thousands of inaccurate results.
I'm sure it's very simple, but I can't for the life of me work out what I need to change! Thanks in advance.
SELECT s.*,
a.*
FROM wwlstatements s
LEFT JOIN wwlassessments a ON a.id = s.id
JOIN (SELECT n.id,n.pupilID,
MAX(n.dateAchieved) AS max_achieved_date
FROM wwlassessments n
where n.pupilID='114631705547'
GROUP BY n.id) y ON y.id = a.id
AND y.max_achieved_date = a.dateAchieved
WHERE s.`category`='Reading'
ORDER BY s.`statementID` ASC

Create view from two tables with subtract operation

I'm almost new with SQL syntax and I need help to create a view on MySQL.
I have a table with a PK column called ID, a column called total_seats and another one is title.
In the second table, I have multiple rows, with a firstname column and a FK that corresponds to the PK (total_seats) present in the first table.
I need to create a view where I can calculate the available_seats (total_seats minus occurrence in the second table) for each element present in the first table.
Actually I'm calculating the "occupied" seats but the join give me the result only for already taken event, so the result is that I don't see the available_seats for the empty event.
SELECT b.ID_event, a.*,
COUNT(*) AS occupied FROM second_table b
LEFT JOIN first_table a ON b.ID_event = a.ID
GROUP BY ID_event
You could subtract the count
select a.ID_event, a.total_seats, count(*) as occupied, a.total_seats - count(*) difference
from first_table a
left join second_table ba ON b.ID_event = a.ID
group by a.ID_event, a.total_seats

Merging 3 Tables, Limiting 1 Table With Multiple Fields Needed

Been looking into this for awhile. Hoping someone might be able to provide some insight. I have 3 tables. All of which I'm grabbing multiple columns, but the 3rd I need to limit the output to just the most recent timestamp entry, BUT still display multiple columns.
If I have the following data [ Please see SQL Fiddle ]:
http://sqlfiddle.com/#!2/84b91/6
The fiddle is a list of (names) in Table1(users), (job_name,years) in Table2(job), and then (score, timestamp) in Table3(job_details). All linked together by the users id.
I am definitely not great at MYSQL. I know I'm missing something.. possibly a series of JOINs. I have been able to get Table 1, Table 2 and one column of Table 3 by doing this:
select a.id, a.name, b.job_name, b.years,
(select c.timestamp
from job_details as c
where c.user_id = a.id
order by c.timestamp desc limit 1) score
from users a, job as b where a.id = b.user_id;
At this point, I can get multiple column data on the first two columns, limit the 3rd to one value and sort that value on the last timestamp...
My question is: How does one go about adding a second column to the limit? In the example in the fiddle, I'd like to add the score as well as the timestamp to the output.
I'd like the output to be:
NAME, JOB, YEARS, SCORE, TIMESTAMP. The last two columns would only be the last entry in job_details sorted by the most recent TIMESTAMP.
Please let me know if more information is required! Thank you for your time!
T
Try this:
select a.id, a.name, b.job_name, b.years, c.timestamp, c.score
from users a
INNER JOIN job as b ON a.id = b.user_id
INNER JOIN (SELECT jd.user_id, jd.timestamp, jd.score
FROM job_details as jd
INNER JOIN (select user_id, MAX(timestamp) as tstamp
from job_details
GROUP BY user_id) as max_ts ON jd.user_id = max_ts.user_id
AND jd.timestamp = max_ts.tstamp
) as c ON a.id = c.user_id
;

ordering a table in Mysql, according to another, but without seeing repetitive rows of the first table

In MySql, I have two tables, A and B.
A has as columns A.id, B has as columns B.id and B.aid.
or each row of A I have many rows of B. And the value of B.aid=A.id
of course.
Now I need to get a list of the values in A, but I need to order them, according to B.
In particular if I have two rows in A: a1 and a2. Each will have a series of rows in B:
b11, b12, b13, ...
and
b21, b22, b23, ...
Now I need to order the A from the one connected with the highest b.id to the one with the second highest, and so on. (of course having one row appearing only once).
I tried this:
SELECT a.id FROM a, b WHERE a.id=b.aib ORDER BY b.id DESC
I did indeed got all the values in the right order, but each value of A would appear n times, if n was the number of rows in B related to that row in A.
How do I avoid that, so that I get only one value.
I am considering taking the wholelist, and then eliminating all the non unique values, but I fear that once the website becomes big it might not be doable anymore.
(In case you wonder, this is to program a fac-simile of a discussion board, the table A is the thread, and the table B is the entry, and I want to have a page where all the threads are presented, but in order of the thread that had the last action later)
Many thanks,
Pietro
P.S. MySql is not my thing, so please do spell out the solution :)
UPDATE:
The actual code is more complex, as it also involves users, and similar. So I am looking at something like:
SELECT DISTINCT a.id, a.question, a.roundid, a.phase, users.username, users.id
FROM a, users, b
WHERE a.phase = 0 AND users.id = a.usercreatorid AND b.experimentid = a.id
ORDER BY b.id DESC
I tried the DISTINCT, as suggested below, but it does not work. I do get all the thread (i.e. questions) uniquely, but thhey are not perfectly ordered. I do not know why, but it seem he is not chosing a random row from b, and this goes generally in the right direction, but it is not the row with the max(b.id). SO the distinct does not sort between rows in the correct way. I will now look at the other solutions proposed.
select * from parent a
order by ( select max(id) from child b where b.parent_id = a.id);
NOTE WELL: this is not a join, so you'll get all rows in a, not just those that have a child in b.
You can see why if you do this:
select *, ( select max(id) from child b where b.parent_id = a.id)
from parent a
order by ( select max(id) from child b where b.parent_id = a.id);
(null sorts before anything else in an ascending sort.)
This avoids grouping or distincting, and has the advantage that the SQL pretty clearly states your intent, not a workaround to get at your intent, which makes it more self-commenting than some alternatives.
As I understand it, you want the a.id values, ordered by the most recent corresponding b.id value.
Where you have a 1->many relation and need that sort of info, you're typically looking at a GROUP BY to aggregrate the data, or a subquery for more complex criteria.
So, something like this should do it, using a group by:
SELECT
a.id, a.question, a.roundid, a.phase,
users.username, users.id,
MAX(b.id) AS latest
FROM
a, users, b
WHERE
a.phase = 0 AND users.id = a.usercreatorid AND b.experimentid = a.id
GROUP BY a.id
ORDER BY latest DESC
You want to use the DISTINCT keyword.
SELECT DISTINCT a.id FROM a, b WHERE a.id=b.aib ORDER BY b.id DESC