MySQL - Join 2 tables - mysql

I have 2 tables: users & balance.
I want to join the tables with all of the details from the user table (all fields of all tuples) with the most recent entry from the balance table (1 field linked by a user id).
Here is the structure of the tables:
balance:
+---------+
| Field |
+---------+
| dbid |
| userId |
| date |
| balance |
+---------+
users:
+-------------+
| Field |
+-------------+
| dbid |
| id |
| fName |
| sName |
| schedName |
| flexiLeave |
| clockStatus |
+-------------+
I have been trying for hours to do this and the closest I can get is to return a row for a single user:
SELECT u.*, b.balance, b.date
FROM users u, balance b
WHERE
u.id = b.userId AND
b.date = (SELECT MAX(date) FROM balance WHERE userId = 'A8126982');
Or I can select all users but not the most recent entry in the balance table:
SELECT u.*, b.balance, b.date
FROM users u, balance b
WHERE u.id = b.userId GROUP BY u.id;
I have tried many different queries and seem to be getting closer but I just can't get to where I want to be.
Any help would be appreciated.

You can use the first SQL you wrote but for all users:
SELECT u.*, b.balance, b.date
FROM users u JOIN balance b ON u.id = b.userId
WHERE b.date = (SELECT MAX(date) FROM balance WHERE userId = u.id);
This may not be the fastest way to get the result, but it'll give you what you need. I use similar queries in quite a few places in my app.

Related

Select a specific row with additional column that its value is from another column in one table

Suppose I have a table named users consist of columns: user_id, user_name, user_created_by.
+------------------+----------------------+-------------------+
| user_id + user_name + user_created_by +
+------------------+----------------------+-------------------+
| 1 | John | 1 |
| 2 | Ann | 1 |
| 3 | Paul | 2 |
| 4 | King | 2 |
| 5 | Dirk | 3 |
+------------------+----------------------+-------------------+
The value of user_created_by is the user_id who created that record. Now, I want to make a query that results one specific row with added column let's say user_created_by_name which is the user_name of the user_id from the user_created_by. Suppose we want to get "Paul"'s record with who (the name) create it (temporary new column). For ease of understanding this is my expected result:
+----------+--------------+-------------------+------------------------+
| user_id | user_name | user_created_by | user_created_by_name |
+----------+--------------+-------------------+------------------------+
| 3 | Paul | 2 | Ann |
+----------+--------------+-------------------+------------------------+
this is my query using codeigniter:
$query=$this->db->query("SELECT *,
(SELECT user_name FROM users WHERE user_id = user_created_by)
AS "user_created_by_name" FROM users WHERE user_id=3);
But my result are:
+----------+--------------+-------------------+------------------------+
| user_id | user_name | user_created_by | user_created_by_name |
+----------+--------------+-------------------+------------------------+
| 3 | Paul | 2 | NULL |
+----------+--------------+-------------------+------------------------+
You culd use a self join (join the same table two time) using alias for fere to the tables as different sets of data
SELECT a.user_id, a.user_name, a.user_created_by, b.user_name as user_created_by_name
from users a
inner join user b on a.user_created_by = b.user_id
where a.user_id = 3
use self join
select u1.user_id, u1.name as user_name,
u2.user_created_by
,u2.user_name as createdby from users u1
join users u2 on u1.user_id=u2.user_created_by
where u1.user_id=3
You can solve this problem using a JOIN.
$sql = "SELECT users.user_id, users.user_name, user_created_by_name.user_name,
FROM users JOIN users AS user_created_by_name ON users.user_id = user_created_by_name.user_id WHERE users.user_id = 3";
$query=$this->db->query($sql);
If you you have users that were not created by another user use a LEFT JOIN instead:
$sql = "SELECT users.user_id, users.user_name, user_created_by_name.user_name,
FROM users LEFT JOIN users AS user_created_by_name ON users.user_id = users.user_id WHERE user_created_by_name.user_id = 3";
$query=$this->db->query($sql);
This will work:
SELECT a.user_id as User_id,
a.user_name as Name,
b.user_id as Created_by_user_id,
b.user_name as Created_by_name
FROM users AS a
INNER JOIN users AS b
ON a.user_id = b.user_created_by
WHERE a.user_id = 3
It is called a self-join, which is used when combining two records of the same table.

