SQL query problem - php

I've got reporting of a user's score everytime it happens. Now I want to show the best score a user has had. The table set up is like this:
Player(id, name)
PlayerHasAchievement(id, playerId,
achievementId)
Achievement(id, type, amount, time)
This is what I have right now:
$query = "SELECT MAX(ach.amount) as amount, p.username, ach.time
FROM achievement as ach
INNER JOIN playerHasAchievement as playAch ON ach.id = playAch.id
INNER JOIN player as p ON p.userId = playAch.userid
WHERE ach.type = 2
GROUP BY amount
ORDER by `amount` DESC
LIMIT $amount";
I tried to select it distinctly but it didn't work. I'm stumped, it's supposed to be so easy! Thanks for reading, I'll be grateful for any help!

The problem is the the ach.time you are getting is not the same row as the MAX(amount). Join another subquery to get the MAX(amount) first.
Note: In the table definitions you posted, playerHasAchievement has a field playerId not userId
SELECT MAX(ach.amount) as amount, p.username, MAX(ach.time) MaxTime
FROM achievement as ach
INNER JOIN playerHasAchievement as playAch ON ach.id = playAch.id
INNER JOIN player as p ON p.userId = playAch.playerId
INNER JOIN (
SELECT playAch.playerId, MAX(ach.amount) as MaxAmount
FROM achievement as ach
INNER JOIN playerHasAchievement as playAch ON ach.id = playAch.id
WHERE ach.type = 2
GROUP BY playAch.playerId
) g ON p.playerId = g.playerId AND ach.amount = g.MaxAmount
WHERE ach.type = 2
GROUP BY p.playerId
ORDER by `amount` DESC
LIMIT $amount";
The reason why we group the outer query, is to avoid ties - say a player had the same score twice.

In your join on line 3 don't you really want
INNER JOIN playerHasAchievement as playAch ON ach.id = playAch.achievementId
and others are correct, you need to group by your non aggregate columns, not the aggregate one.
Assuming your db layout is as specified in the question here is the query I would use.
SELECT ach.amount, p.Name, ach.time
FROM achievement as ach
JOIN playerHasAchievement as playAch ON ach.id=playAch.achievementId
JOIN player AS p ON p.id = playAch.playerId
WHERE ach.type = 2
AND ach.amount = (SELECT MAX(ach.amount)
FROM achievement as ach
JOIN playerHasAchievement as playAch ON ach.id=playAch.achievementId
JOIN player AS p ON p.id = playAch.playerId
WHERE ach.type = 2)
GROUP BY ach.amount
ORDER by ach.time
taking the first result (in case there are multiples of the same score) will give you the high score and the lowest time.
Hope that helps!

You are not using group by appropriately, as you are only grouping by amount.
What about the user name and the time?

Related

EXISTS query optimization on mysql query

I have a big data problem with MySQL.
I have:
a users table with 59033 rows, and
a user_notes table with 8753 rows.
But when I search which users have user note in some dates.
My query like this :
SELECT u.*, rep.name as rep_name FROM users as u
LEFT JOIN users as rep on rep.id = u.add_user
LEFT JOIN authorization on authorization.id = u.authorization
LEFT JOIN user_situation_list on user_situation_list.user_situation_id = u.user_situation
WHERE
EXISTS(
select * from user_notes
where user_notes.note_user_id = u.id AND user_notes.create_date
BETWEEN "2017-10-20" AND "2017-10-22"
)
ORDER BY u.lp_modify_date DESC, u.id DESC
Turn it around -- find the ids first; deal with the joins later.
SELECT u.*,
( SELECT rep.name
FROM users AS rep
WHERE rep.id = u.add_user ) AS rep_name
FROM (
SELECT DISTINCT note_user_id
FROM user_notes
WHERE create_date >= "2017-10-20"
AND create_date < "2017-10-20" + INTERVAL 3 DAY
) AS un
JOIN users AS u ON u.id = un.note_user_id
ORDER BY lp_modify_date DESC, id DESC
Notes
No GROUP BY needed;
2 tables seem to be unused; I removed them;
I changed the date range;
User notes needs INDEX(create_date, note_user_id);
Notice how I turned a LEFT JOIN into a subquery in the SELECT list.
If there can be multiple rep_names, then the original query is "wrong" in that the GROUP BY will pick a random name. My Answer can be 'fixed' by changing rep.name to one of these:
MAX(rep.name) -- deliver only one; arbitrarily the max
GROUP_CONCAT(rep.name) -- deliver a commalist of names
Rewriting your query to use a JOIN rather than an EXISTS check in the where should speed it up. If you then group the results by the user.id it should give you the same result:
SELECT u.*, rep.name as rep_name FROM users as u
LEFT JOIN users as rep on rep.id = u.add_user
LEFT JOIN authorization on authorization.id = u.authorization
LEFT JOIN user_situation_list on user_situation_list.user_situation_id = u.user_situation
JOIN user_notes AS un
ON un.note_user_id
AND un.create_date BETWEEN "2017-10-20" AND "2017-10-22"
GROUP BY u.id
ORDER BY u.lp_modify_date DESC, u.id DESC

