MySQL select result row numbers - mysql

I have a table which contains users and some scores associated with them. something like this:
uid | username | score | time_spent
1 | test | 25 | 12
then I am sorting this table based on score and time_spent. As a result I get some kind of highscores table.
what I want to do is to assign row numbers to this sorted table to have the information about the specific users place in the highscores table and then select a specific user from this sorted table with row number.
I tried to do it like this:
SET #row_number = 0;
SELECT * FROM
(SELECT uid, username, score, time_spent, #row_number:=#row_number+1 AS row_number,
SUM(score) AS points_awarded,
MIN(time_spent) AS time
FROM results
GROUP BY uid
ORDER BY points_awarded DESC, time ASC) as t
WHERE t.uid=1
but this does not work correctly. The result row I get has always the last number of total records.

You must have the #row_number in the outer query:
SET #row_number = 0;
SELECT
t.*, #row_number:=#row_number+1 AS row_number
FROM (
SELECT
uid, username,
SUM(score) AS points_awarded,
MIN(time_spent) AS time
FROM results
GROUP BY uid, username
) t
ORDER BY t.points_awarded DESC, t.time ASC
See the demo.
INSERT INTO results
(`uid`, `username`, `score`, `time_spent`)
VALUES
('1', 'test1', '25', '12'),
('1', 'test1', '20', '13'),
('1', 'test1', '20', '11'),
('2', 'test2', '12', '17'),
('2', 'test2', '29', '16'),
('2', 'test2', '25', '15'),
('3', 'test3', '45', '18'),
('3', 'test3', '15', '69');
Results:
| uid | username | points_awarded | time | row_number |
| --- | -------- | -------------- | ---- | ---------- |
| 2 | test2 | 66 | 15 | 1 |
| 1 | test1 | 65 | 11 | 2 |
| 3 | test3 | 60 | 18 | 3 |

