Finding the 3rd merchant with the highest lifetime transaction amount - mysql

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

Related

MYSQL - how to select with group by and having

I have this schema:
table = users
user_id - integer
user_name - string
table = transaction
transaction_id - string
user_id - integer
this sample data:
user_id - user_name
2 - Jhon
3. - barry
transaction_id - user_id
19123 - 2
20123 - 2
20124 - 2
21123 - 2
I need to get how many transactions the user jhon did in 19 and 20 only
the first 2 digits from the transaction_id is the year, but I can't seem to group them, what I have is:
select u.user_name
from transaction t join users u on u.user_id = t.user_id
group by (substr(t.transaction_id, 1, 2))
where <I have no idea in how to fill this>
what I want as a result is:
jhon 1 2
1 transction in 19
2 transactions in 20
It would be better to save dates in mysql way 2022-12-31, so you cqan use date function withput converting.
You need to GROUP By Nam,ame and the first 2 digits
And the WHERE clause belongs always before the group by
CREATE TABLE users (
`user_id` INTEGER,
`user_name` VARCHAR(5)
);
INSERT INTO users
(`user_id`, `user_name`)
VALUES
('2', 'Jhon'),
('3.', 'barry');
CREATE TABLE transaction (
`transaction_id` INTEGER,
`user_id` INTEGER
);
INSERT INTO transaction
(`transaction_id`, `user_id`)
VALUES
('19123', '2'),
('20123', '2'),
('20124', '2'),
('21123', '2');
select u.user_name, Count(*)
from transaction t join users u on u.user_id = t.user_id
where u.user_name = 'Jhon' AND (substr(t.transaction_id, 1, 2)) IN (19,20)
group by u.user_name,(substr(t.transaction_id, 1, 2))
user_name | Count(*)
:-------- | -------:
Jhon | 1
Jhon | 2
select u.user_name
, SUM((substr(t.transaction_id, 1, 2)) = 19) As `Count_19`
, SUM((substr(t.transaction_id, 1, 2)) = 20) As `Count_20`
from transaction t join users u on u.user_id = t.user_id
where u.user_name = 'Jhon' AND (substr(t.transaction_id, 1, 2)) IN (19,20)
group by u.user_name
user_name | Count_19 | Count_20
:-------- | -------: | -------:
Jhon | 1 | 2
db<>fiddle here

grouping records by a field and submit a query on each group in sql

I have a table like this:
create table product_company (
id int PRIMARY KEY,
productName varchar(100),
companyName varchar(100),
price int
);
I want to know the name of the product which it has the second rank in price in each company.
for example if company1 has three product product1=30, product2=50 and product3=15(the assignment shows the price of each product in this company) so product1 has the second rank in price property in company1 and I want to write a query that returns something like below:
company1 product1
company2 ...
...
I mean for every company I want to know the product that has the second rank in price within that company.
I don't know how to use group by clause because group by is working fine by aggregate functions but I don't want the maximum in price.
I want to write this query with standard sql queries and clauses and without some special funcions that may not work in some DBMS
If you are running MySQL 8.0, you can use window function dense_rank():
select *
from (
select
pc.*,
dense_rank() over(partition by companyName order by price desc) rn
from product_company pc
) t
where rn = 2
In earlier versions, one solution is to filter with a correlated subquery. But you have to be careful to properly handle possible top ties. This should do it:
select pc.*
from product_company pc
where (
select count(distinct pc1.price)
from product_company pc1
where pc1.companyName = pc.companyName and pc1.price > pc.price
) = 1
An EXISTS with a COUNT can also be used for this
For example:
create table product_company (
id int PRIMARY KEY AUTO_INCREMENT,
productName varchar(100),
companyName varchar(100),
price decimal(16,2)
);
insert into product_company
(productName, companyName, price) values
('product 1', 'odd org', 9)
,('product 2', 'odd org', 15)
,('product 3', 'odd org', 11)
,('product 4', 'odd org', 17)
,('product 5', 'even inc.', 18)
,('product 6', 'even inc.', 12)
,('product 7', 'even inc.', 16)
,('product 8', 'even inc.', 14)
;
select *
from product_company t
where exists
(
select 1
from product_company t2
where t2.companyName = t.companyName
and t2.price >= t.price
having count(distinct t2.price) = 2
)
id | productName | companyName | price
-: | :---------- | :---------- | ----:
2 | product 2 | odd org | 15.00
7 | product 7 | even inc. | 16.00
db<>fiddle here
And if you want to have the top 2 per company?
Then change the HAVING clause
...
having count(distinct t2.price) <= 2
...

