Performing arithmetic in SQL query - php

So I'm working on a script that awards "trophies" to the top 4 performers of a game. The table logs each "grab" attempt, the user that performed it, and whether it was successful. I'd like to create a script that is able to pull the top four off of percentage of successful grabs (attempts / successes)
Is something like this possible within the query itself using mysqli?
I have successfully accomplished the code already by just looping through each table entry, but with thousands of attempts per month it just seems like a clunky way to go about it.
Here is an example of a row in the table, I am attempting to grab the top four based off of monthlyTries/monthlySuccessful
id userId PetId PalId tries successfulGrabs monthlyTries MonthlySuccessful
5 44550 84564 3967 825 268 120 37

Assuming you have a success column that's either 1 or 0 you can sum the success and divide that by count(*) which is the total # of attempts
select user_id, sum(success)/count(*) percentage
from attempts a
group by user_id
order by percentage desc
limit 4
If the success column is not a 1/0 value you can use conditional aggregation
select user_id, sum(case when success = 'yes' then 1 else 0 end)/count(*) as percentage
from attempts a
group by user_id
order by percentage desc
limit 4

In MySQL, you can simplify the logic. If success takes on the values 0 and 1:
select a.user_id, avg(success) as percentage
from attempts a
group by a.user_id
order by percentage desc
limit 4;
Otherwise:
select a.user_id, avg(success = 'yes') as percentage
from attempts a
group by a.user_id
order by percentage desc
limit 4;

Related

Getting the top scores, but remove duplicate users (SQL)

I'm working on a time based game (where a lower time is better), and I have a leaderboard for it, which stores every playthrough into a SQL database.
I'd like to get the top 15 lowest (best) times from the database, but only show one output for each user, so that the best times of the top 15 users are displayed, but I can't seem to do it while returning all of the information.
The Query I'm using is:
SELECT * FROM `scores` WHERE size='$size' ORDER BY `time` ASC LIMIT 15
Thank you.
If you group your data using the user column, you can use MIN() to isolate the lowest/best time for each users. Finally, you sort by BestTime ASC (so that lower numbers are listed first) and truncate the result set with LIMIT.
Query:
SELECT `user`, MIN(`time`) AS BestTime
FROM `scores`
WHERE `size` = '10x10'
GROUP BY `user`
ORDER BY `BestTime`
LIMIT 15;
SELECT * FROM (SELECT user,size,min(time) as time FROM scores
WHERE size = '10x10'
GROUP BY user, size)
ORDER BY time
LIMIT 15
Selects minimum time for each users and returns top 15 users with their min time score.
You would appear to want something like this:
select s.*
from scores s
where s.score = (select max(s2.score) from scores s2 where s2.userid = s.userid)
order by s.score asc
limit 15;
I have no idea what size is for in your sample query.

Mysql SELECT ASC without last 50 rows

Let's say I have 1000 rows in a database and I want to display 950 of it.
The rows I want to display should be the first 950 without the last 50. So how can I protect the last 50 rows?
Something like this query but I would like to start with ASC to select the rows from the beggining not from the end.
$tab= mysqli_query($con, "SELECT id,title,url FROM users ORDER by id DESC limit 50,950");
What I need is 1,2,4,5...950 from 1000 rows ( without the last $limit rows). I will change the $limit,950 depending when I need it.
Thank you.
MySQL does allow LIMIT in the DELETE. But, I don't believe it allows an offset. This is easily handled by reversing the order:
DELETE u
FROM users u
ORDER BY id ASC
LIMIT 950;
You can also write this more directly incorporating your original query:
DELETE u
FROM users u JOIN
(SELECT u2.*
FROM users u2
ORDER BY u2.id DESC
LIMIT 50, 950
) todelete
ON u.id = todelete.id;

How to write a query for reatriving results