Join MAX id between two dates

I'm having a trouble with this join. Basically I just need to know if "warehouse" exists on both tables, but just take the last entry of the desired 'warehouse' between 2 dates.
Select c.warehouse, p.id, p.status FROM control c LEFT JOIN payment p ON c.warehouse = p.warehouse WHERE p.date BETWEEN '2018-06-26' AND '2018-06-27'
I also tried (It says: Invalid use of group function):
Select c.warehouse, p.id, p.status FROM control c LEFT JOIN payment p ON c.warehouse = p.warehouse WHERE p.date BETWEEN '2018-06-26' AND '2018-06-27' && p.id = MAX(p.id)
Those are my tables:
payments control
+---------------------------------------+ +-----------+
| id | warehouse | status | date | | warehouse |
+---------------------------------------+ +-----------+
|19006| 226975 | DUE |2018-06-26| | 226975 |
MAX ID-> |19066| 226975 | PAID |2018-06-27| | 226976 |
+---------------------------------------+ +-----------+
Result Obtained:
+--------------------------------------+
| warehouse | id | status | date |
+--------------------------------------+
| 226975 |19006 | DUE |2018-06-26|
+--------------------------------------+
In this case I'm obtaining the "first entry", the lower one (id: 19006) and I want "the last" (id: 19066) the max.
Result expected:
+--------------------------------------+
| warehouse | id | status | date |
+--------------------------------------+
| 226975 |19066 | PAID |2018-06-27|
+--------------------------------------+
Any ideas?
Two requirements.
rows where the warehouse column is in both tables. Exclude others. That's done with an inner JOIN.
the latest entry from the payments table. The id of that latest entry for each warehouse can be retrieved with this subquery.
SELECT MAX(id) id, warehouse
FROM payments
GROUP BY warehouse
Putting it all together:
SELECT p.warehouse, p.id, p.status, p.date
FROM payments p
JOIN ( SELECT MAX(id) id, warehouse
FROM payments
GROUP BY warehouse
) mm ON p.id = mm.id AND p.warehouse = mm.warehouse
JOIN control c ON p.warehouse = c.warehouse
My version with slight differences from O. Jones.
SELECT *
FROM control c
LEFT JOIN payments p ON p.warehouse=c.warehouse AND
p.date = (SELECT MAX(p1.date)
FROM payments p1
WHERE p1.warehouse=c.warehouse)
WHERE p.date IS NOT NULL;

Can I be selective on what rows I join on in MySQL