MySQL select result row numbers

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 :)

MySQL Fetch Result Using Multiple Joins in one query?

I have below tables
tbl_user
uid first_name last_name email_id
1 steve martin steve1#gmail.com
2 mark lee mark1#gmail.com
3 nelson wise nelson23#gmail.com
tbl_tier
tier_id tier_name points_required
1 Silver 100
2 Gold 200
3 Platinum 300
tbl_tier_earned
id tier_id uid
1 1 1
2 2 1
3 3 1
4 1 2
5 2 2
6 1 3
I need unique users with their current tiers like:
first_name last_name email_id current_tier
steve martin steve1#gmail.com Platinum
mark lee mark1#gmail.com Gold
I have tried below query but it gives me only 1 result:
SELECT u.first_name,u.last_name,u.email_id, t.tier_name
FROM tbl_tier_earned AS tte
INNER JOIN tbl_user AS u
ON u.uid = tte.uid
INNER JOIN tbl_tier AS t
ON tte.tier_id = t.tier_id
WHERE u.email_id!=""
ORDER BY t.points_required DESC LIMIT 0,1
How can I retrieve above data using mysql query?
It appears that the current tier is given by the max tier_id in the tbl_tier_earned table, for each user. One approach here would be to join the user table to a subquery on the tbl_tier_earned table which finds the max tier.
SELECT
u.first_name,
u.last_name,
u.email_id,
COALESCE(t2.tier_name, 'NA') AS current_tier
FROM tbl_user u
LEFT JOIN
(
SELECT uid, MAX(tier_id) AS max_tier_id
FROM tbl_tier_earned
GROUP BY uid
) t1
ON u.uid = t1.uid
LEFT JOIN tbl_tier t2
ON t1.max_tier_id = t2.tier_id;
SQL Fiddle
MySQL 5.6 Schema Setup:
CREATE TABLE tbl_user
(`uid` int, `first_name` varchar(6), `last_name` varchar(6), `email_id` varchar(18))
;
INSERT INTO tbl_user
(`uid`, `first_name`, `last_name`, `email_id`)
VALUES
(1, 'steve', 'martin', 'steve1#gmail.com'),
(2, 'mark', 'lee', 'mark1#gmail.com'),
(3, 'nelson', 'wise', 'nelson23#gmail.com')
;
CREATE TABLE tbl_tier
(`tier_id` int, `tier_name` varchar(8), `points_required` int)
;
INSERT INTO tbl_tier
(`tier_id`, `tier_name`, `points_required`)
VALUES
(1, 'Silver', 100),
(2, 'Gold', 200),
(3, 'Platinum', 300)
;
CREATE TABLE tbl_tier_earned
(`id` int, `tier_id` int, `uid` int)
;
INSERT INTO tbl_tier_earned
(`id`, `tier_id`, `uid`)
VALUES
(1, 1, 1),
(2, 2, 1),
(3, 3, 1),
(4, 1, 2),
(5, 2, 2),
(6, 1, 3)
;
Query 1:
SELECT c.first_name, c.last_name, c.email_id,
(SELECT tier_name from tbl_tier WHERE points_required = max(b.points_required)) as current_tier
FROM tbl_tier_earned a
INNER JOIN tbl_tier b ON a.tier_id = b.tier_id
INNER JOIN tbl_user c ON a.uid = c.uid
GROUP BY c.first_name, c.last_name, c.email_id
Results:
| first_name | last_name | email_id | current_tier |
|------------|-----------|--------------------|--------------|
| mark | lee | mark1#gmail.com | Gold |
| nelson | wise | nelson23#gmail.com | Silver |
| steve | martin | steve1#gmail.com | Platinum |

