MYSQL Limit 1 Record per in Joined Table - mysql

Attempting to join two tables on user_id. The users table has unique id for each user. The user_codes table can have multiple rows with the same user_id. I only want to return 1 row from the joined user_codes table, where code_count is the largest.
users Table
| id | email |
| -------- | --------------- |
| 1 | user1#gmail.com |
| 2 | user2#gmail.com |
| 3 | user3#gmail.com |
user_code TABLE
| user_id | invite_code | count |
| -------- | ----------- | ------|
| 1 | X49MCL1 | 40 |
| 1 | K59CLT9 | 1000 |
| 2 | X5BC924 | 15 |
| 2 | 38DF80L | 8 |
| 3 | 641020T | 22 |
EXPECTED RESULT
| id | email | invite_code | count |
| --- | --------------- | ----------- | ------|
| 1 | user1#gmail.com | K59CLT9 | 1000 |
| 2 | user2#gmail.com | X5BC924 | 15 |
| 3 | user3#gmail.com | 641020T | 22 |
The query result only includes a single instance of each user found in the user_codes table with the highest count.
Here is the closest query I could get, but it only returns the invite_code and count for the first user.
SELECT a.id, a.email, b.invite_code, b.count
FROM users a
LEFT JOIN user_codes b
ON b.user_id = a.id
AND b.count = (SELECT MAX(count) FROM user_codes GROUP BY b.user_id)
The above query returns the result:
| id | email | invite_code | count |
| --- | --------------- | ----------- | ------ |
| 1 | user1#gmail.com | K59CLT9 | 1000 |
| 2 | user2#gmail.com | `NULL` | `NULL` |
| 3 | user3#gmail.com | `NULL` | `NULL` |
I can't seem to figure out how/why the records after the first one don't include the invite_code and the count.
Thanks for help!

On MySQL 8+, I suggest using the RANK() window function:
WITH cte AS (
SELECT u.id, u.email, uc.invite_code, uc.count,
RANK() OVER (PARTITION BY u.id ORDER BY uc.count DESC) rnk
FROM users u
INNER JOIN user_code uc
ON uc.user_id = u.id
)
SELECT id, email, invite_code, count
FROM cte
WHERE rnk = 1;
The RANK() function will also match multiple records per user tied for the highest count.
You might be able to salvage your current attempt by correlating the user inside the subquery to the outer query:
SELECT a.id, a.email, b.invite_code, b.count
FROM users a
LEFT JOIN user_codes b
ON b.user_id = a.id AND
b.count = (SELECT MAX(uc.count) FROM user_codes uc WHERE uc.user_id = a.id);

Related

How Affect Group By to Other Second Join Table