I need to find the points to make division (student results). It works fine but it selects only 7 the best scores to be appear on the table of results. I have two problems:
What can I do so that all scores to appear but only seven the best scores to be taken to make division?
This query is for a single student, how can I do to get results of the whole class?
SELECT sum(( case when((test+exam)/2)>=75 then 1 when ((test+exam)/2)>=65 then 2 when ((test+exam)/2)>=45 then 3 when ((test+exam)/2)>=30 then 4 when ((test+exam)/2)<30 then 5 END)) as points
FROM (SELECT test,exam FROM results u
WHERE regno='$regno' and term='$term' and form='$form' and year='$year' ORDER BY ((test+exam)/2) DESC LIMIT 0,7)k;

want to fetch first 9 records and make one more record which will be as “Others” in mysql

I am fetching records from one table with count of one field with other field as name. I want only 10 records. out of which 9 records give me field and its count. but i want to show 10 record as "Others" with all remaining fields with count. This is something like wrapping records.
Something like below will be table contents.
emp_id | designation
1 | software Engg.
2 | software Engg.
3 | Project Manager
not less than 10 designation.
And I want to show first 10 records as
Software Engineers 20
Project Manager 5
....
....
....
Others 50
Is there any way to make SQL Query for mysql db. So that it will be fast and save time in application level where I am adding up record counts for "Others". Or Suggest me how I can make it possible in effective way.
Try this:
SELECT IF(rowNum <= 9, designation, 'Other') designation, SUM(cnt)
FROM (SELECT designation, COUNT(*) cnt, (#row := #row + 1) rowNum
FROM contents, (SELECT #row := 0) dm
GROUP BY designation
ORDER BY designation
) d
GROUP BY IF(rowNum <= 9, designation, 'Other')
Change the GROUP BY and ORDER BY however you decide what 9 to show.
you can use SQL_CALC_FOUND_ROWS option in your query which will tell MySQL to count total number of rows disregarding LIMIT clause. You still need to execute a second query in order to retrieve row count, but it’s a simple query and not as complex as your query which retrieved the data.
Usage is pretty simple. In you main query you need to add SQL_CALC_FOUND_ROWS option just after SELECT and in second query you need to use FOUND_ROWS() function to get total number of rows. Queries would look like this:
SELECT SQL_CALC_FOUND_ROWS name, email FROM users WHERE name LIKE 'a%' LIMIT 10;
SELECT FOUND_ROWS();

Get multiple GROUP BY results per group, or use separate concatenated table

I am working on an auction web application. Now i have a table with bids, and from this table i want to select the last 10 bids per auction.
Now I know I can get the last bid by using something like:
SELECT bids.id FROM bids WHERE * GROUP BY bids.id ORDER BY bids.created
Now I have read that setting an amount for the GROUP BY results is not an easy thing to do, actually I have found no easy solution, if there is i would like to hear that.
But i have come up with some solutions to tackle this problem, but I am not sure if i am doing this well.
Alternative
The first thing is creating a new table, calling this bids_history. In this table i store a string of the last items.
example:
bids_history
================================================================
auction_id bid_id bidders times
1 20,25,40 user1,user2,user1 time1,time2,time3
I have to store the names and the times too, because I have found no easy way of taking the string used in bid_id(20,25,40), and just using this in a join.
This way i can just just join on auction id, and i have the latest result.
Now when there is placed a new bid, these are the steps:
insert bid into bids get the lastinserteid
get the bids_history string for this
auction product
explode the string
insert new values
check if there are more than 3
implode the array, and insert the string again
This all seems to me not a very well solution.
I really don't know which way to go. Please keep in mind this is a website with a lot of bidding's, they can g up to 15.000 bidding's per auction item. Maybe because of this amount is GROUPING and ORDERING not a good way to go. Please correct me if I am wrong.
After the auction is over i do clean up the bids table, removing all the bids, and store them in a separate table.
Can someone please help me tackle this problem!
And if you have been, thanks for reading..
EDIT
The tables i use are:
bids
======================
id (prim_key)
aid (auction id)
uid (user id)
cbid (current bid)
created (time created)
======================
auction_products
====================
id (prim_key)
pid (product id)
closetime (time the auction closses)
What i want as the result of the query:
result
===============================================
auction_products.id bids.uid bids.created
2 6 time1
2 8 time2
2 10 time3
5 3 time1
5 4 time2
5 9 time3
7 3 time1
7 2 time2
7 1 time3
So that is per auction the latest bids, to choose by number, 3 or 10
Using user variable, and control flow, i end up with that (just replace the <=3 with <=10 if you want the ten auctions) :
SELECT a.*
FROM
(SELECT aid, uid, created FROM bids ORDER BY aid, created DESC) a,
(SELECT #prev:=-1, #count:=1) b
WHERE
CASE WHEN #prev<>a.aid THEN
CASE WHEN #prev:=a.aid THEN
#count:=1
END
ELSE
#count:=#count+1
END <= 3
Why do this in one query?
$sql = "SELECT id FROM auctions ORDER BY created DESC LIMIT 10";
$auctions = array();
while($row = mysql_fetch_assoc(mysql_query($sql)))
$auctions[] = $row['id'];
$auctions = implode(', ', $auctions);
$sql = "SELECT id FROM bids WHERE auction_id IN ($auctions) ORDER BY created LIMIT 10";
// ...
You should obviously handle the case where, e.g. $auctions is empty, but I think this should work.
EDIT: This is wrong :-)
You will need to use a subquery:
SELECT bids1.id
FROM ( SELECT *
FROM bids AS bids1 LEFT JOIN
bids AS bids2 ON bids1.created < bids2.created
AND bids1.AuctionId = bids2.AuctionId
WHERE bid2.id IS NULL)
ORDER BY bids.created DESC
LIMIT 10
So the subquery performs a left join from bids to itself, pairing each record with all records that have the same auctionId and and a created date that is after its own created date. For the most recent record, there will be no other record with a greater created date, and so that record would not be included in the join, but since we use a Left join, it will be included, with all the bids2 fields being null, hence the WHERE bid2.id IS NULL statement.
So the sub query has one row per auction, contianing the data from the most recent bid. Then simply select off the top ten using orderby and limit.
If your database engine doesn't support subqueries, you can use a view just as well.
Ok, this one should work:
SELECT bids1.id
FROM bids AS bids1 LEFT JOIN
bids AS bids2 ON bids1.created < bids2.created
AND bids1.AuctionId = bids2.AuctionId
GROUP BY bids1.auctionId, bids1.created
HAVING COUNT(bids2.created) < 9
So, like before, left join bids with itself so we can compare each bid with all the others. Then, group it first by auction (we want the last ten bids per auction) and then by created. Because the left join pairs each bid with all previous bids, we can then count the number of bids2.created per group, which will give us the number of bids occurring before that bid. If this count is < 9 (because the first will have count == 0, it is zero indexed) it is one of the ten most recent bids, and we want to select it.
To select last 10 bids for a given auction, just create a normalized bids table (1 record per bid) and issue this query:
SELECT bids.id
FROM bids
WHERE auction = ?
ORDER BY
bids.created DESC
LIMIT 10
To select last 10 bids per multiple auctions, use this:
SELECT bo.*
FROM (
SELECT a.id,
COALESCE(
(
SELECT bi.created
FROM bids bi
WHERE bi.auction = a.id
ORDER BY
bi.auction DESC, bi.created DESC, bi.id DESC
LIMIT 1 OFFSET 9
), '01.01.1900'
) AS mcreated
COALESCE(
(
SELECT bi.id
FROM bids bi
WHERE bi.auction = a.id
ORDER BY
bi.auction DESC, bi.created DESC, bi.id DESC
LIMIT 1 OFFSET 9
), 0)
AS mid
FROM auctions a
) q
JOIN bids bo
ON bo.auction >= q.auction
AND bo.auction <= q.auction
AND (bo.created, bo.id) >= (q.mcreated, q.mid)
Create a composite index on bids (auction, created, id) for this to work fast.

Categories