Multiple Counts in MYSQL PHP Query

I'm trying to create a leaderboard but i'm not sure how to do the mysql query.
I would like to count all the levels from a player in the skills table and get the total Level and count all the experience from a player in the experience table and get the Total Exp along with displaying the persons name from the users column.
There is 3 tables factions_mcmmo_users, factions_mcmmo_experience, factions_mcmmo_skills.
This is what i have so far but it doesn't work:
$sql = ("SELECT a.id,
(SELECT COUNT(*) FROM factions_mcmmo_experience WHERE user_id = a.id) as TotalXP,
(SELECT COUNT(*) FROM factions_mcmmo_skills WHERE user_id = a.id) as TotalLevel
FROM (SELECT DISTINCT id FROM factions_mcmmo_users) a LIMIT 10;");
Any help would be very appreciated
EDIT: I have it working now but i'm unsure if its the most efficient way to do things so if anyone could help me out if theres a better way, it would mean a lot.
I would also like to know if it's possible to display the total exp and level with commas if the number is in the thousands for example: total level 5,882 and total xp 582,882
EDIT 2:
I have figured out how to format the numbers but still don't know if my code is efficient
$sql = ("SELECT id, user,
(SELECT FORMAT(Sum(taming)+Sum(mining)+Sum(woodcutting)+Sum(repair)+Sum(unarmed)+Sum(herbalism)+Sum(excavation)+Sum(archery)+Sum(swords)+Sum(axes)+Sum(acrobatics)+Sum(fishing)+Sum(alchemy),0) FROM factions_mcmmo_skills b WHERE b.user_id = a.id) as TotalLevel,
(SELECT FORMAT(Sum(taming)+Sum(mining)+Sum(woodcutting)+Sum(repair)+Sum(unarmed)+Sum(herbalism)+Sum(excavation)+Sum(archery)+Sum(swords)+Sum(axes)+Sum(acrobatics)+Sum(fishing)+Sum(alchemy),0) FROM factions_mcmmo_experience c WHERE c.user_id = a.id) as TotalXP
FROM (SELECT id, user FROM factions_mcmmo_users) a group by id ORDER BY TotalLevel DESC, TotalXP DESC LIMIT 10;");
EDIT 3
Updated code from scaisEdge but was displaying everyones level as 1 and XP as 1, so i changed count(*) changed to sum, added an order By TotalLevel in Descending order and that seems to have worked but i can't get it to display the persons name (user column) in the user table? not sure if i was supposed to change to sum because it didn't work the other way.
$sql = ("SELECT a.id, b.TotalXP, c.TotalLevel
FROM (SELECT DISTINCT id FROM factions_mcmmo_users) a
INNER JOIN (
SELECT user_id, Sum(taming)+Sum(mining)+Sum(woodcutting)+Sum(repair)+Sum(unarmed)+Sum(herbalism)+Sum(excavation)+Sum(archery)+Sum(swords)+Sum(axes)+Sum(acrobatics)+Sum(fishing)+Sum(alchemy) as TotalXP
FROM factions_mcmmo_experience
GROUP By user_id
) b on b.user_id = a.id
INNER JOIN (
SELECT user_id, Sum(taming)+Sum(mining)+Sum(woodcutting)+Sum(repair)+Sum(unarmed)+Sum(herbalism)+Sum(excavation)+Sum(archery)+Sum(swords)+Sum(axes)+Sum(acrobatics)+Sum(fishing)+Sum(alchemy) as TotalLevel
FROM factions_mcmmo_skills
GROUP by user_id
) c on c.user_id = a.id
ORDER BY TotalLevel DESC
LIMIT 10;");
EDIT 4
Everything working but when i try to format the totals using "FORMAT(Sum(Columns), 0) on the inner joins, the EXP Total appears to work but the main Total Level is not displaying results that are over 1,000 and it breaks the leaderboard positioning, it should be sorting them on total level but it appears to be random, when u remove the format,0 it goes back to working
I would like it to display commas if the number number is the thousands for example: Total Level: 5,532 and Total EXP 5882,882
See live demo: http://mcbuffalo.com/playground/leaderboards/server/factions-mcmmo.php
Updated Code trying to use Format:
$sql = ("SELECT a.id, a.user, b.TotalXP, c.TotalLevel
FROM (SELECT id, user FROM factions_mcmmo_users) a
INNER JOIN (
SELECT user_id, FORMAT(Sum(taming)+Sum(mining)+Sum(woodcutting)+Sum(repair)+Sum(unarmed)+Sum(herbalism)+Sum(excavation)+Sum(archery)+Sum(swords)+Sum(axes)+Sum(acrobatics)+Sum(fishing)+Sum(alchemy), 0) as TotalXP
FROM factions_mcmmo_experience
GROUP By user_id
) b on b.user_id = a.id
INNER JOIN (
SELECT user_id, FORMAT(Sum(taming)+Sum(mining)+Sum(woodcutting)+Sum(repair)+Sum(unarmed)+Sum(herbalism)+Sum(excavation)+Sum(archery)+Sum(swords)+Sum(axes)+Sum(acrobatics)+Sum(fishing)+Sum(alchemy), 0) as TotalLevel
FROM factions_mcmmo_skills
GROUP by user_id
) c on c.user_id = a.id
ORDER BY TotalLevel DESC;");
EDIT 5
Changed number with PHP, everything works
Original Images
you could use an couple of inner join
$sql = ("SELECT a.id, a.name, b.TotalXP, c.TotalLevel
FROM (SELECT DISTINCT id, name FROM factions_mcmmo_users) a
INNER JOIN (
SELECT user_id, COUNT(*) as TotalXP
FROM factions_mcmmo_experience
GROUP By user_id
) b on b.user_id = a.id
INNER JOIN (
SELECT user_id, COUNT(*) as TotalLevel
FROM factions_mcmmo_skills
GROUP by user_id
) c on c.user_id = a.id
LIMIT 10

MySQL - Having problems with COUNT() in query with GROUP BY

I have a pretty big query which is used by an ajax call to return and also sort active items. From my understanding sub queries should be avoided where possible and since this query will be called very often, I would like to do just that.
At the moment everything is fine except for the COUNT(b.bic) AS bids. If there are two(2) bids the query returns four(4), if there are 4, it returns 8, and so on. I've tried grouping by other columns ... but no luck.
Some of the tables. I hope each of the column names are pretty self explanatory:
countries_ship - each item can be shipped to multiple countries so item_id can be duplicate.
id item_id country_id ship_cost
countries
id country_code country_name
item_expire - not sure if item_expire should have it's own table.
id item_id exp_date
bids - Just as countries_ship, item_id can be duplicate. This is where the bids are stored.
id item_id user_id bid previous_bid bid_date
The query:
$q = $this->db->mysqli->prepare("
SELECT c.ship_cost,
c.item_id,
co.country_name,
co.id AS co_id,
i.id,
i.user_id,
i.item_start,
i.item_title,
i.item_number,
i.item_year,
i.item_publisher,
i.item_condition,
i.item_description,
i.item_location,
e.exp_date AS exp_date,
i.active,
CAST(u.fb_id AS CHAR(50)) AS fb_id,
u.user_pic,
MAX(b.bid) AS maxbid,
COUNT(b.bid) AS bids,
p.publisher_name,
t.tag_name
FROM countries_ship c
JOIN items i
ON c.item_id = i.id
JOIN item_expire e
ON c.item_id = e.item_id
JOIN users u
ON i.user_id = u.id
LEFT JOIN bids b
ON i.id = b.item_id
LEFT JOIN publishers p
ON i.item_publisher = p.id
LEFT JOIN tags_rel tr
ON c.item_id = tr.item_id
JOIN tags t
ON t.id = tr.tag_id
LEFT JOIN countries co
ON i.item_location = co.id
WHERE ".$where."
GROUP BY c.item_id ORDER BY ".$order." ".$limit."");
You may try
COUNT(distinct b.bic) AS bids
This will ignore duplicates due to joins

MySQL Using SUM with multiple joins

I have a projects table and a tasks table I want to do a query that gets all projects and the sum of the time_spent columns grouped by project id. So essentially list all projects and get the total of all the time_spent columns in the tasks table belonging to that project.
With the query posted below I get the latest added time_spent column and not the sum of all the columns.. :S
Below is the query I have at the moment:
SELECT `projects`.`id`, `projects`.`description`, `projects`.`created`,
`users`.`title`, `users`.`firstname`, `users`.`lastname`, `users2`.`title`
as assignee_title, `users2`.`firstname` as assignee_firstname,
`users2`.`lastname` as assignee_lastname,
(select sum(tasks2.time_spent)
from tasks tasks2
where tasks2.id = tasks.id)
as project_duration
FROM (`projects`)
LEFT JOIN `users`
ON `users`.`id` = `projects`.`user_id`
LEFT JOIN `users` as users2
ON `users2`.`id` = `projects`.`assignee_id`
LEFT JOIN `tasks` ON `tasks`.`project_id` = `projects`.`id`
GROUP BY `projects`.`id`
ORDER BY `projects`.`created` DESC
Below is my projects table:
Below is my tasks table:
Thanks in advance!
Usually this query will help you.
SELECT p.*, (SELECT SUM(t.time_spent) FROM tasks as t WHERE t.project_id = p.id) as project_fulltime FROM projects as p
In your question, you don't say about users. Do you need users?
You are on right way, maybe your JOINs can't fetch all data.
This query should do it for you.
Note, whenever you do a group by you must include every column that you select from or order by. Some MySql installations don't prevent you from doing this, but in the end it results in an incorrect result set.
As well you should never do a query as part of your SELECT statement, known as a sub-query, as it will result in an equal amount of additional queries in relation to the number of rows returned. So if you got 1,000 rows back, it would result in 1,001 queries instead of 1 query.
SELECT
p.id,
p.description,
p.created,
u.title,
u.firstname,
u.lastname,
a.title assignee_title,
a.firstname assignee_firstname,
a.lastname assignee_lastname,
SUM(t.time_spent) project_duration
FROM
projects p
LEFT JOIN
users u ON
u.id = p.user_id
LEFT JOIN
users a ON
a.id = u.assignee_id
LEFT JOIN
tasks t ON
t.project_id = p.id
GROUP BY
p.id,
p.description,
p.created,
u.title,
u.firstname,
u.lastname,
a.title,
a.firstname,
a.lastname
ORDER BY
p.created DESC

nested queries and calculations all in the same query - is it possible?

I am running a MySQL query to get all "users" with current orders.
(It is possible for a user to have more than 1 associated orders in the db/query).
However i also want to get the total order value for each user and total order count for each user that is being returned (within the below query).
I could do these calculations in PHP, but feel it is possible and would be neater all done within the same SQL query (if possible).
This is the basic query with no attempt to make the above calculations
SELECT u.UserID, u.UserName,
o.OrdersID, o.OrderProductName, o.OrderProductQT, o.OrderTotalPrice, o.tUsers_UserID, o.tOrderStatus_StatusID, o.OrderDate, o.OrderDateModified, o.OrderVoid, o.tProducts_ProductID,
os.OrderStatusName,
ud.UserDetailsName, ud.UserDetailsPostCode,
p.ProductName, p.ProductImage1
FROM tusers u
INNER JOIN torders o ON o.tUsers_UserID = u.UserID
INNER JOIN torderstatus os ON os.OrderStatus_StatusID = o.tOrderStatus_StatusID
INNER JOIN tuserdetails ud ON ud.tUsers_UserID = u.UserID
LEFT JOIN tproducts p ON p.ProductID = o.tProducts_ProductID
WHERE o.tOrderStatus_StatusID = ?
GROUP BY u.UserID
ORDER BY OrdersID DESC
I have tried various nested select queries, but none of them work (right)
Is what i want to do possible in SQL or should i just do it all in PHP once i have the returned query results?
Any advice is much appreciated
You can embed the slightly modified queries into another query. For instance:
SELECT userid, SUM(orderid) FROM orders GROUP BY userid
and
SELECT userid, SUM(distinct productid)
FROM
orders o
INNER JOIN orderlines ol on ol.orderid = o.orderid
GROUP BY
userid
can be combined to:
SELECT
u.userid
u.fullname,
(SELECT SUM(orderid)
FROM orders o
WHERE o.userid = u.userid) as ORDERCOUNT,
(SELECT SUM(distinct productid)
FROM
orders o
INNER JOIN orderlines ol on ol.orderid = o.orderid
WHERE
o.userid = u.userid) as UNIQUEPRODUCTS
FROM
users u
Note that the latter query will return all users and will return NULL for ORDERCOUNT or UNIQUEPRODUCTS when the subquery doesn't return anything (when a user doesn't have orders). Also, the query will fail when a subquery returns more than 1 row, which should never happen in the example I posted.

Categories