(mysql) Select 50 highest rated items, with at most one item coming from each user

I'm not sure how to go about doing this efficiently in MySQL and would appreciate any help.
The goal is to select 50 of the top-selling items, with at most one item from each user. I'm used to doing this with either CTE's or DISTINCT ON, but of course that's not an option in MySQL. I'm hoping for a single-query solution, and I'd like to avoid using stored procedures.
The basic schema is a table of items posted by users, and a table of sales with a field determining the score of that particular sale.
CREATE TABLE items (
item_id INT PRIMARY KEY,
user_id INT NOT NULL
)
CREATE TABLE sales (
item_id INT NOT NULL,
score INT NOT NULL
)
-- Create some sample data
INSERT INTO items VALUES (1, 1), (2, 1), (3, 1), (4, 2), (5, 2), (6, 3), (7, 3);
INSERT INTO sales VALUES (1, 1), (1, 1), (2, 1), (3, 2), (3, 1), (4, 3), (4, 2), (5, 2), (6, 1), (6, 1), (6, 1), (7, 2);
The result of the query against this sample data should be
+---------+---------+-------------+
| user_id | item_id | total_score |
+---------+---------+-------------+
| 2 | 4 | 5 |
| 1 | 3 | 3 |
| 3 | 6 | 3 |
+---------+---------+-------------+
Here's the PostgreSQL solution:
SELECT DISTIN ON (items.user_id)
items.user_id,
items.item_id,
SUM(sales.score) AS total_score
FROM items
JOIN sales ON (sales.item_id = items.item_id)
GROUP BY items.item_id
ORDER BY total_score DESC
LIMIT 50
Here's the MySQL solution I've come up with, but it's quite ugly. I tried doing essentially the same thing using a temporary table, but in the process learned that MySQL doesn't allow joining to a temporary table multiple times in the same query.
SELECT items_scores.user_id, items_scores.item_id, items_scores.total_score
FROM (
SELECT items.user_id, items.item_id, SUM(sales.score) as total_score
FROM items
JOIN sales ON
sales.item_id = items.item_id
GROUP BY items.item_id
) AS items_scores
WHERE items_scores.total_score =
(
SELECT MAX(t.total_score)
FROM (
SELECT items.user_id, items.item_id, SUM(sales.score) as total_score
FROM items
JOIN sales ON
sales.item_id = items.item_id
GROUP BY items.item_id
) AS t
WHERE t.user_id = items_scores.user_id
)
ORDER BY items_scores.total_score DESC
MySQL query for it:
select user, item, total_score
from (
select sum(sales.score) as total_score, items.user_id as user, items.item_id as item
from sales
inner join items on sales.item_id = items.item_id
group by item,user
order by total_score desc) as t
group by user limit 50;
Output:
+------+------+-------------+
| user | item | total_score |
+------+------+-------------+
| 1 | 3 | 3 |
| 2 | 4 | 5 |
| 3 | 6 | 3 |
+------+------+-------------+
3 rows in set (0.00 sec)
Some explanation
MySQL documentation says:
However, this is useful primarily when all values in each nonaggregated column not named in the GROUP BY are the same for each group. The server is free to choose any value from each group, so unless they are the same, the values chosen are indeterminate. Furthermore, the selection of values from each group cannot be influenced by adding an ORDER BY clause. Sorting of the result set occurs after values have been chosen, and ORDER BY does not affect which values within each group the server chooses.
In our subquery... the nonagregated columns are user_id and item_id , we expect them to be same for every group that we are doing the sum on. Also we are not doing any order by that can influence the agregation..we want all the values of the group to be summed up. Finally we are sorting the output and saving it as a derived table.
Finally we run a select query on this derived table where we do the Group By user .. and Limit the output to 50