Incorrect results from three table join - mysql

I have these three tables in my database:
tblCustomer (id,name,address)
tblLoan (id,customerId,LoanAmount,date)
tblPayment (id,customerId,ReceivedAmount,date)
I want to find the total loanAmount for a customer and how much they have paid.
I wrote this query:
SELECT c.fname, SUM(l.amount), SUM(p.amount)
FROM tblCustomer c
JOIN tblLoan l ON (l.customerId = c.id)
JOIN tblPayment p ON (p.customerId = c.id)
WHERE c.id = 3;
It returns results but they are incorrect.

First, as others have mentioned, your syntax is likely incorrect because you do not have matching column names, but you said you had incorrect results, so I would assume that's not your problem as you were able to run your query..
The problem that I think you are most likely having is that by joining the two tables together like that, rows appear twice for each customer. Am I correct in assuming that your 'incorrect' results are double what you would expect? Let me illustrate for those who don't understand. Consider this data set, with shortened column values:
tblCustomer:
| id | name |
+----+------+
| 1 | Adam |
| 2 | John |
| 3 | Jane |
tblLoan, and for simplicity we'll say the payment table looks the same:
| customerID | loanAmount |
+------------+------------+
| 1 | 100 |
| 2 | 200 |
| 3 | 300 |
| 3 | 300 |
| 2 | 200 |
If I perform the following query (without summing values, just getting the values I want:
SELECT c.id, c.name, l.loanAmount, p.receivedAmount
FROM tblCustomer c
JOIN tblLoan l ON l.customerid = c.id
JOIN tblPayment p ON p.customerid = c.id
WHERE c.id = 3;
It returns this result set:
| id | name | loanAmount | receivedAmount |
+----+------+------------+----------------+
| 3 | Jane | 100 | 100 |
| 3 | Jane | 100 | 300 |
| 3 | Jane | 300 | 100 |
| 3 | Jane | 300 | 300 |
So notice that because we're joining two tables based on a relationship to a third table, were actually creating a cartesian product which is causing the problem. So, what I recommend you do is use subqueries for these two tables. One subquery will pull the loan values, one the payment values, and you can join those together on the id value.
It will look like this:
SELECT t.id, t.totalLoan, w.totalReceived
FROM(SELECT c.id, SUM(l.loanAmount) AS totalLoan
FROM tblCustomer c
JOIN tblLoan l ON l.customerid = c.id
WHERE c.id = 3) t
JOIN(SELECT c.id, SUM(p.receivedAmount) AS totalReceived
FROM tblCustomer c
JOIN tblPayment p ON p.customerid = c.id
WHERE c.id = 3) w
ON t.id = w.id;
And this should give you the values you want. Here is what I tested on SQL Fiddle.

FYI, YOUR COLUMN NAMES ARE WRONG!!!
There is no such column named fname in table tblCustomer
There is no such column named amount in table tblLoan
There is no such column named amount in table tblPayment
You won't get the right result if you don't have the appropriate column names. Even when using aliases, your column name should be EXACTLY THE SAME as in your database table. That's because, you are aliasing TABLES in JOIN queries, not COLUMNS.
So, re-write your query in the following way:
SELECT c.name, SUM(l.LoanAmount), SUM(p.ReceivedAmount)
FROM tblCustomer c
JOIN tblLoan l ON l.customerId = c.id
JOIN tblPayment p ON p.customerId = c.id
WHERE c.id = 3
Note that there's no need to get brackets around the ON clause in JOIN.

Related

Join two tables and group by two columns

I have two MySQL tables.
User Table
id | country
Post Table
id | userId | type
in the Post table I have 3 types of posts.
'Social', 'Economic', 'Culture'
I need to group the data in order to obtain the count of posts per type.
eg.
country | social | economic | culture
UK | 2 | 1 | 0
US | 0 | 2 | 1
LK | 1 | 0 | 0
IN | 0 | 0 | 3
I wrote the following query which seems to group the data only using the country. How can I port this such that it counts the type of post?
SELECT b.country, SUM(a.type)
FROM Post a
INNER JOIN User b ON a.id = b.id
GROUP BY b.country, a.type
Can anyone give me pointer here?
SELECT b.country,
SUM(a.type = 'Social') as social,
SUM(a.type = 'Economic') as economic,
SUM(a.type = 'Culture') as culture
FROM Post a
INNER JOIN User b ON a.id = b.id
GROUP BY b.country
The sum() adds up how many time the condition is true.
Only group by data you want to have its own row in the result set.

SQL left join: how to return the newest from tableB and grouped by another field

I've been trying for two days, without luck.
I have the following simplified tables in my database:
customers:
| id | name |
| 1 | andrea |
| 2 | marco |
| 3 | giovanni |
access:
| id | name_id | date |
| 1 | 1 | 5000 |
| 2 | 1 | 4000 |
| 3 | 2 | 1500 |
| 4 | 2 | 3000 |
| 5 | 2 | 1000 |
| 6 | 3 | 6000 |
| 7 | 3 | 2000 |
I want to return all the names with their last access date.
At first I tried simply with
SELECT * FROM customers LEFT JOIN access ON customers.id =
access.name_id
But I got 7 rows instead of 3 as expected. So I understood I need to use GROUP BY statemet as the following:
SELECT * FROM customers LEFT JOIN access ON customers.id =
access.name_id GROUP BY customers.id
As far I know, GROUP BY combines using a random row. In fact I got unordered access dates with several tests.
Instead I need to group every customer id with its corresponding latest access! How this can be done?
You have to get the latest date from the access table with a group by on the the name_id, then join this result with the customer table. Here is the query:
select c.id, c.name, a.last_access_date from customers c left join
(select id, name_id, max(access_date) last_access_date from access group by name_id) a
on c.id=a.name_id;
Here is a DEMO on sqlfiddle.
I think this is what you'd like to achieve:
SELECT c.id, c.name, max(a.date) last_access
FROM customers c
LEFT JOIN access a ON c.id = a.name_id
GROUP BY c.id, c.name
The LEFT join will return all entries in table customers regardless if the join criteria (c.id = a.name_id) is satisfied. This means that you might get some NULL entries.
Example:
Simply add a new row in the customers table (id: 4, name: manuela). The output will have 4 rows and the newest row will be (id: 4, last_access: null)
I would do this using a correlated subquery in the ON clause:
SELECT a.*, c.*
FROM customers c LEFT JOIN
access a
ON c.id = a.name_id AND
a.DATE = (SELECT MAX(a2.date) FROM access a2 WHERE a2.name_id = a.name_id);
If this statement is true:
I need to group every customer id with its corresponding latest access! How this can be done?
Then you can simply do:
select a.name_id, max(a2.date)
from access a
group by a.name_id;
You do not need the customers table because:
All customers are in access, so the left join is not necessary.
You need no columns from customers.

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

How to combine MySQL number of rows of the joined table, including 0?

I have two tables: 'company' and 'order'. The first one contains company info and the second one holds all orders made with a company. (order.company = company.ID).
I am making a query on the first table, for example all companies in the city of New York. I would like to make a join with the order table, so that it immediately shows how many orders for a company was made. I could do this with a simple JOIN query, however, it does not include 0. For example, if a company has no orders yet, it will not show up at all, while it should be in the list with 0 orders.
Desired end result:
----------------------------------------
| ID | Name | ... | Orders |
----------------------------------------
| 105 | Company A | ... | 14 |
| 115 | Company B | ... | 5 |
| 120 | Company C | ... | 0 |
| 121 | Company D | ... | 0 |
----------------------------------------
Thanks in advance!
This is a left join with aggregation:
SELECT c.ID, c.Name, count(o.company) as total
FROM companies c left outer join
orders o
on c.id = o.company
WHERE c.city = 'New York'
GROUP BY c.ID;
In MySQL, it is best to avoid subqueries in the from clause -- where possible -- because the derived table is actually created.
The COUNT() expression is counting the number of matches by counting the number of non-null values in the id field used for the join.
Try this
SELECT com.id,com.name,od.orders FROM compnay AS com
LEFT JOIN orders AS od ON od.company = com.id;
SELECT companies.ID,companies.Name ,orders.total FROM
(SELECT ID,Name FROM company where county ='NEW YORK') companies
LEFT JOIN (SELECT company,COUNT(*) as total FROM order GROUP BY company) orders
ON orders.company = companies.ID

How would I accomplish this using SQL?

I have 2 tables in my database that look like so:
clients
+-------------+
| id | sms |
|------+------|
| 1 | 0 |
| 2 | 1 |
| 3 | 1 |
| 4 | 1 |
+------+------+
clients_lists_relationships
+----------------------+
| listid | clientid |
|----------+-----------|
| 1 | 1 |
| 1 | 2 |
| 2 | 1 |
| 3 | 1 |
+----------+-----------+
Now what I'm trying to do is get a list of clients who are in a bunch of lists. I do that like so:
SELECT c.id,
l.*
FROM clients AS c,
clients_lists_relationships AS l
WHERE c.id = l.clientid
AND c.sms = '1'
AND ( l.listid = '1'
OR l.listid = '2' );
This does give me a list of the clients that I need. But because a client can be in more than one list I get the same client more than once. How would I limit this to only one row for each client no matter how many lists they are in?
If you just need any client that is in a list, you can just query the relationship table:
SELECT DSITINCT(clientid) FROM clients_lists_relationships
You can also use that distinct on your combined query, but be aware that the "listid" you'll get is just one.
Use GROUP BY:
SELECT c.id,
l.listid
FROM clients c
INNER JOIN clients_lists_relationships l
ON c.id = l.clientid
WHERE c.sms = 1
AND l.listid IN (1,2)
GROUP BY c.id
Note that by doing this you lose information on which lists the client was a member of. This means that you should probably not select anything from client_lists_relationships as this information is either redundant (clientid) or incomplete (listid).
First of all take a look at MySQL:: JOIN It's much better than the WHERE statements you use now.
I think you are looking for GROUP BY.
In total, the query look like:
SELECT
c.id,
l.*
FROM
clients AS c
INNER JOIN
clients_lists_relationships AS l
ON
l.clientid = c.id
AND
c.sms = '1'
AND
( l.listid = '1'
OR l.listid = '2' );
GROUP BY
c.id
To return just the clients participating in more than 1 list you may want to consider using the HAVING clause:
SELECT c.id
FROM Clients c
INNER JOIN Client_Lists_Relationships l
ON l.clientid = c.id
WHERE c.sms = 1
HAVING COUNT(L.listid) > 1
GROUP BY c.id