JOIN on the latest record of another table - sql

I have two datasets
First one: Customers - Has the ID of every customer and it's attributes etc..
Second one: Comments - Has multiple comments for every customer
In the Comments table i also have the ID of the customer related to the comment, but every costumer may have multiple comments.
I want to join on the first table (Customers) the last comment that was made for them.
SELECT Customers.Name
FROM Customers LEFT JOIN
Comments
ON Customers.ID = (SELECT MAX(CommentID)
FROM Comments
WHERE Customers.ID = Comments.CustomerID
)
I'm using MAX(CommentID) to get the last comment that was added to the table.
However this throws me a syntax error, what am i doing wrong?

First get all the last comments for each customer and then join to the table:
SELECT cus.Name, com.*
FROM Customers AS cus LEFT JOIN (
SELECT * FROM Comments
WHERE CommentID IN (
SELECT MAX(CommentID)
FROM Comments
GROUP BY CustomerID
)
) AS com ON cus.ID = com.CustomerID

I think this is what you intend:
SELECT cu.Name, co.*
FROM Customers cu LEFT JOIN
Comments co
ON co.customerId = cu.ID AND
co.ID = (SELECT MAX(co2.ID)
FROM Comments co2
WHERE co.CustomerID = co2.CustomerID
);
In MS Access you probably have to write this as:
SELECT cu.Name, co.*
FROM Customers as cu LEFT JOIN
Comments as co
ON co.customerId = cu.ID AND
WHERE co.ID IS NULL OR
co.ID = (SELECT MAX(co2.ID)
FROM Comments as co2
WHERE co.CustomerID = co2.CustomerID
);
Although your query is not logically correct, I'm not sure why it would have a syntax error. One possibility is that the ids in the two tables have incompatible types. Or perhaps you are not naming the columns correctly in your query.

Related

How to avoid the "ambiguous" error message when using joins to create several tables

