How to show latest record per group in MySQL - mysql

I have the following MySQL query:
SELECT *
FROM person person
LEFT OUTER JOIN (employee_location employee_location
INNER JOIN location_status location_status
ON employee_location.type = location_status.status_id )
ON person.per_id = employee_location.person_id
ORDER BY person.per_given
Which gives me the following result:
I want to be able to show only the latest record for each person:
Am I able to do this just by adding something to the MySQL query above?
My schema:
Table = Columns
person = per_id, per_given
employee_location = id, person_id, type, date_time
location_status = status_id, status_type

One method is a correlated subquery. I can speculate on what your tables look like, so I'm guessing the query looks like this:
SELECT *
FROM person p JOIN
employee_location el
ON el.person_id = p.per_id JOIN
location_status ls
ON el.type = ls.status_id
WHERE el.date_time = (SELECT MAX(el2.date_time)
FROM employee_location el2
WHERE el2.per_id = el.per_id
)
ORDER BY p.per_given

You could use MAX():
SELECT per_id, MAX(date_time) FROM person GROUP BY per_id;

Related

Joining a second table and selecting the first entry

I have been having some troubles getting my head around achieving the below.
I have an 'applications' table and a 'application_logs' table. I am attempting to select all the applications where the 'type' is equal to 'test' and then join the 'application_logs' table and retrieve only the first log entry for the application.
One of the queries I tried and understood most was: (whilst this didn't fail it looked like an endless loop and completed the query.
SELECT applications.id FROM applications JOIN application_logs ON application_logs.application_id =
(
SELECT application_logs.id FROM application_logs
WHERE application_logs.application_id = applications.id
ORDER BY id DESC
LIMIT 1
)
WHERE type = 'test';
There were some other queries (using CROSS APPLY/distinct) I attempted but they didn't make sense to me and didn't look like they were trying to achieve the same thing. I appreciate all the help :)
There are many ways to achieve this in standard SQL. A lateral join (CROSS APPLY) is the first to come to mind, but MySQL doesn't support it. Another would be FETCH FIRST ROWS WITH TIES to get all latest application logs, but MySQL doesn't support it (and its counterpart LIMIT doesn't have a ties clause either). If you are only interested in the application ID alone (as shown in your query) one could even combine this with an INTERSECT operation, but MySQL doesn't support it.
After all you want to find out the maximum log ID per application ID. As of MySQL 8 you can do this on-the-fly:
select *
from applications a
left join
(
select
application_logs.*,
max(id) over (partition by application_id) as max_application_id
from application_logs
) al on al.application_id = a.id and al.application_id = al.max_application_id
where a.type = 'test';
In earlier vesions you would do this in separate steps. One way would be this:
select *
from applications a
left join application_logs al
on al.application_id = alm.application_id
and (al.application_id, al.id) in
(
select application_id, max(id)
from application_logs
group by application_id
)
where a.type = 'test';
Another is your own query, where you only got confused with the IDs:
SELECT *
FROM applications a
JOIN application_logs al ON al.id =
(
SELECT almax.id
FROM application_logs almax
WHERE almax.application_id = a.id
ORDER BY almax.id DESC
LIMIT 1
)
WHERE a.type = 'test';
try this
SELECT
a.*, c.*
FROM
applications a
INNER JOIN (
SELECT
al.application_id AS application_id,
MAX(al.id) AS al_id
FROM
application_logs al
GROUP BY
al.application_id
) c ON a.id = c.application_id
WHERE
a.type = 'test';
To get the first entry from application_logs table with respect to application_id. You need to use Row_Number Over (partition by order by ).
SELECT *
FROM applications A
LEFT JOIN (
SELECT Id AS applications.id
FROM (
SELECT ROW_NUMBER() OVER (Partition By applications.id ORDER BY application_logs.id) as R, application_logs.id, applications.id
FROM application_logs
) AS S
WHERE R = 1
) AS L ON L.applications.id = A.applications.id
Try this -
SELECT a.id, ay.*
FROM applications AS a
INNER JOIN (
SELECT al.application_id, min(al.id) as Min_Id
FROM application_logs AS al
GROUP BY al.application_id
) AS ax ON ax.application_id = a.id
INNER JOIN application_logs AS ay ON ay.id = ax.id
WHERE a.type = 'test';
Your query suggests that "first" means the largest id. (Colloquially, I would expect "first" to mean the smallest id or earliest chronologically.)
I usually recommend a correlated subquery for the filtering. It is worth testing if this is faster than other methods:
select . . .
from applications a join
application_logs al
on al.application_id = a.id
where a.type = 'test' and
al.id = (select max(al2.id)
from application_logs al2
where al2.application_id = al.application_id
);
The optimal indexes for performance are:
application(type, id)
application_logs(application_id, id)

LEFT JOIN SUM with WHERE clause

The following query always outputs SUM for all rows instead of per userid. Not sure where else to look. Please help.
SELECT * FROM assignments
LEFT JOIN (
SELECT SUM(timeworked) AS totaltimeworked
FROM time_entries
) assignments ON (userid = assignments.userid AND ticketid = ?)
WHERE ticketid = ?
ORDER BY assigned,scheduled
If you want to keep the SELECT *, you would have to add a group by clause in the subquery. Something like this
SELECT * FROM assignments
LEFT JOIN (
SELECT SUM(timeworked) AS totaltimeworked
FROM time_entries
GROUP BY userid
) time_entriesSummed ON time_entriesSummed.userid = assignments.userid
WHERE ticketid = ?
ORDER BY assigned,scheduled
But a better way would be to change the SELECT * to instead select the fields you want a add a group by clause directly. Something like this
SELECT
assignments.id,
assignments.assigned,
assignments.scheduled,
SUM(time_entries.timeworked) AS totalTimeworked
FROM assignments
LEFT JOIN time_entries
ON time_entries.userid = assignments.userid
GROUP BY assignments.id, assignments.assigned, assignments.scheduled
Edit 1
Included table names in query 2 as mentioned in chameera's comment below

MySQL Inner Join with where clause sorting and limit, subquery?

Everything in the following query results in one line for each invBlueprintTypes row with the correct information. But I'm trying to add something to it. See below the codeblock.
Select
blueprintType.typeID,
blueprintType.typeName Blueprint,
productType.typeID,
productType.typeName Item,
productType.portionSize,
blueprintType.basePrice * 0.9 As bpoPrice,
productGroup.groupName ItemGroup,
productCategory.categoryName ItemCategory,
blueprints.productionTime,
blueprints.techLevel,
blueprints.researchProductivityTime,
blueprints.researchMaterialTime,
blueprints.researchCopyTime,
blueprints.researchTechTime,
blueprints.productivityModifier,
blueprints.materialModifier,
blueprints.wasteFactor,
blueprints.maxProductionLimit,
blueprints.blueprintTypeID
From
invBlueprintTypes As blueprints
Inner Join invTypes As blueprintType On blueprints.blueprintTypeID = blueprintType.typeID
Inner Join invTypes As productType On blueprints.productTypeID = productType.typeID
Inner Join invGroups As productGroup On productType.groupID = productGroup.groupID
Inner Join invCategories As productCategory On productGroup.categoryID = productCategory.categoryID
Where
blueprints.techLevel = 1 And
blueprintType.published = 1 And
productType.marketGroupID Is Not Null And
blueprintType.basePrice > 0
So what I need to get in here is the following table with the columns below it so I can use the values timestamp and sort the entire result by profitHour
tablename: invBlueprintTypesPrices
columns: blueprintTypeID, timestamp, profitHour
I need this information with the following select in mind. Using a select to show my intention of the JOIN/in-query select or whatever that can do this.
SELECT * FROM invBlueprintTypesPrices
WHERE blueprintTypeID = blueprintType.typeID
ORDER BY timestamp DESC LIMIT 1
And I need the main row from table invBlueprintTypes to still show even if there is no result from the invBlueprintTypesPrices. The LIMIT 1 is because I want the newest row possible, but deleting the older data is not a option since history is needed.
If I've understood correctly I think I need a subquery select, but how to do that? I've tired adding the exact query that is above with a AS blueprintPrices after the query's closing ), but did not work with a error with the
WHERE blueprintTypeID = blueprintType.typeID
part being the focus of the error. I have no idea why. Anyone who can solve this?
You'll need to use a LEFT JOIN to check for NULL values in invBlueprintTypesPrices. To mimic the LIMIT 1 per TypeId, you can use the MAX() or to truly make sure you only return a single record, use a row number -- this depends on whether you can have multiple max time stamps for each type id. Assuming not, then this should be close:
Select
...
From
invBlueprintTypes As blueprints
Inner Join invTypes As blueprintType On blueprints.blueprintTypeID = blueprintType.typeID
Inner Join invTypes As productType On blueprints.productTypeID = productType.typeID
Inner Join invGroups As productGroup On productType.groupID = productGroup.groupID
Inner Join invCategories As productCategory On productGroup.categoryID = productCategory.categoryID
Left Join (
SELECT MAX(TimeStamp) MaxTime, TypeId
FROM invBlueprintTypesPrices
GROUP BY TypeId
) blueprintTypePrice On blueprints.blueprintTypeID = blueprintTypePrice.typeID
Left Join invBlueprintTypesPrices blueprintTypePrices On
blueprintTypePrice.TypeId = blueprintTypePrices.TypeId AND
blueprintTypePrice.MaxTime = blueprintTypePrices.TimeStamp
Where
blueprints.techLevel = 1 And
blueprintType.published = 1 And
productType.marketGroupID Is Not Null And
blueprintType.basePrice > 0
Order By
blueprintTypePrices.profitHour
Assuming you might have the same max time stamp with 2 different records, replace the 2 left joins above with something similar to this getting the row number:
Left Join (
SELECT #rn:=IF(#prevTypeId=TypeId,#rn+1,1) rn,
TimeStamp,
TypeId,
profitHour,
#prevTypeId:=TypeId
FROM (SELECT *
FROM invBlueprintTypesPrices
ORDER BY TypeId, TimeStamp DESC) t
JOIN (SELECT #rn:=0) t2
) blueprintTypePrices On blueprints.blueprintTypeID = blueprintTypePrices.typeID AND blueprintTypePrices.rn=1
You don't say where you are putting the subquery. If in the select clause, then you have a problem because you are returning more than one value.
You can't put this into the from clause directly, because you have a correlated subquery (not allowed).
Instead, you can put it in like this:
from . . .
(select *
from invBLueprintTypesPrices ibptp
where ibtp.timestamp = (select ibptp2.timestamp
from invBLueprintTypesPrices ibptp2
where ibptp.blueprintTypeId = ibptp2.blueprintTypeId
order by timestamp desc
limit 1
)
) ibptp
on ibptp.blueprintTypeId = blueprintType.TypeID
This identifies the most recent records for all the blueprintTypeids in the subquery. It then joins in the one that matches.

MySQL views cannot have subquery in from, and don't maintain order. How can I create a view from this query?

I'm essentially trying to obtain a resultset with each employee's current title. I'd like to create a view from this for later use, but I find I'm being stumped, and likely missing a simple solution. Here's the query in question, and thanks in advance!
select * from
(SELECT
appointment.employee_id,
title.`name` as title_name
FROM
appointment
INNER JOIN appointment_title ON appointment.id = appointment_title.appointment_id
INNER JOIN title ON appointment_title.title_id = title.id
order by appointment_title.effective_date DESC) tmp group by employee_id
Updated:
SELECT
appointment.employee_id ,
( SELECT title.`name`
FROM appointment_title
INNER JOIN title
ON appointment_title.title_id = title.id
WHERE appointment.id = appointment_title.appointment_id
ORDER BY appointment_title.effective_date DESC
LIMIT 1
) AS title_name
FROM appointment
GROUP BY appointment.employee_id
Another option is to break up the query into two views. The first view will contain the derived table subquery, and the second will simply select from that one:
CREATE VIEW vwEmployee_Inner AS
SELECT
appointment.employee_id,
title.`name` as title_name
FROM
appointment
INNER JOIN appointment_title ON appointment.id = appointment_title.appointment_id
INNER JOIN title ON appointment_title.title_id = title.id
order by appointment_title.effective_date DESC
And then your original view becomes:
CREATE VIEW vwEmployee AS
SELECT * FROM vwEmployee_Inner GROUP BY employee_id

MySQL Query Help, including a count?

Any ideas why this isn't working in MySQL?
SELECT blogentry.*,
person.personName,
(SELECT *
FROM BlogEntryComment
Where BlogEntryID = '8') as CommentCount
FROM blogentry
INNER JOIN person ON blogentry.personID = person.personID
WHERE blogentry.deleted = 'N'
ORDER BY blogentry.dateAdded DESC
The subquery needs to return only one value: the field count. * returns all rows, whereas count(*) will return how many there are.
(SELECT count(*) FROM BlogEntryComment Where BlogEntryID = '8')
You have to use the aggregate function COUNT in order to get the value - SELECT * in a subSELECT will frail, because it is attempting to return all the column values for a row into a single column.
That said, what you have will return the same CommentCount value for every BLOGENTRY record returned. The following is a better approach:
SELECT be.*,
p.personname,
COALESCE(x.num, 0) AS CommentCount
FROM BLOGENTRY be
JOIN PERSON p ON p.personid = be.personid
LEFT JOIN (SELECT bec.blogentryid,
COUNT(*) AS num
FROM BLOGENTRYCOMMENT bec
GROUP BY bec.blogentryid) x ON x.blogentryid = be.blogentryid