I have a huge number of rows that I'd like to get say, last 5 records inserted in that database from 10 different users. If the same user inserted the last 3 rows into database, we must get one row, skip the others two and move to get a row per user, until it count up to 5.
A database like that:
user_id | news_id | title
1 | 1 | foo-1
2 | 2 | foo-2
3 | 3 | foo-3
1 | 4 | baa
4 | 5 | baa0
5 | 6 | baa1
5 | 7 | baa2
6 | 8 | baa3
7 | 9 | baa4
Should return:
user_id | news_id | title
1 | 1 | foo-1
2 | 2 | foo-2
3 | 3 | foo-3
4 | 5 | baa0
5 | 6 | baa1
The current filter was done by PHP, like this:
$used = array();
while ($data = mysql_fetch_array($query)) {
$uid = $data['user_id'];
if(in_array($uid, $used))
continue;
array_push($used, $uid);
// do something with data
}
But I want to refactor it, and do the filter purely by mysql, if possible. I don't know much MySql and that's why I'm having problem to archive this...
Here's what I've tried
select DISTINCT(user_id), news_id, title from XXX
WHERE GROUP BY (news_id) DESC
LIMIT 0,5
How can I do that?
1 way you can do it is to generate a partitioned row number per user and then select 5 records where RowNumber = 1.
SELECT *
FROM
(
SELECT
d.user_id
,d.news_id
,d.title
,(#rn:= if(#uid = user_id, #rn + 1,
if(#uid:=user_id,1,1)
)
) as RowNumber
FROM
Data d
CROSS JOIN (SELECT #uid:=-1, #rn:=0) vars
ORDER BY
user_id
,news_id
) t
WHERE
t.RowNumber = 1
ORDER BY news_id
LIMIT 5;
http://rextester.com/JRIZI7402 - example to show it working
Note you can change the row order by simply changing the ORDER BY statement of the derived table so if you have a column that will signify the latest record e.g. an identity column or a datetime column you can use that, but user_id must be the first criteria to be partitioned correctly.
Do it from your query.
"SELECT * FROM table GROUP BY user_id ORDER BY news_id DESC LIMIT 5"
well, i think this will achieve what you are after.
select user_id, news_id, title from tableName
GROUP BY user_id
ORDER BY news_id DESC
LIMIT 0,5
Hope this helps!
My site allows users to guess the result of a sports match. At the end of the match the guesses should be compared to the actual result. The winner(s) are the members with the closest correct guess
Im looking for a way to return all members who guessed the correct result and score difference IF NO (zero) member guessed correctly return members who guessed closest to the correct result
See MYSQL FIDLE EXAMPLE
I modified the script to change fixed values taking variables as you can see below
if(isset($_POST['resultBtn'])){
foreach($_POST['winner'] as $id =>$winner){
$winScore = $_POST['score'][$id];
:
:
$sql="SELECT p.*
FROM Multiple_Picks p
WHERE p.event_id='$matchId' AND
p.pick='$winner' AND
abs(p.score-'$winScore') = (SELECT min(abs(p2.score-1))
FROM Multiple_Picks p2
Where p2.pick=p.pick AND
p2.event_id = p.event_id)";
My problem is if I run this script on the following table:
NOTHING gets displayed even if I put result exactly correct:
My variable values are correct in the sql statment so that is not the problem
Any help will be welcomed...
IMPORTANT THE USER WHO SELECTED CLOSEST CORRECT RESULTS, FOR ALL GAME, DURING THE ROUND IS THE WINNER
example: if user A won 4 of the picks and user B won 5 of the picks then user B is the winner of the round
Why don't you want just
SELECT p.*, abs(p.score-'$winScore') as diff
FROM Multiple_Picks p
WHERE p.event_id='$matchId' AND p.pick='$winner'
ORDER BY diff ASC
LIMIT 1
This will return the closest member for the event. Remove the LIMIT if you need a few of them.
Also, never put your parameters directly into the SQL query, even trusted ones (not your case) and even if you're sure they will always be integer or non-string type. Use prepared statements.
In this answer I call a "Best" pick any pick that has chosen the correct winner for a particular match, and has the closest score to the actual match score.
These scripts also respect the different "rounds" in the competition, since that is an important complication.
This answer comes in two parts: first a query that is similar to the one in the question that returns all the "Best" picks for a particular match. To make it easier to run in SQL Fiddle, I have used MySQL variables instead of PHP variables.
Schema with test data:
create table Multiple_Picks (
pick_id int,
member_nr int,
event_id int,
pick varchar(100),
score int
);
insert into Multiple_Picks
values
(11,100,1,'Crusaders',15),
(12,100,2,'Waratahs',10),
(13,100,3,'Chiefs',4),
(21,200,1,'Crusaders',15),
(22,200,2,'Waratahs',10),
(23,200,3,'Lions',4),
(31,300,1,'Crusaders',15),
(32,300,2,'Waratahs',12),
(33,300,3,'Lions',6),
(41,100,4,'Crusaders',20),
(42,100,5,'Waratahs',20),
(43,100,6,'Lions',20)
;
Queries to show all picks and then best picks for a particular match:
set #matchId = 2;
set #winner = 'Waratahs';
set #winScore = 8;
-- Show all picks for a particular match
select * from Multiple_Picks
where event_id = #matchId;
-- Show best picks for a particular match
select p.*
from Multiple_Picks p
where p.event_id = #matchId
and p.pick = #winner
and abs(p.score - #winScore) =
(select min(abs(other.score - #winScore))
from Multiple_Picks other
where other.event_id = #matchId
and other.pick = #winner
)
;
SQL Fiddle to show picks for particular match
-- Show all picks for a particular match
+---------+-----------+----------+----------+-------+
| pick_id | member_nr | event_id | pick | score |
+---------+-----------+----------+----------+-------+
| 12 | 100 | 2 | Waratahs | 10 |
| 22 | 200 | 2 | Waratahs | 10 |
| 32 | 300 | 2 | Waratahs | 12 |
+---------+-----------+----------+----------+-------+
-- Show best picks for a particular match
+---------+-----------+----------+----------+-------+
| pick_id | member_nr | event_id | pick | score |
+---------+-----------+----------+----------+-------+
| 12 | 100 | 2 | Waratahs | 10 |
| 22 | 200 | 2 | Waratahs | 10 |
+---------+-----------+----------+----------+-------+
Now we need to work towards finding the winner of each round of the competition.
First we have extra test data that contains the actual scores for Matches in rounds 1 and 2.
create table Matches (
event_id int,
winner varchar(100),
score int,
round int
);
insert into Matches
values
(1,'Crusaders',10,1),
(2,'Waratahs',11,1),
(3,'Lions',4,1),
(4,'Crusaders',20,2),
(5,'Waratahs',20,2),
(6,'Chiefs',20,2)
;
Now select the best picks for all Matches. The subselect (aliased as m) calculates best_diff for each match as the minimum difference between the actual score and every guessed score. This subselect is then joined to every pick so that only "Best" picks are returned.
-- Show all best picks for all Matches
select p.*, m.round
from Multiple_Picks p
join (
select m2.event_id, m2.winner, m2.score, m2.round,
min(abs(m2.score-p2.score)) as best_diff
from Matches m2
join Multiple_Picks p2
on p2.event_id = m2.event_id and p2.pick = m2.winner
group by m2.event_id, m2.winner, m2.score, m2.round
) as m
on p.event_id = m.event_id and p.pick = m.winner
and abs(m.score - p.score) = m.best_diff
order by m.round, p.event_id
;
It is then easy to get a count of Best picks for each player for each round by just grouping the previous query by member_nr and round:
-- Show a count of best picks for each player for each round
select p.member_nr, m.round, count(*) as best_count
from Multiple_Picks p
join (
select m2.event_id, m2.winner, m2.score, m2.round,
min(abs(m2.score-p2.score)) as best_diff
from Matches m2
join Multiple_Picks p2
on p2.event_id = m2.event_id and p2.pick = m2.winner
group by m2.event_id, m2.winner, m2.score, m2.round
) as m
on p.event_id = m.event_id and p.pick = m.winner
and abs(m.score - p.score) = m.best_diff
group by p.member_nr, m.round
order by m.round, count(*) desc
;
SQL Fiddle for all best picks and counts for all matches
-- Show all best picks for all Matches
+---------+-----------+----------+-----------+-------+-------+
| pick_id | member_nr | event_id | pick | score | round |
+---------+-----------+----------+-----------+-------+-------+
| 31 | 300 | 1 | Crusaders | 15 | 1 |
| 21 | 200 | 1 | Crusaders | 15 | 1 |
| 11 | 100 | 1 | Crusaders | 15 | 1 |
| 12 | 100 | 2 | Waratahs | 10 | 1 |
| 32 | 300 | 2 | Waratahs | 12 | 1 |
| 22 | 200 | 2 | Waratahs | 10 | 1 |
| 23 | 200 | 3 | Lions | 4 | 1 |
| 41 | 100 | 4 | Crusaders | 20 | 2 |
| 42 | 100 | 5 | Waratahs | 20 | 2 |
+---------+-----------+----------+-----------+-------+-------+
-- Show a count of best picks for each player for each round
+-----------+-------+------------+
| member_nr | round | best_count |
+-----------+-------+------------+
| 200 | 1 | 3 |
| 300 | 1 | 2 |
| 100 | 1 | 2 |
| 100 | 2 | 2 |
+-----------+-------+------------+
The final stage is to select only those players for each round who have the highest number of Best picks. I tried modifying the above queries, but the nesting becomes two confusing, so my solution was to create a few logical views so that the final query can be more easily understood. The views basically encapsulate the logic of the queries I have explained above:
create view MatchesWithBestDiff as
select m.event_id, m.winner, m.score, m.round,
min(abs(m.score-p.score)) as best_diff
from Matches m
join Multiple_Picks p
on p.event_id = m.event_id and p.pick = m.winner
group by m.event_id, m.winner, m.score, m.round
;
create view BestPicks as
select p.*, m.round
from Multiple_Picks p
join MatchesWithBestDiff m
on p.event_id = m.event_id and p.pick = m.winner
and abs(m.score - p.score) = m.best_diff
;
create view BestPickCount as
select member_nr, round, count(*) as best_count
from BestPicks
group by member_nr, round
;
So that the query that shows the winners of each round is simply:
-- Show the players with the highest number of Best Picks for each round
select *
from BestPickCount p
where best_count =
(
select max(other.best_count)
from BestPickCount other
where other.round = p.round
)
order by round
;
SQL Fiddle for players with most Best picks for each round
-- Show the players with the highest number of Best Picks for each round
+-----------+-------+------------+
| member_nr | round | best_count |
+-----------+-------+------------+
| 200 | 1 | 3 |
| 100 | 2 | 2 |
+-----------+-------+------------+
This whole investigation has reminded me how tricky it can be to get SQL to do much manipulation where records need to be selected depending on maximums and sums. Some of these types of queries can be much easier with window functions (the OVER and PARTITION BY clauses), but they are not available in MySQL.
While designing the above queries, I found a few interesting restrictions:
MySQL does not allow joins to subqueries in views definitions.
ANSI SQL does not allow an aggregate in a subquery to reference both a column from the inner query and a column from the outer query. MySQL seems to sometimes allow this, but I couldn't find clear guidance as to when it is allowed, so I chose to code the above queries to avoid this "feature".
scenario 1: NO USERS SELECTED THE CORRECT TEAM
I believe that result in this situation should be empty result because everyone has made a mistake.
SCORE RETURN MEMBERS WHO SELECTED THE CLOSEST TO CORRECT SCORE AND
RESULT
It seems to be already working in your code example except one mistake in select.
abs(p.score-'$winScore') = (SELECT min(abs(p2.score-1))
Instead of constant 1 (one) it should be variable '$winScore'
and to control the number of users you get, you may limit your results so you will get something like this:
$sql="SELECT p.*
FROM Multiple_Picks p
WHERE p.event_id='$matchId' AND
p.pick='$winner' AND
abs(p.score-'$winScore') = (SELECT min(abs(p2.score-'$winner'))
FROM Multiple_Picks p2
Where p2.pick=p.pick AND
p2.event_id = p.event_id)
order by p.id limit '$numberOfMembers'";
SCENARIO 2: SCENARIO 2: MULTIPLE USERS SELECTED CORRECT TEAM BUT
SCORES ARE DIFFERENT RETURN USER(S) WHO GUESSED CLOSEST TO CORRECT
SCORE
Same as in the previous question.
SCENARIO 3: MULTIPLE USERS SELECTED CORRECT TEAM AND SCORE RETURN ALL
USERS WHO SELECTED CORRECT TEAM AND SCORE
You can achieve this using same query just replace the LIMIT with 'rank' function, and also if you will get several closest scores, but you have to limit their number according to their voting order by id, for this purpose I suggest sorting.
So final query will be:
$sql="select * from (SELECT p.*,
abs(p.score-'$winScore') scr_diff,
#rownum := #rownum + 1 rank
FROM Multiple_Picks p,
(SELECT #rownum := 0) rank_gen
WHERE p.event_id='$matchId' AND
p.pick='$winner' AND
abs(p.score-'$winScore') = (SELECT min(abs(p2.score-'$winner'))
FROM Multiple_Picks p2
Where p2.pick=p.pick AND
p2.event_id = p.event_id)
order by p.id
) sq
where sq.scr_diff = 0
or sq.rank < '$numberOfMembers'";
Fiddle.
Best guesser for one match
First find the member(s) who picked the winner and had the closest score guess:
SELECT p.*
FROM
( SELECT MIN(ABS(score-'$winScore')) AS closest
FROM Multiple_Picks
WHERE event_id = '$matchId'
AND pick='$winner'
) AS c
JOIN Multiple_Picks p
WHERE p.event_id = '$matchId'
AND p.pick = '$winner'
AND ABS(score-'$winScore') = c.closest
If that return no results, then what should happen? (It would be because no one picked the winner for a particular event.)
But, I think your question is much more complex. However, the above gives a mapping from (event_id, pick) -> list-of-members who "won". Starting over...
Missing info
There is a mystery -- Where do the event results come from? I will assume this table is already populated:
CREATE TABLE Win (
event_id ..., -- which game
winnner ..., -- who won
score ... -- by what score
)
Best guesser overall
So, create a table of BestGuessers(event_id, member). The details of "all game" and "round" are a bit vague. So I will carry this at least one step further.
CREATE TEMPORARY TABLE BestGuessers(
event_id ...,
member_nr ... -- who guessed the best for that event
)
SELECT p.event_id, p.member_nr
FROM
( SELECT w.event_id, w.winner, MIN(ABS(mp.score-w.score)) AS closest
FROM Multiple_Picks AS mp
JOIN Win AS w ON mp.event_id = w.event_id
AND mp.pick = w.winner
GROUP BY w.event_id, w.winner
) AS c
JOIN Multiple_Picks p
ON p.event_id = c.event_id
AND p.pick = c.pick
AND p.score = c.closest
Now, from that, you can pick the best guesser(s).
SELECT y.member_nr
FROM
( SELECT COUNT(*) AS ct
FROM BestGuessers
GROUP BY member_nr
ORDER BY COUNT(*) DESC
LIMIT 1
) AS x -- the max number of correct guesses
STRAIGHT_JOIN
( SELECT member_nr, COUNT(*) AS ct
FROM BestGuessers
GROUP BY member_nr
) AS y -- the users who guessed correctly that many times
USING (ct);
All this is pretty complex; I may have some typos, even logic errors. But maybe I came close.
It seems an additional table to store the actual results would help here.
E.g let's say this is in a table called results with sample values as follows:
event_id winner result
1 Crusaders 16
2 Waratahs 15
3 Chiefs 4
4 Crusaders 17
5 Reds 12
0 Rebels 14
7 Cheetahs 15
8 Crusaders 14
This can then be JOINed on each row and results compared as follows:
SELECT p.*
, CASE WHEN ABS(p.score - r.result)
- CASE WHEN p.pick = r.winner THEN 999999 ELSE 0 END
= (SELECT MIN(ABS(p2.score - r2.result)
- CASE WHEN p2.pick = r2.winner THEN 999999 ELSE 0 END)
FROM picks p2
JOIN results r2
ON p2.event_id = r2.event_id
WHERE p2.event_id = p.event_id)
THEN 1
ELSE 0
END AS win
FROM picks p
JOIN results r
ON p.event_id = r.event_id;
Explanation
The rightmost win column is 1 if the member is calculated to have won or drawn the event, otherwise it is 0. The method used is similar to the one in your post, with the main difference being the team and score are combined. The main thing to be explained here is the 999999, which is subtracted when a correct team is picked - so this can be sure to eclipse the score difference. (Of course, an even bigger value could be picked if needed).
Demo
SQL Fiddle Demo
I have a table that holds price information. I need to select the max value of every three rows. EXAMPLE:
Table `daily_high`
____ _______
| ID | HIGH |
| 1 | 24.65 |
| 2 | 24.93 |
| 3 | 26.02 |
| 4 | 25.33 |
| 5 | 25.16 |
| 6 | 25.91 |
| 7 | 26.05 |
| 8 | 28.13 |
| 9 | 27.07 |
|____|_______|
Desired output to new table (ID will be auto-increment so don't assume an association exists between this ID 1 and the daily_high ID 1:
____ ___________
| ID | 3MaxHIGH |
|____|___________|
| 1 | 26.02 |
| 2 | 25.91 |
| 3 | 28.13 |
|____|___________|
I want to compare IDs 1,2, and 3 to determine the high value among them. Then once I have compared 1-3, I want to move on to 4 through 6, then 7 through 9, etc until I've done this for all values contained in the table (currently about 400,000 values). I have written code that uses
SELECT max(HIGH) FROM daily_high as dh1 JOIN (SELECT max(HIGH) FROM daily_high WHERE id >= dh1 AND id < (dh1.id + 3))
This works but is horribly slow. I've tried using the SELECT statement where I identify the column values to be pull for display, meaning between the SELECT and FROM parts of the query.
I've tried to use JOIN to join all 3 rows onto the same table for comparison but it too is horribly slow. By slow I mean just under 10 seconds to gather information for 20 rows. This means that the query has analyzed 60 rows (20 groups of 3) in 9.65879893303 seconds (I didn't make this up, I used microtime() to calculate it.
Anyone have any suggestions for faster code than what I've got?
Keep in mind that my actual table is not the same as what I've posted above, but it the concept is the same.
Thanks for any help.
If you ID it continous you can make this
SELECT floor(id/3) as range, max(HIGH) FROM daily_high GROUP BY range;
Why not to use DIV operator for grouping your aggregation:
SELECT (id-1) DIV 3 + 1 AS ID, MAX(high) AS 3MaxHIGH
FROM daily_high
GROUP BY (id-1) DIV 3
This query gives the same result.
ID 3MaxHIGH
1 26.02
2 25.91
3 28.13
I was unable to run your query, and I believe that this one is faster.
UPD: To ensure that you have valid groups for your ranges, use this query:
select id, high, (id-1) div 3 + 1 from daily_high
result:
id high (id-1) div 3 + 1
1 24.65 1
2 24.93 1
3 26.02 1
4 25.33 2
5 25.16 2
6 25.91 2
7 26.05 3
8 28.13 3
9 27.07 3
Fuller answer with an example. The following code will do what I think you want.
SELECT FLOOR((row - 1) / 3), MAX(Sub1.high)
FROM (SELECT #row := #row + 1 as row, daily_high.*
FROM daily_high, (SELECT #row := 0) r) Sub1
GROUP BY FLOOR((row - 1) / 3)
ORDER BY Sub1.ID
The below query worked for me on a test table. perhaps not the best, but the other solutions failed on my test table.
This does require the ID's to be sequential. Also be sure to put an index on High aswell for speed.
SELECT FLOOR(T1.Id/3)+1 AS Id, ROUND(GREATEST(T1.High, T2.High, T3.High),2) AS High FROM `daily_high` T1, `daily_high` T2, `daily_high` T3
WHERE T2.Id=T1.Id+1
AND T3.Id=T2.Id+1
AND MOD(T1.Id, 3)=1
logic: if(id is divisible by 3, id/3-1, id/3)
select if(mod(id,3) = 0,floor(id/3)-1,floor(id/3)) as group_by_col , max(HIGH)
FROM daily_high GROUP BY group_by_col;
Please excuse the vagueness of my title, I really don't know how to succinctly write what I am trying to do.
I have a table with
name - str
item_id - int
position - int
name item_id position
| 6asd | 17 | 1 |
| asdf | 3 | 1 |
| asdf | 3 | 2 |
| asdf | 3 | 3 |
| asdf | 3 | 4 |
I am trying to use an IN query on a list of item_ids and only return 1 row per item and that should be the row with the lowest position.
select name, item_id, position from my_table
WHERE item_id IN (17, 3) AND name != '';
This returns all rows for the item_id when I am only trying to return the lowest position. Naturally I looked at ORDER_BY and LIMIT but my query only is done on the whole results set not on each id individually.
ORDER BY position ASC LIMIT 1
Has anyone tried to do this before?? any help would be greatly appreciated. Do I need to use a subselect somehow?
Use MIN() that is available.
SELECT name, item_id, MIN(position)
FROM my_table
WHERE item_id IN (17, 3) AND name != ''
GROUP BY item_id;
I think you might be looking for the MIN() function.
MySQL MIN function is used to find out the record with minimum value among a record set.
Try something along these lines:
"SELECT name, item_id, MIN(position)
FROM my_table
WHERE item_id IN (17, 3) AND name != ''
GROUP BY item_id";
My table will hold scores and initials.
But the table wont be ordered.
I can get the total row count easy enough and I know I can get all of them and Order By and then loop through them and get the rank THAT way...
But is there a better way? Could this maybe be done with the SQL statement?
I'm not TOO concerned about performance so if the SQL statement is some crazy thing, then Ill just loop.
Sorry - Table has id as primary key, a string to verify unique app install, a column for initials and a column for score.
When someone clicks GET RANK... I want to be able to tell them that their score is 100 out of 1000 players.
SELECT s1.initials, (
SELECT COUNT(*)
FROM scores AS s2
WHERE s2.score > s1.score
)+1 AS rank
FROM scores AS s1
Do you have a primary key for the table or are you fetching data by the initials or are you just fetching all the data and looping through it to get the single record? How many results are you trying to fetch?
If you only want to fetch one record, as the title suggests, you would query the database using a WHERE conditional:
SELECT score FROM table WHERE user_id = '1';
See this answer: https://stackoverflow.com/a/8684441/125816
In short, you can do something like this:
SELECT id, (#next_rank := IF(#score <> score, 1, 0)) nr,
(#score := score) score, (#r := IF(#next_rank = 1, #r + 1, #r)) rank
FROM rank, (SELECT #r := 0) dummy1
ORDER BY score DESC;
And it will produce a result like this:
+------+----+-------+------+
| id | nr | score | rank |
+------+----+-------+------+
| 2 | 1 | 23 | 1 |
| 4 | 1 | 17 | 2 |
| 1 | 0 | 17 | 2 |
| 5 | 1 | 10 | 3 |
| 3 | 1 | 2 | 4 |
+------+----+-------+------+
Note that users with equal scores have equal ranks. :-)