If you only want the position of a single user at a time, the following should work:
-- get best score and time for the user
SELECT score, time_spent
INTO #u_score, #u_time
FROM results
WHERE uid = 2
ORDER BY score DESC, time_spent ASC
LIMIT 1;
SELECT *, -- below: count "better" distinct users
(SELECT COUNT(DISTINCT uid)+1 FROM results WHERE score > #u_score
OR (score = #u_score AND time_spent < #u_time)) AS pos
FROM results
WHERE uid = 2
AND score = #u_score
AND time_spent = #u_time;
EDIT: The request below should give you the complete "leaderboard", which you can then use as subquery from to get a specific user, like you did in your example:
SET #row_number = 0;
SELECT t.*, #row_number:=#row_number+1 AS row_number
FROM (
SELECT r1.*
FROM results r1
LEFT JOIN results r2
ON r1.uid = r2.uid
AND (r1.score < r2.score
OR (r1.score = r2.score
AND r1.time_spent > r2.time_spent))
WHERE r2.uid IS NULL
ORDER BY r1.score DESC, r1.time_spent ASC
) AS t
EDIT2: I assumed each row in your table was a separate score "attempt" and that you wanted to take into consideration the best attempt of each user, but it looks like you want the sum of these scores, so forpas's answer is the one you want :)

Related

Finding the 3rd merchant with the highest lifetime transaction amount

I am trying to write a query to get the 3rd merchant with the highest lifetime
transaction amount. Also, I have to provide the total transactions to date for this
merchant.
This is the create table statement.
CREATE TABLE transaction(
transaction_id int , user_id int , merchant_name varchar(255), transaction_date date , amount int
);
INSERT INTO transaction(transaction_id, user_id, merchant_name, transaction_date, amount)
VALUES (1, 1 ,'abc', '2015-01-17', 100),(2, 2, 'ced', '2015-2-17', 100),(3, 1, 'def', '2015-2-16', 120),
(4, 1 ,'ced', '2015-3-17', 110),(5, 1, 'ced', '2015-3-17', 150),(6, 2 ,'abc', '2015-4-17', 130),
(7, 3 ,'ced', '2015-12-17', 10),(8, 3 ,'abc', '2015-8-17', 100),(9, 2 ,'abc', '2015-12-17', 140),(10, 1,'abc', '2015-7-17', 100),
(11, 1 ,'abc', '2015-01-17', 120),(12, 2 ,'ced', '2015-12-23', 130);
I am not sure how the o/p would look like. I am stuck here.
SELECT distinct(merchant_name), max(amount) from transaction
For MySQL, we could do this:
CREATE TABLE transaction (
transaction_id int , user_id int , merchant_name varchar(255), transaction_date date , amount int
);
INSERT INTO transaction(transaction_id, user_id, merchant_name, transaction_date, amount)
VALUES (1, 1 ,'abc', '2015-01-17', 100),(2, 2, 'ced', '2015-2-17', 100),(3, 1, 'def', '2015-2-16', 120),
(4, 1 ,'ced', '2015-3-17', 110),(5, 1, 'ced', '2015-3-17', 150),(6, 2 ,'abc', '2015-4-17', 130),
(7, 3 ,'ced', '2015-12-17', 10),(8, 3 ,'abc', '2015-8-17', 100),(9, 2 ,'abc', '2015-12-17', 140),(10, 1,'abc', '2015-7-17', 100),
(11, 1 ,'abc', '2015-01-17', 120),(12, 2 ,'ced', '2015-12-23', 130)
;
SELECT merchant_name
, SUM(amount) AS sum_amount
FROM transaction
GROUP BY merchant_name
ORDER BY sum_amount DESC
LIMIT 2, 1
;
Result:
+---------------+------------+
| merchant_name | sum_amount |
+---------------+------------+
| def | 120 |
+---------------+------------+
The full result without limiting, for comparison, is:
+---------------+------------+
| merchant_name | sum_amount |
+---------------+------------+
| abc | 690 |
| ced | 500 |
| def | 120 |
+---------------+------------+
Understanding the meaning of "lifetime transaction", playing with orders and limits, and two levels of aggregation, you can get what you want:
select t.merchant_name, count(*)
from transactions t
join (
select merchant_name, sum(amount) amount
from transactions
group by merchant_name
order by 2 desc
limit 3
) top3
on t.merchant_name = top3.merchant_name
group by t.merchant_name
order by top3.amount asc
limit 1
You can test on this db<>fiddle
If you had a "merchant" dimension, you could get the merchant_name from there and perform the sum and count in the same subquery, improving this way the query performance. Something like this...
select m.merchant_name, top3.amount, top3.nbtrans
from merchants m
join (
select merchant_name, sum(amount) amount, count(1) as nbtrans
from transactions
group by merchant_name
order by 2 desc
limit 3
) top3
on t.merchant_name = top3.merchant_name
order by top3.amount asc
limit 1

MySQL: Returning rows which are matching the conditions: 1. Dates are ascending and 2. Views are descending

I have a table (tab1) which contains id, trending_date and views coulmns. I have to write a query to return those records whose views are decreasing as the trending_dates are going by. E.g. -
|ID |Trending_Date | Views |
|---|--------------|-------|
|A |2021-01-03 | 10 |
|B |2020-10-30 | 8 |
|A |2021-02-05 | 9 |
|B |2020-11-02 | 11 |
My intended query will return only the records having "A" as ID. Need help on developing this query...
Thanks,
Amitava
If you want only the IDs in the results then aggregate and use GROUP_CONCAT() in the HAVING clause:
SELECT ID
FROM tablename
GROUP BY id
HAVING COUNT(*) = COUNT(DISTINCT Views)
AND GROUP_CONCAT(Views ORDER BY Views DESC) = GROUP_CONCAT(Views ORDER BY Trending_Date)
GROUP_CONCAT(Views ORDER BY Views DESC) sorts the Views of each ID descending and GROUP_CONCAT(Views ORDER BY Trending_Date) sorts the the Views of each ID by Trending_Date ascending.
If the 2 results are the same this means that Views are monotonically decreasing.
The condition COUNT(*) = COUNT(DISTINCT Views) filters out IDs with duplicate Views.
If you want full rows of the table use the above query with the operator IN:
SELECT *
FROM tablename
WHERE id IN (
SELECT ID
FROM tablename
GROUP BY id
HAVING COUNT(*) = COUNT(DISTINCT Views)
AND GROUP_CONCAT(Views ORDER BY Views DESC) = GROUP_CONCAT(Views ORDER BY Trending_Date)
)
See the demo.
With MySQL 8 you can use the window function LAG
I added also a version for MySQL 5.x
CREATE TABLE tab1 (
`ID` VARCHAR(1),
`Trending_Date` VARCHAR(10),
`Views` INTEGER
);
INSERT INTO tab1
(`ID`, `Trending_Date`, `Views`)
VALUES
('A', '2021-01-03', '10'),
('B', '2020-10-30', '8'),
('A', '2021-02-05', '9'),
('B', '2020-11-02', '11');
SELECT ID,`Trending_Date`,`Views`
FROM
(SELECT ID,`Trending_Date`,
`Views` ,
LAG(`Views`, 1) OVER (
PARTITION BY `ID`
ORDER BY `Trending_Date`
) lastview
FROM tab1) t1
WHERE `Views` < lastview
ID | Trending_Date | Views
:- | :------------ | ----:
A | 2021-02-05 | 9
SELECT ID,`Trending_Date`,`Views`
FROM
(SELECT
`Trending_Date`,
IF (#id <> ID,#views := 0,#views := #views) idchange,
IF (#views > `Views`,1,0) smaller,
#views := `Views` as Views,
#id := ID as ID
FROM tab1,(SELECT #views := 0,#id := '') t1
ORDER BY ID,`Trending_Date`) t2
WHERE smaller = 1;
ID | Trending_Date | Views
:- | :------------ | ----:
A | 2021-02-05 | 9
db<>fiddle here
I have added two queries. First one with window function for MySQL 8.0 and second one is using subquery for older version of MySQL.
CREATE TABLE tab1 (
`ID` VARCHAR(1),
`Trending_Date` VARCHAR(10),
`Views` INTEGER
);
INSERT INTO tab1
(`ID`, `Trending_Date`, `Views`)
VALUES
('A', '2021-01-03', '10'),
('B', '2020-10-30', '8'),
('A', '2021-02-05', '9'),
('B', '2020-11-02', '11');
Query#1 (For MySQL 8.0)
select id
from
(
select id,trending_date,views,max(trending_date)over(partition by id)maxtdate,
min(trending_date)over(partition by id)mintdate
from tab1
)t
group by id
having max(case when trending_date=maxtdate then views end) < max(case when trending_date=mintdate then views end)
Output:
id
A
Query#2 (For older version of MySQL)
select id
from
(
select id,trending_date,views,(select max(trending_date)from tab1 t2 where t1.id=t2.id)maxtdate,
(select min(trending_date)from tab1 t2 where t1.id=t2.id)mintdate
from tab1 t1
)t
group by id
having max(case when trending_date=maxtdate then views end) < max(case when trending_date=mintdate then views end)
Output:
id
A
db<fiddle here

How to get the highest date for each register after left join in mysql?

I have two tables (user and log) in mysql.
USER TABLE
id email name status
-- ------- ------ --------
1 "x#domain.com" "Carlos" 1
2 "c#domain.com" "Marie" 1
3 "k#domain.com" "Jason" 1
LOG TABLE
id time user_id
-- ------- -------
123 "2020-09-07 08:05:03" 1
124 "2020-09-07 08:32:21" 2
125 "2020-09-09 09:01:46" 1
126 "2020-09-07 11:05:03" 3
I would like to get all the users and its last log in for each one. Then, I have this query:
SELECT
user.id,
user.name,
user.email,
MAX(log.time) as time
FROM user
LEFT JOIN log on user.id = log.user_id
WHERE user.status_id = 1
GROUP BY user.id
ORDER BY log.time DESC
The query returns all the users with its log (null if there is no log for the user) but the log is not the last of the user. May somebody help me?
Thanks in advance.
There is very little you need to change to make your query work (apart from two typos - extra comma and status vs status_id):
The order by clause does not make any sense, since the resultset does not have log.time field, it only has max(log.time), so that's you need to order on and no need to use CTEs or subqueries - as long as you do not want to include additional columns from the log table:
Schema (MySQL v5.7)
CREATE TABLE `user` (
`id` INTEGER primary key,
`email` VARCHAR(14),
`name` VARCHAR(8),
`status` INTEGER
);
INSERT INTO `user`
(`id`, `email`, `name`, `status`)
VALUES
('1', 'x#domain.com', 'Carlos', '1'),
('2', 'c#domain.com', 'Marie', '1'),
('3', 'k#domain.com', 'Jason', '1');
CREATE TABLE log (
`id` INTEGER,
`time` VARCHAR(21),
`user_id` INTEGER
);
INSERT INTO log
(`id`, `time`, `user_id`)
VALUES
('123', '2020-09-07 08:05:03', '1'),
('124', '2020-09-07 08:32:21', '2'),
('125', '2020-09-09 09:01:46', '1'),
('126', '2020-09-07 11:05:03', '3');
Query #1
SELECT
user.id,
user.name,
user.email,
MAX(log.time) as time
FROM user
LEFT JOIN log on user.id = log.user_id
WHERE user.status = 1
GROUP BY user.id
ORDER BY max(log.time) DESC;
Result:
| id | name | email | time |
| --- | ------ | ------------ | ------------------- |
| 1 | Carlos | x#domain.com | 2020-09-09 09:01:46 |
| 3 | Jason | k#domain.com | 2020-09-07 11:05:03 |
| 2 | Marie | c#domain.com | 2020-09-07 08:32:21 |
If you are using MySQL 8+, then ROW_NUMBER is one sensible approach here:
WITH cte AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY time DESC) rn
FROM log
)
SELECT
u.id,
u.name,
u.email,
t.time
FROM user u
LEFT JOIN cte t ON u.id = t.user_id
WHERE
u.status = 1 AND t.rn = 1
ORDER BY
t.time DESC;
SELECT u.*, l.*
FROM user
JOIN log ON user.id = log.user_id
JOIN ( SELECT user_id, MAX(time) time
FROM log
GROUP BY user_id ) log1 ON log.user_id = log1.user_id AND log.time = log1.time
Applicable for MySQL 5.x

Select last distinct records in mysql

I have a table in mysql have a lot of data.i want to display only distinct and last saved data...here is what i have
CREATE TABLE amounts(
id MEDIUMINT NOT NULL AUTO_INCREMENT,
bank_id INT,
amount INT,
PRIMARY KEY (id)
) ENGINE=MyISAM;
INSERT INTO `amounts` (`id`, `bank_id`, `amount`) VALUES (NULL, '1', '100'),
(NULL, '2', '200'),(NULL, '3', '300'),(NULL, '1', '500'),(NULL, '2', '600'),
(NULL, '1', '800'),(NULL, '2', '50');
I want result like this
bank_id amount
1 800
2 50
3 300
how will be select query for this?
Use a self join. This is almost always faster than a subquery.
SELECT a.*
FROM amounts a LEFT JOIN amounts b ON a.bank_id = b.bank_id AND a.id < b.id
WHERE b.id IS NULL
See a demo
This should do it:
SELECT a.bank_id,
b.amount
FROM (SELECT bank_id,
Max(id) AS id
FROM amounts
GROUP BY bank_id) a
INNER JOIN amounts b
ON b.id = a.id
Result
| BANK_ID | AMOUNT |
--------------------
| 1 | 800 |
| 2 | 50 |
| 3 | 300 |
See a demo
You could use a subquery where you select max IDs for every bank_id, then you select all rows whose IDs are returned by this subquery:
SELECT amounts.*
FROM amounts
WHERE id IN (select max(id)
from amounts
group by bank_id)
ORDER BY bank_id
Please see this fiddle.

MySQL Ordering a query - further question

Further to a recently answered question, I have the following code:
SELECT q21coding, COUNT(q21coding) AS Count
FROM tresults_acme
WHERE q21 IS NOT NULL AND q21 <> ''
GROUP BY q21coding
ORDER BY IF(q21coding = 'Other', 1, 0) ASC, Count DESC
It brings back the following:
q21coding Count
Difficulty in navigating/finding content 53
Positive comments 28
Suggestions for improvement 14
Inappropriate content/use 13
Improve search facility 6
Include information about staff and teams 5
Content needs updating 4
Other 30
You'll notice that Other is now at the bottom - However is there a way of ensuring that Positive comments and Other is ALWAYS the bottom two (with other at the bottom) regardless of the Count size?
Thanks,
Homer
Actually there was no need to use IF(q21coding = 'Other', 1, 0) in your original query. In MySQL you can use any expression in the ORDER BY caluse and q21coding = 'Other' would have been enough:
... ORDER BY q21coding = 'Other', Count DESC
The q21coding = 'Other' expression will return 1 if true, or 0 if false. That will put rows with a q21coding = 'Other' at the bottom.
What you need to do to have 'Positive Comments' and 'Other' both at the bottom is something like this:
... ORDER BY q21coding = 'Other', q21coding = 'Positive comments', Count DESC
Basic test case:
CREATE TABLE my_table (id int, q21coding varchar(100), count int);
INSERT INTO my_table VALUES (1, 'Inappropriate content/use', 13);
INSERT INTO my_table VALUES (2, 'Other', 30);
INSERT INTO my_table VALUES (3, 'Difficulty in navigating/finding content', 53);
INSERT INTO my_table VALUES (4, 'Positive comments', 28);
INSERT INTO my_table VALUES (5, 'Improve search facility', 6);
INSERT INTO my_table VALUES (6, 'Content needs updating', 4);
INSERT INTO my_table VALUES (7, 'Suggestions for improvement', 14);
INSERT INTO my_table VALUES (8, 'Include information about staff and teams', 5);
Result:
SELECT q21coding, count
FROM my_table
ORDER BY q21coding = 'Other', q21coding = 'Positive comments', Count DESC;
+-------------------------------------------+-------+
| q21coding | count |
+-------------------------------------------+-------+
| Difficulty in navigating/finding content | 53 |
| Suggestions for improvement | 14 |
| Inappropriate content/use | 13 |
| Improve search facility | 6 |
| Include information about staff and teams | 5 |
| Content needs updating | 4 |
| Positive comments | 28 |
| Other | 30 |
+-------------------------------------------+-------+
8 rows in set (0.00 sec)