MySQL order by distinct items first - mysql

I have a table of item possessions which is something like this:
item_possession
===============
id product_id status
1 50 (weapon) available
2 50 (weapon) unavailable
3 10 (shield) unavailable
4 10 (shield) unavailable
5 50 (weapon) available
6 20 (helmet) available
7 20 (helmet) available
8 50 (weapon) available
9 50 (weapon) available
10 30 (thunder) unavailable
11 20 (helmet) available
Note: This is a game and I can own duplicated products (I can sell them), please note that I can have different rows referencing the same item.
Is it possible to order my item possessions listing the distinct item first (I don't care the order, I can just use the table ID) and the duplicates at the end?
Something like this:
item_possession
===============
id product_id status
1 50 (weapon) available
3 10 (shield) unavailable
6 20 (helmet) available
8 30 (thunder) unavailable
2 50 (weapon) unavailable
4 10 (shield) unavailable
5 50 (weapon) available
7 20 (helmet) available
8 50 (weapon) available
9 50 (weapon) available
10 30 (thunder) unavailable
11 20 (helmet) available

or, old school...
SELECT x.*
FROM item_possession x
JOIN item_possession y
ON y.product_id = x.product_id
AND y.id <= x.id
GROUP
BY x.id
ORDER
BY COUNT(*)
, id;
EDIT: Actually, you seem to want this...
SELECT x.*
FROM item_possession x
LEFT
JOIN
( SELECT MIN(id) id FROM item_possession GROUP BY product_id ) y
ON y.id = x.id
ORDER
BY y.id IS NULL,x.id;
For further help, see Why should I provide an MCRE for what seems to me to be a very simple SQL query

You can use window functions for this:
order by row_number() over (partition by product_id order by id)
There is no need to put this in the select. You can also do this using a subquery in older versions:
order by (select count(*)
from item_possession t2
where t2.product_id = t.product_id and t2.id <= t.id
)
Alternatively, you can use variables -- which requires sorting twice:
select ip.*
from (select ip.*,
(#rn := if(#p = ip.product_id, #rn + 1,
if(#p := ip.product_id, 1, 1)
)
) as rn
from (select ip.* from item_possession ip order by ip.product_id, ip.id
) ip cross join
(select #p := -1, #rn := 0) params
) ip
order by rn, id;

You can use row_number()
select t.*, row_number() over (partition by product_id order by id) as seq
from table t
order by seq, id;

Related

MySQL Leaderboard Table

I'm trying to figure out how to Select a specific number of rows from a MySQL table based on WHERE clause. I have a table with 10 dummy users, I want to get 2 previous and 2 next users of specific user with their ranks.
user_id | points
==================
10 200
4 130
2 540
13 230
15 900
11 300
3 600
17 110
20 140
1 430
5 800
I achieved adding a column for ranking like:
user_id | points | rank
===========================
15 900 1
5 800 2
3 600 3
2 540 4
1 430 5
11 300 6
13 230 7
10 200 8
20 140 9
4 130 10
17 110 11
But the problem is that I want only 5 rows. Suppose I'm retrieving data for user with user_id = 11. The output should look like this:
user_id | points | rank
===========================
2 540 4
1 430 5
11 300 6
13 230 7
10 200 8
where user_id = 11 is in the centre with 2 rows above and 2 below. I have tried nesting UNIONS and SELECT statements but nothing seems to work properly.
Here's a suggestion if you're on MySQL 8+:
WITH cte AS (
SELECT user_id, points,
ROW_NUMBER() OVER (ORDER BY points DESC) AS Rnk
FROM mytable)
SELECT cte2.user_id,
cte2.points,
cte2.Rnk
FROM cte cte1
JOIN cte cte2
ON cte1.user_id=11
AND cte2.Rnk >= cte1.Rnk-2
AND cte2.Rnk <= cte1.Rnk+2
Using common table expression (cte) then do a self join with condition of user_id=11 as base to get the Rnk value of -2 and +2.
Demo fiddle
Since you're on older MySQL version, here's what I can suggest:
SET #uid := 11;
SET #Rnk := (SELECT Rnk
FROM
(SELECT user_id, points,
#r := #r+1 AS Rnk
FROM mytable
CROSS JOIN (SELECT #r := 0) r
ORDER BY points DESC) v
WHERE user_id = #uid);
SELECT user_id, points, Rnk
FROM
(SELECT user_id, points,
#r := #r+1 AS Rnk
FROM mytable
CROSS JOIN (SELECT #r := 0) r
ORDER BY points DESC) v
WHERE Rnk >= #Rnk-2
AND Rnk <= #Rnk+2;
If you will only use user_id as base, then the only part here you need to change is the SET #uid. The remaining queries are just fulfilling your condition of getting two positions above and below the rank retrieved according to the user_id. The base query in SET #Rnk is the same as the base query for the last one. The idea is to assign #Rnk variable with Rnk position of user_id=11 then use it in WHERE condition for the last query.
I'm not aware if there's any online fiddle still using MySQL 5.1 but here's probably the closest version to it, MySQL 5.5 demo fiddle.

"SELECT id, title, #natusort:=#natusort + 1 AS ordercount" does not increment as expected

My query in MySQL does not behave as expected.
SET #natusort := 0;
SELECT id, title, #natusort:=#natusort + 1 AS ordercount
FROM categories
JOIN table1 ON id = table1.parentid
ORDER BY title LIMIT 10
I expected a set of results like this:
ID title ordercount
------------------------------------
67 aaa 1
23 aab 2
65 aac 3
47 aad 4
78 aba 5
32 abc 6
43 abd 7
33 aca 8
46 acb 9
12 acd 10
But I got this set instead:
ID title ordercount
------------------------------------
67 aaa 12
23 aab 3
65 aac 12
47 aad 34
78 aba 4
32 abc 36
43 abd 31
33 aca 15
46 acb 19
12 acd 50
How can I get the increment to work sequentially starting from 1 and follow the order by?
You can use ROW_NUMBER(), as in:
SELECT id, title,
row_number() over(order by title) as ordercount
FROM categories
JOIN table1 ON id = table1.parentid
ORDER BY title
LIMIT 10
What appears to be happening here is that first your sequence is being generated across the result set, and then you are limiting to 10 records based on some order. What you're left with isn't necessarily a sequence from 1 to 10. The best fix here might be to use ROW_NUMBER, if you are using MySQL 8+. If you must stick with your current approach, then wrap in a subquery before generating the sequence:
SELECT id, title, #natusort:=#natusort + 1 AS ordercount
FROM
(
SELECT id, title
FROM categories
INNER JOIN table1 ON id = table1.parentid
ORDER BY title
LIMIT 10
) t
ORDER BY title;
For the ROW_NUMBER option, just change your select to:
SELECT id, title, ROW_NUMBER() OVER (ORDER BY title) AS ordercount
FROM categories
...
You should use row_number() in MySQL 8+.
The issue you are having is that ORDER BY and GROUP BY are not compatible with variables in more recent versions of MySQL pre-8.0. I don't remember exactly when this stopped working, but I have in mind GROUP BY stopped working in 5.6 and ORDER BY in 5.7. I wish I could forget such trivia.
In any case, the solution is to order in a subquery:
SELECT tc.*, (#natusort := #natusort + 1) AS ordercount
FROM (SELECT id, title
FROM categories c JOIN
table1 t1
ON c.id = t1.parentid
ORDER BY title
) tc CROSS JOIN
(SELECT #natusort := 0) params
ORDER BY title
LIMIT 10;
Note that I've included the initialization of #natusort in the same query, so only one statement is necessary.
If using SET #natusort := 0at the beginning is not working then you can Initialize it using joins example :
SELECT id, title, (#natusort:=#natusort + 1) AS ordercount
FROM categories
JOIN table1 ON id = table1.parentid
inner join (SELECT #natusort := 0)
ORDER BY title LIMIT 10

MySQL group by with max value

Hi I have this table.
id lat lng userId
1 12 23 1
2 45 34 2
3 42 34 3
4 33 34 1
5 36 79 2
6 53 98 2
7 23 90 3
8 23 67 1
Here we have three users. (user ids 1,2,3). I want to get lateset record (id column max value) of each user.
My excepted output is this
userId lat lng
1 23 67
2 53 98
3 23 90
This query will give me group by option
SELECT
*
FROM
covid.locations
GROUP BY userId;
But how do I combine this with MAX(id) function.
One way is to use the following:
SELECT
cl.*
FROM covid.locations cl
INNER JOIN (
SELECT
userid
, MAX( id ) mid
FROM covid.locations
GROUP BY
userId
) g ON cl.userid = g.userid
AND cl.id = cl.mid
Another is to use row_number() over()
SELECT
userId
, lat
, lng
FROM (
SELECT
*
, ROW_NUMBER() OVER (PARTITION BY userid ORDER BY id DESC) rn
FROM covid.locations
GROUP BY
userId
) d
WHERE rn = 1
Both will identify the "most recent" row in the source table based in the id column of that table. Note that the second query requires MySQL version 8+ as this is when row_number() became supported in that database. The first query should run in dbms supporting SQL.
This will do
SELECT
*
FROM
covid.locations
where id in (select max(t.id) from covid.locations t group by t.userId)
order by id desc;
An example of the above query can be found in this SQLFiddle

Mysql query that can search with a criteria of total summation of an object

hello please help me with this mysql query,
i want to select the rows that have a summation of 17 or less when the quantity columns is added together and the searched item is Pen in an ascending order based on id.
My table structure is like this:
ID quantity item
------------------
1 5 Pen
2 3 Pen
3 10 Books
4 7 Pen
5 4 Pen
6 8 Pen
7 1 Rubber
so i want my output to be like this, because when the quantity is added together for the pen that can be equal to 17, it will have the below rows
ID quantity item
------------------
1 5 Pen
2 3 Pen
4 7 Pen
5 4 Pen
Thank you in advance.
I am guessing that you have a threshold of 17 and want the maximum number of rows that do not exceed the threshold. If so, then a cumulative sum is the right approach, and variables are the most efficient method in MySQL:
select t.*
from (select t.*, (#sum := #sum + quantity) as sumq
from t cross join
(select #sum := 0) params
where item = 'Pen'
order by id
) t
where sumq <= 17;
Here's one idea... slower than Gordon's but (for the time being at least), more accurate...
SELECT DISTINCT a.*
FROM my_table a
JOIN
( SELECT x.*
, SUM(y.quantity) total
FROM my_table x
JOIN my_table y
ON y.item = x.item AND y.id <= x.id
WHERE x.item = 'pen'
GROUP
BY x.id
HAVING 17 > SUM(y.quantity)-x.quantity
AND 17 <= SUM(y.quantity)
) b
ON b.item = a.item
AND b.id >= a.id;

How to get rank in MySQL from 2 tables?

I have 2 different tables in my database by the name of: rank, settings.
Here is how each table looks like with a few records in them:
Table #rank:
id points userid
-- ----- ------
1 500 1
2 300 2
3 900 3
4 1500 4
5 100 5
6 700 6
7 230 7
8 350 8
9 850 9
10 150 10
Table #settings:
userid active
------ ------
1 0
2 1
3 1
4 1
5 1
6 0
7 1
8 1
9 0
10 1
I want to get the rank of a specific user by user_id from the rank table ordering by their points. Also I would Only want to include the users in the ranking results, if they have active = 1 set in the settings table.
I have a simple ranking query, but it is not really effective, because it does include everyone even if the user is not active:
SELECT * FROM
(SELECT #sort:=#sort+1 AS sort, points, userid
FROM rank,
(SELECT #sort := 0) s
ORDER BY points DESC) t
WHERE userid= 8
Any idea, how could I achieve my goals here?
Few sub queries. First gets all the users who are active in the right order. That is used as a source for another query to add the rank. Then this is used as the source for the points and rank for the userid you are actually interested in
SELECT sort, points
FROM
(
SELECT #sort:=#sort + 1 AS sort, points, userid
FROM
(
SELECT rank.points, rank.userid
FROM rank
INNER JOIN settings
ON rank.userid = settings.userid
WHERE settings.active = 1
ORDER BY points DESC
) sub0
CROSS JOIN (SELECT #sort:=0) sub2
) sub1
WHERE sub1.userid = 8
Borrowing the idea from: https://stackoverflow.com/a/4474389/92063
SELECT
#rn:=#rn+1 AS RANK
,USER_ID
,POINTS
FROM (
SELECT
R.userid AS USER_ID
,R.points AS POINTS
FROM
rank R
INNER JOIN
settings S
ON R.userid = S.userid
WHERE
S.active = 1
ORDER BY
R.points DESC
) t1, (SELECT #rn:=0) t2;