I am running MySQL Community Server v5.7.40 and have 2 tables: "customers" and "events" created as follows:
create table customers (customer_id int, name varchar(100), primary key (customer_id));
create table events (event_id int, customer_id int, event_date datetime, event_type varchar(100), primary key (event_id), foreign key (customer_id) references customers(customer_id));
with data structured as below:
CUSTOMERS
customer_id | name |
---|---|
1 | Cust1 |
2 | Cust2 |
3 | Cust3 |
EVENTS
event_Id | customer_Id | event_date | event_type |
---|---|---|---|
1 | 1 | 2022-11-30 10:00:00 | 100m |
2 | 1 | 2022-11-30 12:00:00 | High Jump |
3 | 1 | 2022-11-30 12:00:00 | Long Jump |
4 | 2 | 2022-11-29 11:00:00 | 400m |
5 | 2 | 2022-11-28 09:00:00 | 800m |
6 | 3 | 2022-11-27 07:00:00 | Triple Jump |
I have a requirement to create a view where each customer record will be joined to the events table on the latest event record (ordered by event_date, for which there may be duplicates - selecting the first is acceptable here) for that customer, i.e.:
customer_id | customer_name | latest_event_id | latest_event_date | latest_event_type |
---|---|---|---|---|
1 | Cust1 | 2 | 2022-11-30 12:00:00 | High Jump |
2 | Cust2 | 4 | 2022-11-29 11:00:00 | 400m |
3 | Cust3 | 6 | 2022-11-27 07:00:00 | Triple Jump |
(inner or left join are both acceptable - customer records with no matching events will eventually be ignored anyway).
For newer versions of MySQL, I can use "ROW_NUMBER() OVER" + PARTITION BY (similar to Oracle) such as below to achieve this:
WITH ranked_events AS (
SELECT e.*, ROW_NUMBER() OVER (PARTITION BY Customer_Id ORDER BY Event_Date DESC) AS rn
FROM events AS e
)
SELECT c.customer_id,
c.name as customer_name,
re.event_id as latest_event_id,
re.event_date as latest_event_date,
re.event_type as latest_event_type
FROM customers c, ranked_events re
WHERE c.customer_id = re.customer_id and re.rn = 1;
However, for the older version I am stuck using this will not work. I can currently achieve the same result via a select statement using a row_number variable incremented for each row as follows:
with ordered_events as (
select e.*, (@row_number := @row_number + 1) AS rn
from events e,
(SELECT @row_number := 0) AS x
order by customer_id, event_date desc
)
select c.customer_id,
c.name as customer_name,
b.event_id as latest_event_id,
b.event_date as latest_event_date,
b.event_type as latest_event_type
from customers c, (
select customer_id, min(rn) as latest_event_rn
from ordered_events
group by customer_id) a,
ordered_events b
where a.customer_id = b.customer_id
and a.customer_id = c.customer_id
and a.latest_event_rn = b.rn;
If I try to add create or replace view customer_latest_events as
to the above syntax and compile, I get the error
Error Code: 1351. View's SELECT contains a variable or parameter
Is there a way to achieve the desired outcome in a view for this version of MySQL?
Update
As has been pointed out, CTE syntax is also not valid in v5.7.40 - I had wrongly tested that in a v8 environment. The query below achieves the same result without the CTE, but also fails due to the inclusion of variables in the view's SELECT statement:
create or replace view customer_latest_events as
select c.customer_id,
c.name as customer_name,
b.event_id as latest_event_id,
b.event_date as latest_event_date,
b.event_type as latest_event_type
from customers c, (
select customer_id, min(rn) as latest_event_rn
from (
select e.*, (@row_number := @row_number + 1) AS rn
from events e,
(select @row_number := 0) AS x
order by customer_id, event_date desc
) oe
group by customer_id) a, (
select e.*, (@row_number2 := @row_number2 + 1) AS rn
from events e,
(select @row_number2 := 0) AS x
order by customer_id, event_date desc
) b
where a.customer_id = b.customer_id
and a.customer_id = c.customer_id
and a.latest_event_rn = b.rn;
Update 2
This question has been marked as a duplicate of Retrieving the last record in each group - MySQL. However, there are 2 issues present in this question that are not addressed by any of the answers given there.
- The CTE syntax
with x as (
is not valid for the version of MySQL I have specified and thus will not work - I specifically require a view. The workaround use of variables to cater for v5.7 works fine in a select statement only, but fails specifically when used in a create view statement (which is not something addresses in that question or by any of the answers)