SQL - JOIN with MAX(created_at) - mysql

Problem
I have 3 tables: customers, companies and calls.
A customer has many Companies, and a Company has many calls. (both one to many).
The current status of a Company is the result of the last call for the company (MAX(created_at)).
Now I want a list of all companies for a customer with the columns of the last call in the results.
Needed Result
The result should be:
company.*, lastcall.*,
There could be calls with the same created_at date. Then there should be only 1 row in de result.
Not all companies has a call yet, the company should still be in the result and the columns of the call should be NULL. (left join)
Tables
customers
- id (int, primary key)
- name (varchar)
- address (varchar)
- city (varchar)
companies
- id (int, primary key)
- customer_id (int)
- name (varchar)
- address (varchar)
- city (varchar)
calls
- id (int, primary key)
- company_id (int)
- result (varchar)
- created_at (datetime)
Attempt
A query which didn't work I came up with is:
SELECT * FROM companies co
LEFT JOIN calls ca ON co.id = ca.company_id
WHERE co.customer_id = ?
GROUP BY co.id
HAVING ca.created_at = (SELECT max(ll.created_at) FROM calls ll WHERE ll.company_id = co.id)

you should just join a select so that way you arent trying to re evaluate the select.
SELECT co.id, co.label, ca.result, ca.id, t.date_created as most_recent
FROM companies co
LEFT JOIN
( SELECT MAX(created_at) as date_created, company_id
FROM calls
GROUP BY company_id
) t ON t.company_id = co.id
JOIN calls ca ON ca.company_id = t.company_id AND t.date_created = ca.created_at
WHERE co.customer_id = ?
EDIT:
the issue is you have more than one call per company at the max date. to test this just pull out one customer and company and look at the results.
SELECT co.id, co.label, ca.result, ca.id, ca.created_at as most_recent_date
FROM companies co
LEFT JOIN
( SELECT MAX(created_at) as date_created, company_id
FROM calls
GROUP BY company_id
) t ON t.company_id = co.id
JOIN calls ca ON ca.company_id = t.company_id AND t.date_created = ca.created_at
WHERE co.customer_id = ? AND co.id = ?
run this query and specify a specific company. look at the move_recent_date column and see if the date is the same for each row and if it is the max date

You can do this by joining on calls twice, the first time being in a subquery where you retrieve the last call date for each company:
SELECT * FROM companies co
LEFT JOIN (SELECT company_id, MAX(created_at) AS last_call FROM calls GROUP BY company_id) AS last_calls ON last_calls.company_id = co.id
LEFT JOIN calls ca ON ca.company_id = last_calls.company_id AND ca.created_at = last_calls.last_call
WHERE co.customer_id = ?
GROUP BY co.id

It looks like I found the answer.
This one gives the correct result, and still fast enough (0.27 seconds)
SELECT co.*, v.*
FROM companies co
LEFT JOIN
(
SELECT
ca.*
FROM calls ca
JOIN
(
SELECT
company_id,
MAX(created_at) AS max_created_at
FROM calls
GROUP BY company_id
) t
ON ca.company_id = t.company_id AND ca.created_at = t.max_created_at
GROUP BY company_id
) v ON co.id = v.company_id
Thanks everybody!

You should make a subselect to get max created from calls and use that as join condition to company and calls tables.
SELECT
co.id AS company_id,
co.name AS company_name,
ca.id AS lastcall_id,
ca.result AS lastcall_result
FROM companies AS co
LEFT JOIN calls AS ca
ON co.id = ca.company_id
INNER JOIN
(
SELECT
company_id,
MAX(created_at) AS max_created_at
FROM calls
GROUP BY company_id
) AS max_created_per_company
ON ca.company_id = max_created_per_company.company_id
AND ca.created_at = max_created_per_company.created_at
WHERE co.customer_id = ?