Suppose I have two tables, people and emails. emails has a person_id, an address, and an is_primary:
people:
id
emails:
person_id
address
is_primary
To get all email addresses per person, I can do a simple join:
select * from people join emails on people.id = emails.person_id
What if I only want (at most) one row from the right table for each row in the left table? And, if a particular person has multiple emails and one is marked as is_primary, is there a way to prefer which row to use when joining?
So, if I have
people: emails:
------ -----------------------------------------
| id | | id | person_id | address | is_primary |
------ -----------------------------------------
| 1 | | 1 | 1 | a#b.c | true |
| 2 | | 2 | 1 | b#b.c | false |
| 3 | | 3 | 2 | c#b.c | true |
| 4 | | 4 | 4 | d#b.c | false |
------ -----------------------------------------
is there a way to get this result:
------------------------------------------------
| people.id | emails.id | address | is_primary |
------------------------------------------------
| 1 | 1 | a#b.c | true |
| 2 | 3 | c#b.c | true | // chosen over b#b.c because it's primary
| 3 | null | null | null | // no email for person 3
| 4 | 4 | d#b.c | false | // no primary email for person 4
------------------------------------------------
You got it a bit wrong, how left/right joins work.
This join
select * from people join emails on people.id = emails.person_id
will get you every column from both tables for all records that match your ON condition.
The left join
select * from people left join emails on people.id = emails.person_id
will give you every record from people, regardless if there's a corresponding record in emails or not. When there's not, the columns from the emails table will just be NULL.
If a person has multiple emails, multiple records will be in the result for this person. Beginners often wonder then, why the data has duplicated.
If you want to restrict the data to the rows where is_primary has the value 1, you can do so in the WHERE clause when you're doing an inner join (your first query, although you ommitted the inner keyword).
When you have a left/right join query, you have to put this filter in the ON clause. If you would put it in the WHERE clause, you would turn the left/right join into an inner join implicitly, because the WHERE clause would filter the NULL rows that I mentioned above. Or you could write the query like this:
select * from people left join emails on people.id = emails.person_id
where (emails.is_primary = 1 or emails.is_primary is null)
EDIT after clarification:
Paul Spiegel's answer is good, therefore my upvote, but I'm not sure if it performs well, since it has a dependent subquery. So I created this query. It may depend on your data though. Try both answers.
select
p.*,
coalesce(e1.address, e2.address) AS address
from people p
left join emails e1 on p.id = e1.person_id and e1.is_primary = 1
left join (
select person_id, address
from emails e
where id = (select min(id) from emails where emails.is_primary = 0 and emails.person_id = e.person_id)
) e2 on p.id = e2.person_id
Use a correlated subquery with LIMIT 1 in the ON clause of the LEFT JOIN:
select *
from people p
left join emails e
on e.person_id = p.id
and e.id = (
select e1.id
from emails e1
where e1.person_id = e.person_id
order by e1.is_primary desc, -- true first
e1.id -- If e1.is_primary is ambiguous
limit 1
)
order by p.id
sqlfiddle

mysql multi join from same table give alias

currently i have the following query
SELECT * FROM tabs
JOIN users d ON tabs.`debit` = d.id
JOIN users c ON tabs.`credit` = c.id
as the table contains two user objects the names that get returned are the same like so:
id | amount | type | id | username | avatar | id | username | avatar
i need it to return as the following
id | amount | type | debit.id | debit.username | debit.avatar | credit.id | credit.username | credit.avatar
or something simmilar as long as the column names from the users are prefixed.
I think this is what you are looking for. Give it a try. (Assuming that id | amount | type belongs to the tabs table)
SELECT t.id,
t.amount,
t.type,
d.id as 'debit.id',
d.username as 'debit.username',
d.avatar as 'debit.avatar',
c.id as 'credit.id',
c.username as 'credit.username',
c.avatar as 'credit.avatar',
FROM tabs t
JOIN users d ON t.`debit` = d.id
JOIN users c ON t.`credit` = c.id

mysql select top unique values with inner join

I have 2 tables that look like this:
users (uid, name)
-------------------
| 1 | User 1 |
| 2 | User 2 |
| 3 | User 3 |
| 4 | User 4 |
| 5 | User 5 |
-------------------
highscores (user_id, time)
-------------------
| 3 | 12005 |
| 3 | 29505 |
| 3 | 17505 |
| 5 | 19505 |
-------------------
I want to query only for users that have a highscore and only the top highscore of each user. The result should look like:
------------------------
| User 3 | 29505 |
| User 5 | 19505 |
------------------------
My query looks like this:
SELECT user.name, highscores.time
FROM user
INNER JOIN highscores ON user.uid = highscores.user_id
ORDER BY time ASC
LIMIT 0 , 10
Actually this returns multiple highscores of the same user. I also tried to group them but it did not work since it did not return the best result but a random one (eg: for user id 3 it returned 17505 instead of 29505).
Many thanks!
You should use the aggregated function MAX() together with group by clause.
SELECT a.name, MAX(b.`time`) maxTime
FROM users a
INNER JOIN highscores b
on a.uid = b.user_id
GROUP BY a.name
SQLFiddle Demo
Your effort of grouping users was correct. You just needed to use MAX(time) aggregate function instead of selecting only time.
I think you wrote older query was like this:
SELECT name, time
FROM users
INNER JOIN highscores ON users.uid = highscores.user_id
GROUP BY name,time
But actual query should be:
SELECT user.name, MAX(`time`) AS topScore
FROM users
INNER JOIN highscores ON users.uid = highscores.user_id
GROUP BY user.name