I have some table like this
table request_buys
| id | invoice | user_id |
| -- | ----------------- | ------- |
| 3 | 20220405/01104298 | 1 |
table traces
| id | request_buy_id | status_id | created_at |
| -- | -------------- | --------- | ------------------- |
| 37 | 3 | 1 | 2022-03-27 14:12:25 |
| 38 | 3 | 2 | 2022-03-28 14:12:25 |
| 39 | 3 | 3 | 2022-03-29 14:12:25 |
| 40 | 3 | 4 | 2022-03-30 14:12:25 |
| 41 | 3 | 5 | 2022-03-31 14:12:25 |
| 42 | 3 | 6 | 2022-04-01 14:12:25 |
table statuses
| id | nama |
| -- | ----------------- |
| 1 | Order Placed |
| 2 | Order Paid |
| 3 | Accepted |
| 4 | Picked by Courier |
| 5 | In Transit |
| 6 | Delivered |
| 7 | Rated |
| 8 | Rejected |
| 9 | Canceled |
and then i try to design query like below
select
request_buys.invoice,
MAX(traces.id) as traces_id,
MAX(statuses.nama) as statuses_nama
from
`request_buys`
inner join `traces` on `request_buys`.`id` = `traces`.`request_buy_id`
inner join `statuses` on `traces`.`status_id` = `statuses`.`id`
where
`user_id` = 1
group by
request_buys.id
and produces output like the following
output
| invoice | traces_id | statuses_nama |
| ----------------- | --------- | ----------------- |
| 20220405/01104298 | 42 | Picked by Courier |
and the output i expect should be like in the table below
expect
| invoice | traces_id | statuses_nama |
| ----------------- | --------- | ----------------- |
| 20220405/01104298 | 42 | Delivered |
I understand my error is in MAX(statuses.nama) which I should change like removing MAX() in statuses.nama
But i just get error like this "SELECT list is not in GROUP BY clause and contains nonaggregated ... this is incompatible with sql_mode=only_full_group_by"
then I tried some to clear the value "ONLY_FULL_GROUP_BY" with a query like the following
SET sql_mode=(SELECT REPLACE(##sql_mode,'ONLY_FULL_GROUP_BY',''))
and the result is like this
output
| invoice | traces_id | statuses_nama |
| ----------------- | --------- | ----------------- |
| 20220405/01104298 | 42 | Order Placed |
and I'm really stuck at this
and how to make trace_id.status_id from the "GROUP BY" result based on request_buys.id still have a relationship with statuses.id
Your problem lies with your misuse of the MAX(statuses.nama) expression. Based on your expected output,you intend to get the statuses.nama which matches the MAX(traces.id), NOT the MAX(statuses.nama) value which returns the highest value in terms of alphabetic order. In this case, the initial letter 'P' > 'D' . I have tweaked your code a bit and tried it on workbench,supposing there are more than one invoice for a particular user.(e.g insert into request_buys values (4,'20230405/01104298',1); insert into traces values (43,4,7,'2022-04-01 14:12:25');) It works as intended.
select invoice, t.id as traces_id, s.nama as statuses_name from request_buys r
join traces t on r.id=t.request_buy_id
join statuses s on t.status_id=s.id
join
(select traces.request_buy_id, MAX(traces.id) as traces_id
from `request_buys`
inner join `traces` on `request_buys`.`id` = `traces`.`request_buy_id`
where
`user_id` = 1
group by
traces.request_buy_id ) join_t
on t.request_buy_id=join_t.request_buy_id and t.id=join_t.traces_id
;
If I'm understanding correctly, you're trying to retrieve the most recent status for each invoice. Using MAX(nama) won't return that result, because it just picks the maximum status name alphabetically.
Assuming you're using MySQL 8.x, use ROW_NUMBER() to sort and rank the statuses for each invoice, by the most recent date first. Then grab the latest one using where rowNum = 1
WITH cte AS (
SELECT rb.id AS request_buy_id
, rb.invoice
, t.id AS traces_id
, s.nama AS statuses_nama
, ROW_NUMBER() OVER(PARTITION BY rb.id ORDER BY t.created_at DESC) AS RowNum
FROM request_buys rb
INNER JOIN traces t ON rb.id = t.request_buy_id
INNER JOIN statuses s ON t.status_id = s.id
WHERE user_id = 1
)
SELECT *
FROM cte
WHERE RowNum = 1
;
Result:
request_buy_id
invoice
traces_id
statuses_nama
RowNum
3
20220405/01104298
42
Delivered
1
db<>fiddle here

Get latest record per timestamp in a left join query

I currently have 4 tables that I query to list status of orders.
Tables with relevant field look like this
+--------------+ +------------+ +----------+ +----------+
|Orders | | Customers | | Users | | Status |
+--------------+ +------------+ +----------+ +----------+
| id | | id | | id | | id |
| customer_id | | name | | name | | order_id |
| rep_id | +------------+ +----------+ | status |
+--------------+ | comments |
| date |Timestamp
| tech_id |
+----------+
Using the following SQL I can display the list of orders and status.
SELECT
Orders.id AS orderid,
Customers.name AS CLIENT,
Users.name AS rep,
Status.status
FROM
Orders
LEFT JOIN
Customers ON Orders.customer_id = Customers.id
LEFT JOIN
Users ON Orders.rep_id = Users.id
LEFT JOIN
Status ON Orders.id = Status.order_id
I get something like this.
+---------+----------+-------+--------+
| orderid | CLIENT | rep | status |
+---------+----------+-------+--------+
| 1 | Client 1 | Rep 1 | 1 |
| 2 | Client 2 | Rep 2 | 1 |
| 3 | Client 3 | Rep 1 | 1 |
| 4 | Client 4 | Rep 2 | 1 |
| 6 | Client 6 | Rep 4 | 1 |
| 1 | Client 1 | Rep 3 | 4 |
| 6 | Client 6 | Rep 4 | 4 |
+---------+----------+-------+--------+
I need to get one record per orderid based on the most recent date on the Status table. I feel that I'm so close however can't figure it out.
The simplest way is probably a correlated subquery:
SELECT o.id AS orderid, c.name AS CLIENT, u.name AS rep,
(SELECT s.status
FROM status s
WHERE o.id = s.order_id
ORDER BY s.date DESC
LIMIT 1
) latest_status
FROM Orders o LEFT JOIN
Customers c
ON o.customer_id = c.id LEFT JOIN
Users u
ON o.rep_id = u.id ;

Join records with lowest value

I have two tables, users and survey. I want query the table user and to join the table survey in a way that only the survey record with the lowest value is returned for each record in user table.
I want to avoid subqueries and temporary tables.
table users:
--------------
| uid | name |
--------------
| 1 | mike |
| 2 | john |
| 3 | bill |
--------------
table survey:
----------------------
| id | uid | value |
----------------------
| 1 | 3 | 9 |
| 2 | 3 | 5 |
| 3 | 1 | 3 |
| 4 | 1 | 7 |
| 5 | 1 | 2 |
| 6 | 2 | 4 |
| 7 | 2 | 9 |
| 8 | 1 | 0 |
| 9 | 2 | 5 |
---------------------
expected output:
---------------------
| id | name | value |
---------------------
| 8 | mike | 0 |
| 2 | bill | 5 |
| 6 | john | 4 |
---------------------
What kind of JOIn should I do, or how should I write the query?
The following query gets all rows with minimum value (doesn't exist another survey with value under the selected value)
Try this:
SELECT u.*, s.value
FROM survey s
JOIN users u
ON s.uid = u.uid
WHERE NOT EXISTS(
SELECT 'maximum'
FROM survey s2
WHERE s2.uid = s.uid
AND s2.value < s.value)
You could use something like this:
select s.id, u.name, y.min_value
from
(
select uid, min(value) as min_value
from survey
group by uid
) y
join survey s
on s.value = y.min_value
and s.uid = y.uid
join user u
on u.uid = y.uid
I think this will help you
SELECT * FROM SURVEY S
INNER JOIN USERS U
ON S.UID=U.UID
QUALIFY ROW_NUMBER() OVER (PARTITION BY S.UID ORDER BY S.VALUE1 ASC )=1;

MySQL sort by sum multiple columns in different tables

I have 3 tables:
Users
| id | name |
|----|-------|
| 1 | One |
| 2 | Two |
| 3 | Three |
Likes
| id | user_id | like |
|----|---------|-------|
| 1 | 1 | 3 |
| 2 | 1 | 5 |
| 3 | 2 | 1 |
| 4 | 3 | 2 |
Transations
| id | user_id | transaction |
|----|---------|-------------|
| 1 | 1 | -1 |
| 2 | 2 | 5 |
| 3 | 2 | -1 |
| 4 | 3 | 10 |
I need get sum of likes.like and transations.transation for each user and then sort it by its result.
I was able to do it for users and likes:
select users.*, sum(likes.like) as points
from `users`
inner join `likes` on `likes`.`user_id` = `users`.`id`
group by `users`.`id`
order by points desc
But then I add transactions table like this:
select users.*, (sum(likes.like)+sum(transactions.`transaction`)) as points
from `users`
inner join `likes` on `likes`.`user_id` = `users`.`id`
inner join `transactions` on `transactions`.`user_id` = `users`.`id`
group by `users`.`id`
order by points desc
It is show wrong results.
I expecting to see:
| id | name | points |
|----|-------|--------|
| 3 | Three | 12 |
| 1 | One | 7 |
| 2 | Two | 5 |
But get this instead:
| id | name | points |
|----|-------|--------|
| 3 | Three | 12 |
| 1 | One | 6 |
| 2 | Two | 5 |
So, how sort users by sum likes.like and transations.transation?
Thank you!
Since there's not a 1-to-1 relationships between transactions and likes, I think you need to use subqueries:
select users.*,
(select sum(points) from likes where user_id = users.id) as points,
(select sum(transaction) from transactions where user_id = users.id) as transactions
from users
order by points desc
Updated after more explanation of requirements:
select users.*,
(select sum(points) from likes where user_id = users.id) +
(select sum(transaction) from transactions where user_id = users.id) as points
from users
order by points desc

SELECTing rows where no other rows match

This seemed pretty simple to start with, but it's getting awkward.
Suppose we have a table containing...
+---------+-----------+
| chat_id | friend_id |
+---------+-----------+
| A | 1 |
| A | 2 |
| A | 3 |
| B | 1 |
| B | 2 |
| C | 1 |
| C | 2 |
| C | 3 |
| D | 1 |
| D | 2 |
| D | 3 |
| D | 4 |
| D | 5 |
| E | 0 |
| E | 1 |
| E | 2 |
| E | 3 |
| E | 4 |
| E | 5 |
| E | 6 |
| E | 7 |
| F | 0 |
| F | 1 |
| G | 1 |
| G | 2 |
+---------+-----------+
And I wish to select only those chat_id that have friend_ids 1 and 2 and no other friend_id, what would the SQL be to get B and G returned?
So far, the best I've come up with is:
SELECT DISTINCT a.chat_id, COUNT(*)
FROM tt2 a
LEFT JOIN tt2 b
ON a.chat_id = b.chat_id
AND b.friend_id NOT IN (1,2)
WHERE a.friend_id in (1,2)
and b.chat_id IS NULL GROUP BY a.chat_id HAVING COUNT(*) = 2;
+---------+----------+
| chat_id | count(*) |
+---------+----------+
| B | 2 |
| G | 2 |
+---------+----------+
2 rows in set (0.00 sec)
And just in case I was looking for chat_id where only 1,2,3 exist...
SELECT DISTINCT a.chat_id, COUNT(*)
FROM tt2 a
LEFT JOIN tt2 b
ON a.chat_id = b.chat_id
AND b.friend_id not in (1,2,3)
WHERE a.friend_id IN (1,2,3)
AND b.chat_id IS NULL
GROUP BY a.chat_id
HAVING COUNT (*) = 3;
+---------+----------+
| chat_id | count(*) |
+---------+----------+
| A | 3 |
| C | 3 |
+---------+----------+
But this table could get massive and I need the SQL to be swift, does anyone know a better way?
To try and clarify... I get given a bunch of friend_id's and I want to get chat_id where only those friend_id exist for that chat_id.... with the SQL being quick (on sqlite)
Many thanks in advance!
Here's an option that should be able to limit the amount of data needed
SELECT
d.chat_id,
COUNT(DISTINCT s.friend_id) AS matchedFriends,
COUNT(DISTINCT d.friend_id) AS totalFriends
FROM tt2 AS d
INNER JOIN tt2 AS s
ON s.chat_id = d.chat_id
AND s.friend_id IN (1,2)
GROUP BY d.chat_id
HAVING matchedFriends = 2
AND totalFriends = matchedFriends
The INNER JOIN s makes sure that it only hits rows that have got at least one of the requested friends in. The matchedFriends count checks how many of the requested friends are found.
The totalFriends count then checks how many friends in total are on that chat.
Finally the HAVING first makes sure there are 2 matched friends, and then checks the number of friends in total equals the number of matched friends.
This will require you to supply both a list of friends, and a number of friends you are looking for, but should be efficient.
For increased efficiency, have an index on (chat_id,friend_id) (if you don't already, assuming it's a 2-part PK at time of writing)
Try this:
SELECT chat_id, GROUP_CONCAT(DISTINCT friend_id ORDER BY friend_id) AS friends
FROM table_1
GROUP BY chat_id
HAVING friends = '1,2'
Note: This works in mysql but I doubt that it will work on sqlite.