You could double join to the lastcall table, like in this example:
select companies.id, companies.name , lastcall.id, lastcall.result from companies
inner join (select max(created_at) as lastcall, company_id from calls group by company_id) maxcalls
on (companies.id = maxcalls.company_id)
inner join lastcall on (lastcall = created_at and companies.id = lastcall.company_id)
where customer_id = ?
select companies.* , ca.* from companies
inner join (select max(created_at) as lastcall, company_id from calls group by company_id) maxcalls on (companies.id = maxcalls.company_id)
inner join calls ca on (maxcalls.lastcall = ca.created_at and companies.id = ca.company_id)
where customer_id = ?

Related

fetch id who have register all adv

There are 3 types of adventures for which I used distinct function in query.
There is only one 1 customer who have booked all types of adventures.
The query i used to fetch the data is:
select c.customerid,c.name
from customer c
inner join booking b
on c.customerid = b.customerid
inner join destination d
on b.destinationid=d.destinationid
inner join adventure a
on d.adventureid=a.adventureid
group by c.customerid
having count(distinct b.bid)=(select count(*) from bid)
or count(distinct a.adventuretype)=(
select count(distinct a.adventuretype)
from adventure
)
You can get the customer ids using aggregation and having:
select b.customerid
from booking b join
destination d
on b.destinationid = d.destinationid join
adventure a
on d.adventureid = a.adventureid
group by b.customerid
having count(distinct a.advtype) = 3;
Or, if you don't want to hardcode the "3", you can use:
having count(distinct a.advtype) = (select count(distinct advtype from adventure)
I'll leave it up to you to add in the customer name (using join, exists, or in).

How to fix query results not showing up?

I have a MySQL query which I want to execute to see who is the employee with the best skill X in a company I work for. To do this I randomly pick a company from my cv_profile (skill_cv_test) and find all users who work there for the same employer. And then I randomly choose a skill I have.
The result should either be zero or a list.
But when testing with PHPMyAdmin I get results where I don't see any row, but the status says there is at least one row.
Here's an example of the message I get: https://imgur.com/bVMH716
I have been trying different structures, even "walling" the query with another query, different joins.
SELECT
DISTINCT(sv.usr_id),
u.first_name AS fn,
u.last_name AS ln,
c.name AS company,
s.name AS skill
FROM
(
SELECT
MAX(last_change) as date,
id,
usr_id,
skill_id
FROM skill_valuations
GROUP BY usr_id, skill_id
ORDER BY date
) sv
LEFT JOIN skill_valuations skv ON skv.last_change = sv.date
INNER JOIN
(
SELECT
DISTINCT(skct.comp_id),
skct.usr_id AS usr_id,
skct.category
FROM skill_cv_test skct
WHERE skct.end_date IS NULL AND skct.comp_id IN (SELECT comp_id FROM (SELECT comp_id FROM skill_cv_test WHERE usr_id = 1 ORDER BY RAND() LIMIT 1) x)
) uqv ON uqv.usr_id = sv.usr_id
INNER JOIN
(
SELECT skill_id
FROM usr_skills
WHERE usr_id = $uid
ORDER BY RAND()
LIMIT 1
) usq ON usq.skill_id = sv.skill_id
LEFT JOIN companies c ON c.id = uqv.comp_id
LEFT JOIN skills s ON s.id = sv.skill_id
LEFT JOIN users u ON u.id = sv.usr_id
As mentioned before, I expect either no results or a result of at least one row.

Using SUM with Joins in MySQL returns unexpected result

I have these tables : customers, customer_invoices, customer_invoice_details, each customer has many invoices, and each invoice has many details.
The customer with the ID 574413 has these invoices :
select customer_invoices.customer_id,
customer_invoices.id,
customer_invoices.total_price
from customer_invoices
where customer_invoices.customer_id = 574413;
result :
customer_id invoice_id total_price
574413 662146 700.00
574413 662147 250.00
each invoice here has two details (or invoice lines) :
first invoice 662146:
select customer_invoice_details.id as detail_id,
customer_invoice_details.customer_invoice_id as invoice_id,
customer_invoice_details.total_price as detail_total_price
from customer_invoice_details
where customer_invoice_details.customer_invoice_id = 662146;
result :
detail_id invoice_id detail_total_price
722291 662146 500.00
722292 662146 200.00
second invoice 662147 :
select customer_invoice_details.id as detail_id,
customer_invoice_details.customer_invoice_id as invoice_id,
customer_invoice_details.total_price as detail_total_price
from customer_invoice_details
where customer_invoice_details.customer_invoice_id = 662147;
result :
detail_id invoice_id detail_total_price
722293 662147 100.00
722294 662147 150.00
I have a problem with this query :
select customers.id as customerID,
customers.last_name,
customers.first_name,
SUM(customer_invoices.total_price) as invoice_total,
SUM(customer_invoice_details.total_price) as details_total
from customers
join customer_invoices
on customer_invoices.customer_id = customers.id
join customer_invoice_details
on customer_invoice_details.customer_invoice_id = customer_invoices.id
where customer_id = 574413;
unexpected result :
customerID last_name first_name invoice_total details_total
574413 terry amine 1900.00 950.00
I need to have the SUM of the total_price of the invoices, and the SUM of the total_price of the details for each customer. In this case I'm supposed to get 950 as total_price for both columns (invoice_total& details_total) but it's not the case. what am I doing wrong & how can I get the correct result please. The answers in similar topics don't have the solution for this case.
When you mix normal columns with aggregate functions (for example SUM), you need to use GROUP BY where you list the normal columns from the SELECT.
The reason for the excessive amount in total_price for invoices is that the SUM is also calculated over each detail row as it is part of the join. Use this:
select c.id as customerID,
c.last_name,
c.first_name,
SUM(ci.total_price) as invoice_total,
SUM((select SUM(d.total_price)
from customer_invoice_details d
where d.customer_invoice_id = ci.id)) as 'detail_total_price'
from customers c
join customer_invoices ci on ci.customer_id = c.id
where c.id = 574413
group by c.id, c.last_name, c.first_name
db-fiddle
I used join against sub queries and then did a sum on the sums
SELECT c.id as customerID,
c.last_name,
c.first_name
SUM(i.sum) as invoice_total,
SUM(d.sum) AS details_total
FROM customers c
JOIN (SELECT id, customer_id, SUM(total_price) AS sum
FROM customer_invoices
GROUP BY id, customer_id) AS i ON i.customer_id = c.id
JOIN (SELECT customer_invoice_id as id, SUM(total_price) AS sum
FROM customer_invoice_details
GROUP BY customer_invoice_id) AS d ON d.id = i.id
WHERE c.id = 574413
GROUP BY c.id, c.name
The issue is in the joining logic. The table customers is used as the driving table in the joins. But in the second join, you are using a derivative key column from the first join, to join with the third tables. This is resulting in a Cartesian output doubling the records from the result from the nth-1 join, which is leading to customer_invoices.total_price getting repeated twice, hence the rolled up value of this field is doubled.
At a high level I feel that the purpose of rolling up the prices is already achieved in SUM(customer_invoice_details.total_price).
But if you have a specific project requirement that SUM(customer_invoices.total_price) should also be obtained and must match with SUM(customer_invoice_details.total_price), then you can do this:
In a separate query, Join customer_invoice_details and customer_invoices. Roll up the pricing fields, and have a result such that you have only one record for one customer ID.
Then use this as a sub-query and join it with the customers table.
You are aggregating along multiple dimensions. This is challenging. I would suggest doing the aggregation along each dimension independently:
select c.id as customerID, c.last_name, c.first_name,
ci.invoice_total,
cid.details_total
from customers c join
(select ci.sum(ci.total_price) as invoice_total
from customer_invoices ci
group by ci.customer_id
) ci
on ci.customer_id = c.id join
(select ci.sum(cid.total_price) as details_total
from customer_invoices ci join
customer_invoice_details cid
on cid.customer_invoice_id = ci.id
group by ci.customer_id
) cid
on cid.customer_id = c.id
where c.id = 574413;
A faster version (for one customer) uses correlated subqueries:
select c.id as customerID, c.last_name, c.first_name,
(select ci.customer_id, sum(ci.total_price) as invoice_total
from customer_invoices ci
where ci.customer_id = c.id
) as invoice_total,
(select ci.customer_id, sum(cid.total_price) as details_total
from customer_invoices ci join
customer_invoice_details cid
on cid.customer_invoice_id = ci.id
where ci.customer_id = c.id
) as details_total
from customers c
where c.id = 574413;

Mysql query involving multiple tables and an OR statement

Sorry in advance for an ambiguous title, but I can't think of a good one.
I have 2 tables:
bookings(booking_id, customer_id)
charges(customer_id, booking_id, amount)
where in charges table, either booking_id or customer_id must be entered. Not both.
I'm trying to get amount that are associated with a customer_id
Because the customer_id's are null sometimes in charges table, I have to use booking_id to acquire customer_id through bookings table.
So, I had a query like this:
SELECT c.amount
FROM charges as c, bookings as b
WHERE (c.customer_id = 1234) OR
(c.customer_id = null AND c.booking_id = b.booking_id AND b.customer_id = 1234)
However, it seems to create an infinite loop.
Any suggestions?
Thanks,
The problem is that you are doing a cross join (cartesian product), based on the structure of your where clause.
A better approach is to use left outer join (and proper join syntax). So, join to the bookings table, if it exists. Then use or in the where clause to look for a match on the customer_id in either place:
SELECT c.amount
FROM charges as c left outer join
bookings as b
on c.booking_id = b.booking_id
WHERE (c.customer_id = 1234) OR (b.customer_id = 1234)
Why you don't use a JOIN ? like
SELECT c.amout
FROM charges AS c
LEFT JOIN bookings AS b ON c.bookin_id = b.booking_id
WHERE c.customer_id = 1234 OR b.customer_id = 1234
SELECT amount, customer_id from charges where customer_id = 1234
UNION
select amount, b.customer_id from charges c, bookings b where b.booking_id = c.booking_id and c.customer_id is null and b.customer_id = 1234

MySQL Query (or Doctrine 1.2 query) - get most recent item from joined table and filter

I am having trouble constructing a query to do the following:
Filter contacts by activity_type_id, only displaying contacts where the most recent activity has the desired activity_type_id or is NULL (no activity)
Tables are structured as follows:
A contact can have many activities of varying types
activity:
id
contact_id
activity_type_id
date
contact:
id
first_name
last_name
I have this so far:
SELECT * FROM (
SELECT c.first_name, c.last_name, a.activity_type_id, MAX(a.date) AS maxdate
FROM contact AS c
LEFT JOIN activity AS a ON a.contact_id = c.id
GROUP BY c.id
ORDER BY c.first_name ASC
) AS act
then adding this to filter:
WHERE activity_type_id = 3 /* <- I would like to filter using this */
However I am getting the wrong activity_type_id for contacts that have multiple activities.
I ultimately would like to use this as a Doctrine 1.2 query but I like to get things working in MySQL first.
Thankyou.
The Final Solution
SELECT c.first_name, c.last_name, a.activity_type_id FROM contact c
LEFT JOIN
(SELECT a1.contact_id, a1.date, a1.activity_type_id FROM activity a1
JOIN (SELECT contact_id, MAX(DATE) DATE FROM activity GROUP BY contact_id) a2
ON a1.contact_id = a2.contact_id AND a1.date = a2.date
) a
ON c.id = a.contact_id
WHERE a.activity_type_id = 2;
The final where cause can be adjusted to return various activity type or set to IS NULL.
DQL 1.2 Compatible version
SELECT * FROM contact c
LEFT JOIN activity ON c.id = contact_id
WHERE ROW (c.id,DATE) IN (SELECT contact_id, MAX(date) date FROM activity GROUP BY contact_id)
AND activity_type_id = 2
Try this query -
edit: a1.activity_type_id is added
SELECT c.first_name, c.last_name FROM contacts c
LEFT JOIN
(SELECT a1.contact_id, a1.date, a1.activity_type_id FROM activity a1
JOIN (SELECT contact_id, MAX(date) date FROM activity GROUP BY contact_id) a2
ON a1.contact_id = a2.contact_id AND a1.date = a2.date
) a
ON c.contact_id = a.contact_id
WHERE a.contact_id IS NULL OR a.activity_type_id = 3;