I've got following, simple table
Item (id, name, date, fixed_position)
(1, 'first entry', '2016-03-09 09:00:00', NULL)
(2, 'second entry', '2016-03-09 04:00:00', 1)
(3, 'third entry', '2016-03-09 05:00:00', NULL)
(4, 'fourth entry', '2016-03-09 19:00:00', NULL)
(5, 'fifth entry', '2016-03-09 13:00:00', 4)
(6, 'sixth entry', '2016-03-09 21:00:00', 2)
The number of items is not fixed, in fact can vary from ~100 to ~1000.
What i want to achieve is to perform a query to return set of Items ordered by date field which takes into consideration fixed_position field, which stands for something like "pinned" results to specific positions. If fixed_position for given entry is not NULL the result should be pinned to n-th position and if fixed_position is NULL the ORDER BY should take precedence.
Desired output of query for brighter explanation:
(2, 'second entry', '2016-03-09 04:00:00', 1) // pinned to 1-st position
(6, 'sixth entry', '2016-03-09 21:00:00', 2) // pinned to 2-nd position
(3, 'third entry', '2016-03-09 05:00:00', NULL) // ORDER BY `date`
(5, 'fifth entry', '2016-03-09 13:00:00', 4) // pinned to 4-th position
(1, 'first entry', '2016-03-09 09:00:00', NULL) // ORDER BY `date`
(4, 'fourth entry', '2016-03-09 19:00:00', NULL) // ORDER BY `date`
I've tried solution posted in Ordering MySql results when having fixed position for some items but even with copy-paste method this doesn't seem to work at all.
What I've tried this far is this query:
SELECT
#i := #i +1 AS iterator,
t.*,
COALESCE(t.fixed_position, #i) AS positionCalculated
FROM
Item AS t,
(
SELECT
#i := 0
) AS foo
GROUP BY
`id`
ORDER BY
positionCalculated,
`date` DESC
Which returns:
iterator | id | name | date | fixed_position | positionCalculated
1 1 first entry 2016-03-09 09:00:00 NULL 1
2 2 second entry 2016-03-09 04:00:00 1 1
6 6 sixth entry 2016-03-09 21:00:00 2 2
3 3 third entry 2016-03-09 05:00:00 NULL 3
4 4 fourth entry 2016-03-09 19:00:00 NULL 4
5 5 fifth entry 2016-03-09 13:00:00 4 4
Does MySQL can perform such task or should I take backend approach and perform PHP's array_merge() on two result sets?
A brute force method to solve this would be to first create a tally table having an amount of rows bigger than the original table:
SELECT #rn := #rn + 1 AS rn
FROM (
SELECT 1 AS x UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1) AS t1
CROSS JOIN (
SELECT 1 AS x UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1) AS t2
CROSS JOIN (SELECT #rn := 0) AS v
Then you can left join this table to a derived table containing all fixed positions of your original table:
SELECT Tally.rn
FROM (
... tally table query here
) AS Tally
LEFT JOIN (
SELECT fixed_position
FROM Item
) AS t ON Tally.rn = t.fixed_position
WHERE t.t.fixed_position IS NULL
The above returns the to-be-filled missing order positions.
Demo here
You can now use the above query as yet another derived table joined to the original table to achieve the desired ordering:
SELECT id, name, `date`, fixed_position, Gaps.rn,
derived.seq, Gaps.seq
FROM (
SELECT id, name, `date`, fixed_position,
#seq1 := IF(fixed_position IS NULL, #seq1 + 1, #seq1) AS seq
FROM Item
CROSS JOIN (SELECT #seq1 := 0) AS v
ORDER BY `date`
) AS derived
LEFT JOIN (
SELECT Tally.rn,
#seq2 := #seq2 + 1 AS seq
FROM (
SELECT #rn := #rn + 1 AS rn
FROM (
SELECT 1 AS x UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1) AS t1
CROSS JOIN (
SELECT 1 AS x UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1) AS t2
CROSS JOIN (SELECT #rn := 0) AS v
) AS Tally
LEFT JOIN (
SELECT fixed_position
FROM Item
) AS t ON Tally.rn = t.fixed_position
CROSS JOIN (SELECT #seq2 := 0) AS v
WHERE t.t.fixed_position IS NULL
ORDER BY rn
) AS Gaps ON (derived.seq = Gaps.seq) AND (derived.fixed_position IS NULL)
ORDER BY COALESCE(derived.fixed_position, Gaps.rn)
Demo here
I've had the same problem (sort by date + inject rows with fixed position values). The above solution seems to work. But you have to know how much values your table has. The line:
SELECT 1 AS x UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
has to get extended if you have more rows because the temporary table is to short. After some google search results and tests in my DB I've figured out a solution that fits your needs and is smarter to understand I think.
SELECT *
FROM
(SELECT
create_date,
fixedposition,
#rownum:=#rownum + 1 AS colposition,
1 AS majorEntry
FROM myTable
JOIN (SELECT #rownum:=0) r
WHERE fixedposition IS NULL
ORDER BY crdate DESC) AS orderedFixed
UNION ALL
(SELECT create_date,
fixedposition,
fixedposition AS colposition,
0 AS majorEntry
FROM myTable
WHERE fixedposition IS NOT NULL
ORDER BY fixedposition ASC)
ORDER BY colposition ASC, majorEntry
So this is how it works: There are two SELECT statements.
The first SELECT searches for all columns without a fixed position and sorts them by date. Additionally it makes a JOIN to add a row counter column.
The second one searches for all rows with a fixed position and returns the sorting by "colposition".
Both select statements gets combined by an UNION ALL. The union gets sorted first by the ASCending colposition and in the second step by the majonEntry-value which indicates that the rows with fixedposition are to be placed before the other rows which have the same position.
If anyone stumbles upon this, I came up with a good solution with the help of dk1's answer. While his answer is good in principle, it has a flaw that every iteration of a pinned row has its position increased by one. For example: post pinned to the 1st position is on the 1st place (+0), post pinned to the 2nd position is on the 3rd place (+1), and post pinned to the 4th position is on the 6th place (+2).
I solved this by adding a second variable to decrease the final position of a pinned post.
Code explanation:
minor = derived table with unpinned posts
major= derived table with pinned posts
#rownum = iterator for posts in minor table
#decrease = iterator for posts in major table
SELECT
final.*
FROM
(
(
# see note #1 about this wrapper query
SELECT * FROM
(
SELECT
minor.*,
(#rownum := #rownum + 1) AS 'rowPosition',
0 AS 'rowDecrease',
0 AS 'majorEntry'
FROM table_with_your_posts minor
JOIN (#rownum := 0) x
WHERE minor.your_fixed_position IS NULL
ORDER BY minor.your_date DESC
) y
ORDER BY y.your_date DESC
)
UNION ALL
(
SELECT
major.*,
major.your_fixed_position AS 'rowPosition',
(#decrease := #decrease + 1) AS 'rowDecrease',
1 AS 'majorEntry'
FROM table_with_your_posts major
JOIN (#decrease := -1) x
WHERE major.your_fixed_position IS NOT NULL
ORDER BY major.your_fixed_position ASC
)
) final
# see note #2 about this ORDER BY clause
ORDER BY final.rowPosition - final.rowDecrease ASC, final.majorEntry DESC
note #1: Now I'm not an expert, but if you don't wrap the query of the minor table in another query and don't order it by the same thing as in the inner query, the sorting in the minor table just doesn't work. Maybe someone can shed more light on that.
note #2: ORDER BY final.rowPosition - final.rowDecrease - final.majorEntry / 2 ASC also works and looks cooler
Note that if you want to further filter your results (good example here would be to only show published posts), you will most likely need to add the filter condition to the WHERE clause of both the derived tables. Otherwise the final positions will be incorrect.
In the end I want to add that this isn't foolproof. For example if there are two pinned posts with the same position, it will shift the results by one. This could be solved by making the column with the fixed position UNIQUE. Other thing that comes to my mind is that if the table has less results than the position value of a pinned post, the post will obviously not be on its desired position (e.g. if the table contains 5 posts and you pin a post to the 8th position, it will probably end up being 5th). And remember that I didn't meaningfully test this, make sure your query is efficient for your needs.
Related
I have this table :
id idm date_play
1 5 2017-08-23 12:12:12
2 5 2017-08-23 12:12:12
3 6 2017-08-23 12:14:13
I want to identify if user has more then one insert in the same second. In the case describe I want to get the user id that is 5.
I tried like this :
SELECT `idm`, MAX(`s`) `conseq` FROM
(
SELECT
#s := IF(#u = `idm` AND (UNIX_TIMESTAMP(`date_play`) - #pt) BETWEEN 1 AND 100000, #s + 1, 0) s,
#u := `idm` `idm`,
#pt := UNIX_TIMESTAMP(`date_play`) pt
FROM table
WHERE date_play >= '2017-08-23 00:00:00'
AND date_play <= '2017-08-23 23:59:59'
ORDER BY `date_play`
) AS t
GROUP BY `idm`
Can you help me please ? Thx in advance and sorry for my english.
Assuming your dates are accurate down to the second level, you can do this with a single aggregation:
select idm
from t
group by idm
having count(*) > count(distinct date_play);
If date_play has fractional seconds, then you would need to remove those (say by converting to a string).
If you want the play dates where there are duplicates:
select idm, date_play
from t
group by idm, date_play
having count(*) >= 2;
Or, for just the idms, you could use select distinct with group by:
select distinct idm
from t
group by idm, date_play
having count(*) >= 2;
(I only mention this because this is the only type of problem that I know of where using select distinct with group by makes sense.)
If you want all the rows that are duplicated, I would go for exists instead:
select t.*
from t
where exists (select 1
from t t2
where t2.idm = t.idm and t2.date_play = t.date_play and
t2.id <> t.id
);
This should have reasonable performance with an index on (idm, date_play, id).
If your table is called mytable, the following should work:
SELECT t.`idm`
FROM mytable t INNER JOIN mytable t2
ON t.`idm`=t2.`idm` AND t.`date_play`=t2.`date_play` AND t.`id`!=t2.`id`
GROUP BY t.`idm`
Basically we join the table with itself, pairing records that have the same idm and date_play, but not the same id. This will have the effect of matching up any two records with the same user and datetime. We then group results by user so you don't get the same user id listed multiple times.
Edit:
Gordon Linoff and tadman's suggestions led me to this probably much more efficient query (credit to them)
SELECT t.`idm`
FROM mytable t
GROUP BY t.`date_play`
HAVING COUNT(t.`id`)>1
I have a table like following
ID student_name dept email
1 Mary Wise Eng mary-wise#xxx.cc
2 John Walter Sc john-walter#xxx.cc
3 Sophia Jacob Politics sophia-jacob#xxx.cc
4 Ava William Eng ava-william#xxx.cc
5 Mary Wise Politics mary-wise#xxx.cc
6 John Walter Eng john-walter#xxx.cc
7 John Walter Politics john-walter#xxx.cc
8 Sophia Eng sophia#xxx.cc
9 Emma Eng emma#xxx.cc
10 Sherlock Eng sherlock#xxx.cc
The email ids col is generated by firstname-lastname#xxx.cc
The problem is when the name is same the email id is also same.
I want the email id to be appended with 1, 2, 3 when same name exists.
For example in table above
the mary-wise on 5th row should be mary-wise1#xxx.cc,
6th row should be, john-walter1#xxx.cc,
7th row should be, john-walter2#xxx.cc
How can I update my email column with mysql query as fast as possible.
I tried with php with mysql it takes too long when the table contains million rows.
Thanks
I believe it's better for you to make email column unique and to use ON DUPLICATE KEY UPDATE syntax (more here).
You still need to keep track of a number you want to append to the new value. For this purpose you can create a separate table with auto increment field and just get the new value from there.
The following SQL will enumerate the duplicates:
select t.*,
#rn := if(#StudentName = StudentName, 1, #rn + 1) as seqnum,
#StudentName := StudentName
from table t cross join
(select #rn := 0, #StudentName := '') const
order by StudentName;
You can put this in an update using join:
update t join
(select t.*,
#rn := if(#StudentName = StudentName, 1, #rn + 1) as seqnum,
#StudentName := StudentName
from table t cross join
(select #rn := 0, #StudentName := '') const
order by StudentName
) toupdate
on t.name = toupdate.name and toupdate.seqnum > 1
set email = concat(replace(t.StudentName, ' ', '-'), toupdate.seqnum - 1, '#xxx.cc);
It would be easy to achieve if you had CTE (maybe switch to postgres 9 if you can):
SELECT
id
, student_name
, concat(
replace(lower(student_name), ' ', '-')
, case
when cnt > 1 then numb
end
,'#xxx.cc'
) as newmail
FROM (
SELECT
count(*) over (partition BY student_name) as cnt
, count(*) over (partition BY student_name order by id) as numb
, id
, student_name
FROM tab1
order by id
) subq
sqlFiddle demo
I have two tables - incoming tours(id,name) and incoming_tours_cities(id_parrent, id_city)
id in first table is unique, and for each unique row from first table there is the list of id_city - s in second table(i.e. id_parrent in second table is equal to id from first table)
For example
incoming_tours
|--id--|------name-----|
|---1--|---first_tour--|
|---2--|--second_tour--|
|---3--|--thirth_tour--|
|---4--|--hourth_tour--|
incoming_tours_cities
|-id_parrent-|-id_city-|
|------1-----|---4-----|
|------1-----|---5-----|
|------1-----|---27----|
|------1-----|---74----|
|------2-----|---1-----|
|------2-----|---5-----|
........................
That means that first_tour has list of cities - ("4","5","27","74")
AND second_tour has list of cities - ("1","5")
Let's assume i have two values - 4 and 74:
Now, i need to get all rows from first table, where my both values are in the list of cities. i.e it must return only the first_tour (because 4 and 74 are in it's list of cities)
So, i wrote the following query
SELECT t.name
FROM `incoming_tours` t
JOIN `incoming_tours_cities` tc0 ON tc0.id_parrent = t.id
AND tc0.id_city = '4'
JOIN `incoming_tours_cities` tc1 ON tc1.id_parrent = t.id
AND tc1.id_city = '74'
And that works fine.
But i generate the query dynamically, and when the count of joins is big (about 15) the query slowing down.
i.e. when i try to run
SELECT t.name
FROM `incoming_tours` t
JOIN `incoming_tours_cities` tc0 ON tc0.id_parrent = t.id
AND tc0.id_city = '4'
JOIN `incoming_tours_cities` tc1 ON tc1.id_parrent = t.id
AND tc1.id_city = '74'
.........................................................
JOIN `incoming_tours_cities` tc15 ON tc15.id_parrent = t.id
AND tc15.id_city = 'some_value'
the query run's in 45s(despite on i set indexes in the tables)
What can i do, to optimaze it?
Thanks much
SELECT t.name
FROM incoming_tours t INNER JOIN
( SELECT id_parrent
FROM incoming_tours_cities
WHERE id IN (4, 74)
GROUP BY id_parrent
HAVING count(id_city) = 2) resultset
ON resultset.id_parrent = t.id
But you need to change number of total cities count.
SELECT name
FROM (
SELECT DISTINCT(incoming_tours.name) AS name,
COUNT(incoming_tours_cities.id_city) AS c
FROM incoming_tours
JOIN incoming_tours_cities
ON incoming_tours.id=incoming_tours_cities.id_parrent
WHERE incoming_tours_cities.id_city IN(4,74)
HAVING c=2
) t1;
You will have to change c=2 to whatever the count of id_city you are searching is, but since you generate the query dynamically, that shouldn't be a problem.
I'm pretty sure this works, but a lot less sure that it is optimal.
SELECT * FROM incoming_tours
WHERE
id IN (SELECT id_parrent FROM incoming_tours_cities WHERE id_city=4)
AND id IN (SELECT id_parrent FROM incoming_tours_cities WHERE id_city=74)
...
AND id IN (SELECT id_parrent FROM incoming_tours_cities WHERE id_city=some_value)
Just an hint.
If you use the IN operator in a WHERE clause, you can hope that the short-circuit of operator AND may remove unnecessary JOINs during the execution for the tours that do not respect the constraint.
Seems like an odd way to do that query, here
SELECT t.name FROM `incoming_tours` as t WHERE t.id IN (SELECT id_parrent FROM `incoming_tours_cities` as tc WHERE tc.id_city IN ('4','74'));
I think that does it, but not tested...
EDIT: Added table alias to sub-query
I've written this query using CTE's and it includes the test data in the query. You'll need to modify it so that it queries the real tables instead. Not sure how it performs on a large dataset...
Declare #numCities int = 2
;with incoming_tours(id, name) AS
(
select 1, 'first_tour' union all
select 2, 'second_tour' union all
select 3, 'third_tour' union all
select 4, 'fourth_tour'
)
, incoming_tours_cities(id_parent, id_city) AS
(
select 1, 4 union all
select 1, 5 union all
select 1, 27 union all
select 1, 74 union all
select 2, 1 union all
select 2, 5
)
, cityIds(id_city) AS
(
select 4
union all select 5
/* Add all city ids you need to check in this table */
)
, common_cities(id_city, tour_id, tour_name) AS
(
select c.id_city, it.id, it.name
from cityIds C, Incoming_tours_cities tc, incoming_tours it
where C.id_city = tc.id_city
and tc.id_parent = it.id
)
, tours_with_all_cities(id_city) As
(
select tour_id from common_cities
group by tour_id
having COUNT(id_city) = #numCities
)
select it.name from incoming_tours it, tours_with_all_cities tic
where it.id = tic.id_city
I have a table that is is sorted 1st by Reminder Date then ID
Table Looks like:
ID | remind_date
1 2011-01-23
2 2010-02-21
4 2011-04-04
5 2011-04-04
6 2009-05-04
I am using a PHP front end to move forward and back thur the records. I want to have forward and back buttons but i am running into a problem with the 2 reminder dates that are the same.
Just to note the ID's are NOT in order, they are here but in the actual database they are mixed up when sorting by reminder_date
The select statement i am using is: ($iid is the current record i am on)
SELECT id FROM myDB.reminders where remind_date > (SELECT remind_date FROM myDB.reminders where id=$iid) order by remind_date ASC LIMIT 1
So what happens when i get to the dates that are the same its skips over one because its asking for remind_date >.
If i use remind_date >= it returns the current record. My solution was then to use limit 2 and check via PHP to if the 1st record = my current ID, if it did use the next one. but what it there are 3 dates the same or 4 etc..
I also thought about using the ID field but since they are out of order i can't add in a ID > $iid.
Any ideas? it works great except for 2 dates that are the same.
You might be able to use this:
SELECT ID, remind_date
FROM
(
SELECT #prev_id := -1
) AS vars
STRAIGHT_JOIN
(
SELECT
ID,
remind_date,
#prev_id AS prev_id,
#prev_id := id
FROM myDB.reminders
ORDER BY remind_date, ID
) T1
WHERE prev_id = $iid
Here is a test of the above with your test data from your comment:
CREATE TABLE Table1 (ID INT NOT NULL, remind_date DATE NOT NULL);
INSERT INTO Table1 (ID, remind_date) VALUES
(45, '2011-01-14'),
(23, '2011-01-22'),
(48, '2011-01-23'),
(25, '2011-01-23'),
(63, '2011-02-19');
SELECT ID, remind_date
FROM
(
SELECT #prev_id := -1
) AS vars
STRAIGHT_JOIN
(
SELECT
ID,
remind_date,
#prev_id AS prev_id,
#prev_id := id
FROM table1
ORDER BY remind_date, ID
) T1
WHERE prev_id = 25
Result:
ID remind_date
48 2011-01-23
add a condition WHERE ID<>MY_LAST_ID. This can not work with triple and more same dates, so you can collect already taken ID's to array like (4,5,6) - see array_push(), implode it with "," to convert to a string (let's call it YOUR_IDS_STRING) and add to your query:
WHERE id NOT IN( YOUR_IDS_STRING )
And after each query make check, does date has changed and if it does - you can unset your array and start from begining (this is not neccesary, but gives you more performance, because YOUR_ID_STRING will be only that long as is need).
If your page is refreshing between queries, maybe try to push YOUR_ID_STRING in session variable, _GET or cookies, and simply concat next id's by operator .=
I used the code provided by Mark Byers and with small changes I adapted it to navigate in opposite directions (and to pass other columns too, not only the date and ID):
$results = $mysqli->query("SELECT * FROM (SELECT #prev_id := -1) AS vars STRAIGHT_JOIN (SELECT *, #prev_id AS prev_id, #prev_id := ID FROM my_table ORDER BY data, ID) T1 WHERE prev_id = ".$ID);
$results = $mysqli->query("SELECT * FROM (SELECT #next_id := 1) AS vars STRAIGHT_JOIN (SELECT *, #next_id AS next_id, #next_id := ID FROM my_table ORDER BY data DESC, ID DESC) T1 WHERE next_id = ".$ID);
I tested it on duplicate dates and it navigates well trough a list of records displayed with:
$results = $mysqli->query("SELECT * FROM my_table ORDER BY data DESC, ID DESC");
I have a points system setup on my site, where every single point accumulated is logged in the points table. The structure is simple, p_userid, p_points (how many points accumulated during this action), and p_timestamp.
I wanna display top 3 point accumulating users, for each month. So essentially, it should sum the p_points table for the month, for each user id, and display the top 3 users, grouped into months. The user ids will be joined to a users table, to get actual user names.
What would be the best way to do it? I use php/mysql.
EDIT:
As a possible solution, I could create another column, and log YYYY-MM into it, and simply group it based on that, but thats more data I gotta log, for an already huge table.
EDIT 2:
Data stored as such
INSERT INTO `points` (`point_userid`, `point_points`, `point_code`, `point_date`) VALUES
(8465, 20, 3, 1237337627),
(46745, 20, 3, 1237337678),
(7435, 20, 3, 1237337733),
(46565, 20, 3, 1237337802),
(4466, 20, 3, 1237337836),
(34685, 20, 3, 1237337885),
(8544, 20, 3, 1237337908),
(6454, 20, 3, 1237337998),
(45765, 20, 3, 1237338008),
(3476, 20, 3, 1237338076);
This isn't easy in MySQL.
First you need to create a table of variables, one for storing the current group, and one for storing the current row number in the group. Initialize them both to NULL.
Then iterate group by month and select all rows ordered by score and select the current rown number and increase it. If the group changes, reset the row number to one.
Then put all this in a subselect and in the outer select, select all rows with rownumber <= 3.
You could use this query:
SELECT month, p_userid, points FROM (
SELECT
*,
(#rn := CASE WHEN month = #last_month THEN #rn + 1 ELSE 1 END) AS rn,
(#last_month := month)
FROM (
SELECT p_userid, month(p_timestamp) AS month, SUM(p_points) AS points
FROM Table1, (SELECT #last_month := NULL, #rn := 0) AS vars
GROUP BY p_userid, month(p_timestamp)
ORDER BY month, points DESC
) AS T1
) AS T2
WHERE rn <= 3
Result:
Month User Score
1 4 7
1 3 5
1 2 4
2 4 17
2 5 10
2 3 6
Test data:
CREATE TABLE Table1 (p_userid INT NOT NULL,
p_points INT NOT NULL,
p_timestamp TIMESTAMP NOT NULL);
INSERT INTO Table1 (p_userid, p_points, p_timestamp) VALUES
(1, 1, '2010-01-01'),
(1, 2, '2010-01-02'),
(1, 3, '2010-02-01'),
(2, 4, '2010-01-01'),
(3, 5, '2010-01-01'),
(3, 6, '2010-02-01'),
(4, 7, '2010-01-01'),
(4, 8, '2010-02-01'),
(4, 9, '2010-02-02'),
(5, 10, '2010-02-02');
Hm,
Too simple?
SELECT COUNT(tb1.p_points) as total_points, tb1.p_userid, tb1.p_timestamp, tb2.username
FROM tb1, tb2
WHERE tb1.p_userid = tb2.username AND p_timestamp BETWEEN 'start_of_date' AND 'end_of_month'
GROUP BY p_userid
ORDER BY total_points DESC LIMIT 3
Syntax might be a little bit out (relatively new to SQL) - wouldn't iterating through a query like this get the result you're looking for? Must admit that Mark's response makes me think this definitely is too simple but figured I'd let you see it anyway.
I'm plpgsql addicted and I don't know if something simmilar can work in MySQL, and how PHP will get results (I don't know if multiple queries will be taken as UNION) but few tests were promising.
CREATE PROCEDURE topusers(OUT query TEXT) BEGIN
DECLARE time TIMESTAMP;
SELECT MIN(CONCAT(EXTRACT(YEAR_MONTH FROM FROM_UNIXTIME(p_timestamp)), '01')) INTO time FROM t;
SET #query = '';
REPEAT
SET #query = CONCAT(#query, '(SELECT SUM(p_points) as total_points, p_userid, ', UNIX_TIMESTAMP(time), '
FROM t
WHERE p_timestamp BETWEEN ', UNIX_TIMESTAMP(time), ' AND ', UNIX_TIMESTAMP(ADDDATE(time, INTERVAL 1 MONTH)), '
GROUP BY p_userid
ORDER BY total_points DESC LIMIT 3)');
SELECT ADDDATE(time, INTERVAL 1 MONTH) INTO time;
IF time < NOW() THEN
SET #query=CONCAT(#query, ' UNION ');
END IF;
UNTIL time > NOW() END REPEAT;
SELECT #query INTO query;
END//
And query
CALL topusers(#query); PREPARE stmt1 FROM #q; EXECUTE stmt1;
and at the end
DEALLOCATE PREPARE stmt1;