I'm trying to use the following code to create a list of customers and their brands that they buy. The brands table has the brand name and customer_id is in the customers table. To link them I have to get the brand_id and receipt_id linked together via the receipts table (connects to customers table) and receipt_item_details1 table (connects to brands table).
So, receipt_item_details1 table (has brand_id column to then connect to brands table) and new table customer_receipts (created by first inner most subquery) are trying it to be linked by receipt_id. I'd like to show the customer_id column when I build my table joining these two table (an original: receipt_item_details1 joined to a new table: customer_receipts).
ISSUE: I keep getting the following error. how do Infix it and also list the brands?
"column reference "customer_id" is ambiguous
LINE 3: ...pts.receipt_id, receipt_item_details1.receipt_id, customer_r.."
SELECT customer_brandids.brand_id, brands.brand_id, customer_brandids.customer_id, brands.brand_name
FROM
(SELECT customer_receipts.receipt_id, receipt_item_details1.receipt_id, customer_receipts.customer_id, receipt_item_details1.brand_id
FROM
(SELECT receipts.customer_id, customers.customer_id, receipts.receipt_id
FROM receipts
INNER JOIN customers
ON receipts.customer_id = customers.customer_id) AS customer_receipts
INNER JOIN receipt_item_details1
ON customer_receipts.receipt_id = receipt_item_details1.receipt_id) AS customer_brandids
INNER JOIN brands
ON customer_brandids.brand_id = brands.brand_id
Your inner subselect
(SELECT receipts.customer_id, customers.customer_id
generates a result with two columns named customer_id. So your next higher subselect cannot differ between both columns if you reference customer_id
You should give one or both an alias:
(SELECT receipts.customer_id as r_customer_id,
customers.customer_id as c_customer_id
Then your next higher query can call
SELECT customer_receipts.c_customer_id...
So first step of solving the problem:
SELECT
customer_brandids.brand_id,
brands.brand_id,
customer_brandids.c_customer_id, --> reference alias
brands.brand_name
FROM
(SELECT
customer_receipts.receipt_id as c_receipt_id, --> same problem
receipt_item_details1.receipt_id as r_receipt_id,
customer_receipts.c_customer_id, --> reference alias
receipt_item_details1.brand_id
FROM
(SELECT
receipts.customer_id as r_customer_id, --> here was the problem
customers.customer_id as c_customer_id,
receipts.receipt_id
FROM receipts
INNER JOIN customers
ON receipts.customer_id = customers.customer_id) AS customer_receipts
INNER JOIN receipt_item_details1
ON customer_receipts.receipt_id = receipt_item_details1.receipt_id) AS customer_brandids
INNER JOIN brands
ON customer_brandids.brand_id = brands.brand_id
Addionally:
You don't need to take both columns (e.g. of receipt_id) because of the INNER JOIN it is ensured that both columns have the same value
You can use aliases to shorten your query.
You don't need to create a subquery for each join. Just join.
All in all, this should do the same:
SELECT b.brand_id, c.customer_id, b.brand_name
FROM receipts r
INNER JOIN customers c ON r.customer_id = c.customer_id
INNER JOIN receipt_item_details1 rid ON r.receipt_id = rid.receipt_id
INNER JOIN brands b ON b.brand_id = rid.receipt_id
demo: db<>fiddle
Do not use nested selects when it is not necessary, try to use joins, and query will be more simple and will look something like this
select * from receipts
join customers on receipts.customer_id = customers.customer_id
join receipt_item_details1 on receipts.receipt_id = receipt_item_details1.receipt_id
join brands on receipt_item_details1.brand_id = brands.brand_id
Instead of asterisk you can define columns you want to get

SQL: Selecting multiple columns from multiple tables

I am using MS Access 2013.
I am trying to selecting the number and name from Salesperson table. Number, name and postcode from Customers table as well as all the information from the CarSale table all within the past month and order by salesperson no.
I have come up with the following
SELECT CS.carNo, CS.dateOfSale, SA.salespersonNo, SA.name AS SalesName,
CU.customerNo, CU.name AS CustName, CU.postCode
FROM CarSale AS CS, Car AS C, Salesperson AS SA, Customer AS CU
WHERE CS.carNo = C.carNo AND CS.salespersonNo = SA.salespersonNo
AND CS.customerNo = CU.customerNo AND dateOfSale BETWEEN #01/09/2016#
AND #02/09/2016#
ORDER BY CS.salespersonNo;
However as you can see, this is butt-ugly! I did some research and found that I should be using "JOINS" so I went ahead and included them, this is where my problem starts.
After inserting the JOINS into the query I get something that looks like this:
SELECT CS.carNo, CS.dateOfSale, SA.salespersonNo, SA.name AS SalesName,
CU.customerNo, CU.name AS CustName, CU.postCode
FROM CarSale AS CS
JOIN Car AS C ON CS.carNo = C.carNo
JOIN Salesperson AS SA on CS.salespersonNo = SA.salespersonNo
JOIN Customer AS CU ON CS.customerNo = CU.customerNo
WHERE cs.dateOfSale BETWEEN #01/09/2016# AND #02/09/2016#
ORDER BY CS.salespersonNo;
Here are the tables:
**CarSale**
carNo salespersonNo customerNo dateOfSale
-------------------------------------------------------
**Salesperson**
salespersonNo name contactNo monthlySalary centreNo
--------------------------------------------------------------
**Customer**
customerNo name contactNo postCode
---------------------------------------------
The error I am getting is "Syntax error in FROM clause."
I think you're close, but there is something wonky about your JOINs - you have a join on 'Car', but that's not one of your tables. JOINing occurs between tables, with ON specifying the fields that are equivalent (what you are JOINing ON). With that in mind:
SELECT s.salespersonNo, s.name, c.customerNo, cs.carNo,
cs.dateofsale, c.name, c.postCode
FROM salesperson s
INNER JOIN carsale cs
ON cs.salespersonNo = s.salespersonNo
INNER JOIN customer c
ON cs.customerNo = c.customerNo
WHERE cs.dateOfSale BETWEEN #01/09/2016# AND #02/09/2016#
ORDER BY CS.salespersonNo;
Notice that your WHERE and ORDER BY are unchanged, and I just used different aliases in my test run. The main difference is in the JOIN - I join from salesperson to CarSales ON the salespersonNo, and then from CarSales to customerNo, similar to what you already have.
The syntax error is because with multiple JOINs you need parentheses around every pair of them.
It would be a lot easier to use the query designer, it does those things automatically.
SELECT CS.carNo, CS.dateOfSale, SA.salespersonNo, SA.name AS SalesName,
CU.customerNo, CU.name AS CustName, CU.postCode
FROM (((CarSale AS CS
INNER JOIN Car AS C ON CS.carNo = C.carNo)
INNER JOIN Salesperson AS SA on CS.salespersonNo = SA.salespersonNo)
INNER JOIN Customer AS CU ON CS.customerNo = CU.customerNo)
WHERE cs.dateOfSale BETWEEN #01/09/2016# AND #02/09/2016#
ORDER BY CS.salespersonNo;
As Stidgeon wrote, if these are all fields you need, you can omit the join with Car.

The amount changes and is incorrect when I add a second join

I should start off saying I am using postgresql. I have four tables, the customer_orders, payments and clearinghouse_orders.
Customer_orders is one to many with payments and clearinghouse_orders.
When I use this query I the total of remits is correct:
SELECT mailers.mail_date, SUM(cho.remit) AS remits
FROM mailers
RIGHT JOIN customer_orders co
ON mailers.id = co.mailer_id
LEFT JOIN clearinghouse_orders cho
ON co.id = cho.customer_order_id
GROUP BY mailers.mail_date
The remit totals are perfect. When I add the payments table the remits increase. I suspect when I add the payments it starts counting some of the remits twice where there are two payment. Here is the code that needs correcting:
SELECT mailers.mail_date, SUM(cho.remit) AS remits, SUM(p.payment_amt) AS payments
FROM mailers
RIGHT JOIN customer_orders co
ON mailers.id = co.mailer_id
LEFT JOIN clearinghouse_orders cho
ON co.id = cho.customer_order_id
LEFT JOIN payments p
ON co.id = p.customer_order_id
GROUP BY mailers.mail_date
I am a very early beginner of SQL so please go easy on me. Any help you can offer will be greatly appreciated.
If your theory is correct, a simple sub-query to remove duplicate payment rows should fix this...
SELECT mailers.mail_date, SUM(cho.remit) AS remits, SUM(p.payment_amt) AS payments
FROM mailers
RIGHT JOIN customer_orders co
ON mailers.id = co.mailer_id
LEFT JOIN clearinghouse_orders cho
ON co.id = cho.customer_order_id
LEFT JOIN
(
select
customer_order_id,
sum(payment_amt) as payment_amt
from
payments
group by
customer_order_id
) as p
ON co.id = p.customer_order_id
GROUP BY mailers.mail_date

What are some good examples where SQL's OUTER JOIN is used?

I often get asked the questions in an interview that "what is an outer join in SQL"?
While it can be answered, I wonder what might be some classic and good real life examples where a (LEFT) OUTER JOIN is used?
In the Northwind database on the Customers and Orders table.
Doing an inner join will only give you customers that have placed orders.
Doing an outer join will get all customers and orders for customers that have placed orders.
To add to Robin Day's answer, you can also use a Left Outer Join to grab only customers who have NOT placed orders by checking for NULL.
SELECT *
FROM Customer
LEFT OUTER JOIN Order
ON Customer.CustomerId = Order.CustomerId
WHERE Order.CustomerId IS NULL
Following is the visual represntation of the left outer join
SELECT <select_list>
FROM Table_A A
LEFT JOIN Table_B B
ON A.Key = B.Key
read more about joins in the below article
http://www.codeproject.com/KB/database/Visual_SQL_Joins.aspx ( one of the best article must read )
A LEFT OUTER JOIN can be used when you want all records from one table, as well as records from another table if any.
E.g., given table User and Address, where Address has a FK to User and there could be 0 or more addresses per user:
select *
from User u
left outer join Address a on u.UserID = a.UserID
This will ensure you get all User records, regardless of whether there was a corresponding Address record or not.
If you want to show all Users that do not have addresses, you can do this:
select *
from User u
left outer join Address a on u.UserID = a.UserID
where a.UserID is null
Classic example is cutomers and orders. Some customers have orders and others do not. You want to show a list of customers with total sales. So you do a left outer join from the customer to the order and get:
Customer A: $100;
Customer B: $0;
Customer C: $500
instead of:
Customer A: $100;
Customer C: $500
Here is an example:
I need a list of all customers, with their vouchers, I also need the customers that never used vouchers.
SELECT *
FROM Customer
LEFT OUTER JOIN Voucher
ON Customer.CustomerId = Voucher.CustomerId
Get a list of all customers including any details of orders they have made. Some customers may not have made orders and therefore an INNER JOIN would exclude them from this list.
SELECT
*
FROM
Customer
LEFT OUTER JOIN
Order
ON
Customer.CustomerId = Order.CustomerId

Join two tables where all child records of first table match all child records of second table

I have four tables: Customer, CustomerCategory, Limit, and LimitCategory. A customer can be in multiple categories and a limit can also have multiple categories. I need to write a query that will return the customer name and limit amount where ALL the customers categories match ALL the limit categories.
I'm guessing it would be similar to the answer here, but I can't seem to get it right. Thanks!
Edit - Here's what the tables look like:
tblCustomer
customerId
name
tblCustomerCategory
customerId
categoryId
tblLimit
limitId
limit
tblLimitCategory
limitId
categoryId
I THINK you're looking for:
SELECT *
FROM CustomerCategory
LEFT OUTER JOIN Customer
ON CustomerCategory.CustomerId = Customer.Id
INNER JOIN LimitCategory
ON CustomerCategory.CategoryId = LimitCategory.CategoryId
LEFT OUTER JOIN Limit
ON Limit.Id = LimitCategory.LimitId
Updated!
Thanks to Felix for pointing out a flaw in my existing solution (3 years after I originally posted it, hehe). After looking at it again, I think this might be correct. Here I'm getting (1) the customers and limits with matching categories, plus the number of matching categories, (2) the number of categories per customer, (3) the number of categories per limit, (4) I then ensure the number of categories for customer and limits is the same as the number of the matches between the customers and limits:
UNTESTED!
select
matches.name,
matches.limit
from (
select
c.name,
c.customerId,
l.limit,
l.limitId,
count(*) over(partition by cc.customerId, lc.limitId) as matchCount
from tblCustomer c
join tblCustomerCategory cc on c.customerId = cc.customerId
join tblLimitCategory lc on cc.categoryId = lc.categoryId
join tblLimit l on lc.limitId = l.limitId
) as matches
join (
select
cc.customerId,
count(*) as categoryCount
from tblCustomerCategory cc
group by cc.customerId
) as customerCategories
on matches.customerId = customerCategories.customerId
join (
select
lc.limitId,
count(*) as categoryCount
from tblLimitCategory lc
group by lc.limitId
) as limitCategories
on matches.limitId = limitCategories.limitId
where matches.matchCount = customerCategories.categoryCount
and matches.matchCount = limitCategories.categoryCount
I don't know if this will work or not, just a thought i had and i can't test it, I'm sures theres a nicer way! don't be too harsh :)
SELECT
c.customerId
, l.limitId
FROM
tblCustomer c
CROSS JOIN
tblLimit l
WHERE NOT EXISTS
(
SELECT
lc.limitId
FROM
tblLimitCategory lc
WHERE
lc.limitId = l.id
EXCEPT
SELECT
cc.categoryId
FROM
tblCustomerCategory cc
WHERE
cc.customerId = l.id
)