I want to delete rows with an offset, so I am forced to use a nested query since its not support in the raw DELETE clause.
I know this would worked (ID is the primary key):
DELETE FROM customers
WHERE ID IN (
SELECT ID
FROM customers
WHERE register_date > '2012-01-01'
ORDER BY register_date ASC
LIMIT 5, 10
);
However, this is unsupported in my version as I get the error
This version of MariaDB doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery'.
Server version: 10.4.22-MariaDB
What can I do to achieve the same result as above that is supported in my version.
CREATE TABLE customers (
ID INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(32) NOT NULL,
REGISTER_DATE DATETIME NOT NULL
);
Join the table to a subquery that uses ROW_NUMBER() window function to sort the rows and filter the rows that you want to be deleted:
DELETE c
FROM customers c
INNER JOIN (
SELECT *, ROW_NUMBER() OVER (ORDER BY register_date) rn
FROM customers
WHERE register_date > '2012-01-01'
) t ON t.ID = c.ID
WHERE t.rn > 5 AND t.rn <= 15; -- get 10 rows with row numbers 6 - 15
See the demo.
If I did not miss something a simple delete with join will do the job...
delete customers
from (select *
from customers
WHERE register_date > '2012-01-01'
order by register_date asc
limit 5, 2) customers2
join customers on customers.id = customers2.id
Here is a demo for your version of MariaDB
You could try assigning a rank to your rows with the ROW_NUMBER window function, then catch those rows whose rank position is between 5 and 15.
DELETE FROM customers
WHERE ID IN (
SELECT *
FROM (SELECT ID,
ROW_NUMBER() OVER(
ORDER BY IF(register_date>'2012-01-01', 0, 1)
register_date ) AS rn
FROM customers) ranked_ids
WHERE rn > 4
AND rn < 16
);
This would safely avoid the use of LIMIT, though achieves the same result.
EDIT. Doing it with a join.
DELETE FROM customers c
INNER JOIN (SELECT ID,
ROW_NUMBER() OVER(
ORDER BY IF(register_date>'2012-01-01', 0, 1)
register_date ) AS rn
FROM customers) ranked_ids
WHERE
) ids_to_delete
ON c.ID = ids_to_delete.ID
AND ids_to_delete.rn > 4
AND ids_to_delete.rn < 16
Given a very simple table bits:
ID | bit_id | timestamp | percent | delta
where bitid is varchar(255) and timestamp is a bigint.
There are many, many rows with identical bit_id's. ID is primary and timestamp is not unique.
With the following SQL i get the rows for a specific set of bit_id's:
SELECT bits.bit_id, bits.timestamp, bits.percent
FROM bits
WHERE bits.bit_id IN ( '00e04c0353bc', '00e04c02c749' )
AND bits.timestamp>1480075040
ORDER BY bits.timestamp DESC
What i want is only the 5 latest rows, per bit_id that match the WHERE-statement. So for each given bit_id in the subset, i want the 5 newest rows.
So simply adding LIMIT n won't do.
How? My MySQL-version does not work with LIMIT in sub-selects.
If you have only two values, then the easiest way is union all:
(SELECT b.bit_id, b.timestamp, b.percent
FROM bits b
WHERE b.bit_id = '00e04c0353bc'
b.timestamp > 1480075040
ORDER BY b.timestamp DESC
LIMIT 5
) UNION ALL
(SELECT b.bit_id, b.timestamp, b.percent
FROM bits b
WHERE b.bit_id = '00e04c02c749'
b.timestamp > 1480075040
ORDER BY b.timestamp DESC
LIMIT 5
);
I'm not sure if the WHERE condition on timestamp is necessary.
If you wanted to do this for more bit_ids or all of them, then variables might be simpler:
select b.*
from (select b.*,
(#rn := if(#b = bit_id, #rn + 1,
if(#b = bit_id, 1, 1)
)
) as rn
from bits b cross join
(select #b := '', #rn := 0) params
order by b.bit_id, b.timestamp desc
) b
where rn <= 5;
I need to show a ranking lists for a sport we manage.
It needs to sum up the 4 best results for each player (from a table that could have hundreds of results per player) and then sort the entire list from the player with the most points to least points.
The query below returns
ERROR 1054 (42S22): Unknown column 'r1.user_id' in 'where clause'
so I've gone off track somewhere.
SELECT r1.user_id, (
SELECT SUM(points)
FROM (
SELECT *
FROM ranking_matrix_points r2
WHERE user_id=r1.user_id
ORDER BY points DESC
LIMIT 4
) r3
) AS total_points
FROM ranking_matrix_points r1
WHERE
user.status IN('active')
GROUP BY r1.user_id
ORDER BY total_points DESC
One possible solution might be to number the rows for each user in order of points descending and then sum up the points with a rank <= 4. This might not perform very well though, and also you'll get a problem with ties (but you would have using limit too).
select user_id, sum(points) total_points
from (
select user_id, points,
(
case user_id
when #cur_user
then #row := #row + 1
else #row := 1 and #cur_user := user_id end
) as rank
from ranking_matrix_points,
(select #row := 0, #cur_user := '') r
order by user_id, points desc
) t
where rank <= 4
group by user_id;
I'm pretty sure there are better ways to do this but I can't think of any at the moment. This would have been very easy in just about any database with support for window functions, but sadly MySQL doesn't support any yet.
You don't need a double query, just
SELECT user_id, SUM(points)
FROM ranking_matrix_points
WHERE user.status in('active')
GROUP BY user_id
ORDER BY total_points DESC
LIMIT 4
or
SELECT TOP 4 user_id, SUM(points)
FROM ranking_matrix_points
WHERE user.status in('active')
GROUP BY user_id
ORDER BY total_points DESC
I've got 2 tables: members and member_logs.
Members can belong to groups, which are in the members table. Given a date range and a group I'm trying to figure out how to get the 10 days with the highest number of successful logins. What I have so far is a massive nest of subquery terror.
SELECT count(member_id) AS `num_users`,
DATE_FORMAT(`login_date`,'%Y-%m-%d') AS `reg_date`
FROM member_logs
WHERE `login_success` = 1
and `reg_date` IN
(SELECT DISTINCT DATE_FORMAT(`login_date`,'%Y-%m-%d') AS `reg_date`
FROM member_logs
WHERE `login_success` = 1
and (DATE_FORMAT(`login_date`,'%Y-%m-%d') BETWEEN '2012-02-25' and '2014-03-04'))
and `member_id` IN
(SELECT `member_id`
FROM members
WHERE `group_id` = 'XXXXXXX'
and `deleted` = 0)
ORDER BY `num_users` desc
LIMIT 0, 10
As far as I understand what is happening is that the WHERE clause is evaluating before the subqueries generate, and that I also should be using joins. If anyone can help me out or point me in the right direction that would be incredible.
EDIT: Limit was wrong, fixed it
The first subquery is totally unnecessary because you can filter by dates directly in the current table member_logs. I also prefer a JOIN for the second subquery. Then what you are missing is grouping by date (day).
A query like the following one (not tested) will do the job you want:
SELECT COUNT(ml.member_id) AS `num_users`,
DATE_FORMAT(`login_date`,'%Y-%m-%d') AS `reg_date`
FROM member_logs ml
INNER JOIN members m ON ml.member_id = m.member_id
WHERE `login_success` = 1
AND DATE_FORMAT(`login_date`,'%Y-%m-%d') BETWEEN '2012-02-25' AND '2014-03-04'
AND `group_id` = 'XXXXXXX'
AND `deleted` = 0
GROUP BY `reg_date`
ORDER BY `num_users` desc
LIMIT 10
SELECT count(member_id) AS `num_users`,
DATE_FORMAT(`login_date`,'%Y-%m-%d') AS `reg_date`
FROM member_logs
WHERE `login_success` = 1
and `login_date` IN
(SELECT `login_date`
FROM member_logs
WHERE `login_success` = 1
and (DATE_FORMAT(`login_date`,'%Y-%m-%d') BETWEEN '2012-02-25' and '2014-03-04'))
and `member_id` IN
(SELECT `member_id`
FROM members
WHERE `group_id` = 'XXXXXXX'
and `deleted` = 0)
Group by `login_date`
ORDER BY `num_users` desc
LIMIT 0, 10
As a slightly more index friendly version of the previous answers;
To make the query index friendly, you shouldn't do per row calculations in the search conditions. This query removes the per row calculation of the string format date in the WHERE, so it should be faster if there are many rows to eliminate by date range;
SELECT COUNT(*) num_users, DATE(login_date) reg_date
FROM member_logs JOIN members ON member_logs.member_id = members.member_id
WHERE login_success = 1 AND group_id = 'XXX' AND deleted = 0
AND login_date >= '2012-02-25'
AND login_date < DATE_ADD('2014-03-04', INTERVAL 1 DAY)
GROUP BY DATE(login_date)
ORDER BY num_users DESC
LIMIT 10
Table with following columns:
Player_id (primary key), Event_type(A,B,C), Points.
1 player may appear many times for every event_type
I would like to show an overall ranking with DESC SUM(Points) GROUP BY player_id from all event-type while putting some conditions:
only best 5 results per player_id for event type A
only best 2 results per player_id for event type B
only best 3 results per player_id for event type C
I have tried in vain :
SUM(points) WHERE event_type ="X"
GROUP BY Player_id ORDER BY SUM(points) LIMIT N
Ive been fighting this headache for a week now, pretty confused when it comes to include sub-queries, UNION, or temp tables. I cant figure out how to put all the pieces together...
My dream would be to get this overall ranking running with the ability to access detailed points breakdown per player upon click....
Open to any kind of help on this one...thanks!
Example of the source table :
player_id------event_type-------score-----
---1-------------------A----------------5----------
---1-------------------A---------------10---------
---1-------------------A----------------5---------
---1-------------------A----------------5---------
---1-------------------A----------------2---------
---1-------------------A----------------15---------
---1-------------------A----------------10---------
---1-------------------C----------------20---------
---1-------------------B----------------5---------
---1-------------------B----------------5---------
---1-------------------B----------------20---------
---2-------------------A----------------50---------
---2-------------------B----------------55---------
Desired output according to this example:
Rank---player_id-------overall_score-----
----1----------2-----------105 POINTS [50 from A(best 5) + 55 from B (best 2)]---------
----2----------1-----------90 POINTS [45 from A(best 5) + 20 from C (best3) + 25 from B (best 2)]---------
First of all: The features you desire are called sliding window and ranking. Oracle implements these with the OVER-keyword and the rank()-function. MySQL does not support these features, so we have to work around this.
I used this answer to create the following query. Give him a +1 too, if this is helpful to you.
SELECT
`player_id`, `event`, `points`,
(SELECT 1 + count(*)
FROM `points`
WHERE `l`.`player_id` = `player_id`
AND `l`.`event` = `event`
AND `points` > `l`.`points`
) AS `rank`
FROM
`points` `l`
This will output for every player_id and event the rank of the points. For example:
Assuming (player_id, event, points) has (1,A,10), (1,A,5), (1,A,2), (1,A,2), (1,A,1), (2,A,0) then the output would be
player_id event points rank
1 A 10 1
1 A 5 2
1 A 2 3
1 A 2 3
1 A 1 5
2 A 0 1
The rank is not dense, so if you have duplicate tuples, you will have output tuples with the same rank as well as gaps in your rank number.
To get the top N* tuples for each player_id and event you could either create a view or use the subquery in the condition. The view is the preferred way, but you don't have the priviledge to create views on many servers.
Creating a view that contains the rank as column.
CREATE VIEW `points_view`
AS SELECT
`player_id`, `event`, `points`,
(SELECT 1 + count(*)
FROM `points`
WHERE `l`.`player_id` = `player_id`
AND `l`.`event` = `event`
AND `points` > `l`.`points`
) as `rank`
FROM
`points` `l`
Get the desired top N results from the view:
SELECT
`player_id`, `event`, `points`
FROM `points_view`
WHERE
`event` = 'A' AND `rank` <= 5
OR
`event` = 'B' AND `rank` <= 2
OR
`event` = 'C' AND `rank` <= 3
Using the rank in the condition
SELECT
`player_id`, `event`, `points`
FROM
`points` `l`
WHERE
(SELECT 1 + count(*)
FROM `points`
WHERE `l`.`player_id` = `player_id`
AND `l`.`event` = `event`
AND `points` > `l`.`points`
) <= N
To further get a different amount of tuples depending on your event, you could do
SELECT
`player_id`, `event`, `points`
FROM
`points` `l`
WHERE
`event` = 'A' AND
(SELECT 1 + count(*)
FROM `points`
WHERE `l`.`player_id` = `player_id`
AND `l`.`event` = `event`
AND `points` > `l`.`points`
) <= 5
OR
`event` = 'B' AND
(SELECT 1 + count(*)
FROM `points`
WHERE `l`.`player_id` = `player_id`
AND `l`.`event` = `event`
AND `points` > `l`.`points`
) <= 2
OR
`event` = 'C' AND
(SELECT 1 + count(*)
FROM `points`
WHERE `l`.`player_id` = `player_id`
AND `l`.`event` = `event`
AND `points` > `l`.`points`
) <= 3
I would just use the maximum of your N's which is 5 and ignore the other tuples for the other event-types as MySQL does not optimize this query which results in 3 separate dependent subqueries. If performance is not an issue or you don't have much data anyways, keep it that way.
* As I explained the rank is not dense, so getting all tuples with rank <= N will generally result in more than N tuples. The additional tuples are duplicates.
Simply removing duplicates is a bad idea as you can see from the example table. If you wanted the top 5 results for player_id = 1 and event = A, you would need both tuples (1,A,2). They both have rank 3. But if you remove one of them, you will only end up with the top 4 results (1,A,10,1), (1,A,5,2), (1,A,2,3), (1,A,1,5).
To get a dense rank you could use this subquery
(SELECT count(DISTINCT `points`)
FROM `points`
WHERE `l`.`player_id` = `player_id`
AND `l`.`event` = `event`
AND `points` >= `l`.`points`
) as `dense_rank`
Be careful as this will still produce duplicate ranks.
Edit
To sum all event's points to one score, use GROUP BY
SELECT
`player_id`, SUM(`points`)
FROM `points_view`
WHERE
`event` = 'A' AND `rank` <= 5
OR
`event` = 'B' AND `rank` <= 2
OR
`event` = 'C' AND `rank` <= 3
GROUP BY `player_id`
ORDER BY SUM(`points`) DESC
Before the partitioning (GROUP BY) the result contains the correct amount of top-scores so you can simply sum all points together.
The big problem you are facing here is that neither rank nor dense_rank will give you the tool get exactly 5 tuples for each player_id and event. For example: If someone got 1000 times 1 point for event A, he will end up with 1000 points as all points will get rank and dense_rank 1.
There is the ROWNUM but again: MySQL does not support this, so we have to emulate this. The problem with ROWNUM is that it will generate a composite numer for all tuples. But we want composite numbers for groups of player_id, event. I'm still working on this solution though.
Edit2
Using this answer I found this solution to work:
select
player_id, sum( points )
from
(
select
player_id,
event,
points,
/* increment current_pos and reset to 0 if player_id or event changes */
#current_pos := if (#current_player = player_id AND
#current_event = event, #current_pos, 0) + 1 as position,
#current_player := player_id,
#current_event := event
from
(select
/* global variable init */
#current_player := null,
#current_event := null,
#current_pos := 0) set_pos,
points
order by
player_id,
event,
points desc
) pos
WHERE
pos.event = 'A' AND pos.position <= 5
OR
pos.event = 'B' AND pos.position <= 2
OR
pos.event = 'C' AND pos.position <= 3
GROUP BY player_id
ORDER BY SUM( points ) DESC
The inner query selects (player_id, event, points)-tuples, sorts them by player_id and event and finally gives each tuple a composite number which is reset to 0 every time either player_id or event changes. Because of the order all tuples with the same player_id will be consecutive. the outer query does the same as the previously used query does with the view.
Edit3 (see comments)
You can create intermediate sums, or different kind of partitions with OLAPs ROLLUP-operator. The query would for example look like this:
select
player_id, event, sum( points )
from
(
select
player_id,
event,
points,
/* increment current_pos and reset to 0 if player_id or event changes */
#current_pos := if (#current_player = player_id AND
#current_event = event, #current_pos, 0) + 1 as position,
#current_player := player_id,
#current_event := event
from
(select
/* global variable init */
#current_player := null,
#current_event := null,
#current_pos := 0) set_pos,
points
order by
player_id,
event,
points desc
) pos
WHERE
pos.event = 'A' AND pos.position <= 5
OR
pos.event = 'B' AND pos.position <= 2
OR
pos.event = 'C' AND pos.position <= 3
GROUP BY player_id, event WITH ROLLUP
/* NO ORDER BY HERE. SEE DOCUMENTATION ON MYSQL's ROLLUP FOR REASON */
The result will now first be grouped by player_id, event, then by only player_id and lastly by null (summing up all rows).
The first groups look like (player_id, event, sum(points)) = {(1, A, 20), (1,B,5)} where 20 and 5 are the sum of the points regarding player_id and event. The second groups look like (player_id, event, sum(points)) = {(1,NULL,25)}. 25 is the sum of all points regarding the player_id. Hope that helps. :-)
You probably need to give the sum(points) a name.
So do:
select player,sum(points) as points from table where event_type = "x" group by player order by points desc limit 5;
(I'd need to see your exact table schema to write this as something you can just drop in, but this is the gist of it)