MySQL query - Inner join using only the latest version of an entry - mysql

I have a table, named jobs with various pieces of information. Each job is given a job number (unique id).
There is then another table, named purchaseOrders that has a FK of jobID and a PK of poID.
when a purchase order entry is edited, the old information is saved... meaning, i create a new PO entry (new unique id).
What i'm trying to do, is write one query that selects all fields from "jobs" and all fields from "purchaseOrders" but only the latest poID for that job.
For example:
jobID Name State poID time jobID
========================== ==========================
1 foo fl 1 1:00 1
2 bar ga 2 1:10 1
3 zzz ny 3 1:20 1
4 2:00 2
5 2:01 2
6 2:30 2
7 3:00 3
8 3:40 3
9 3:15 3
How can I run a query that will select all the columns from both tables, but only include the information with the highest poID for the specific jobID?

SELECT a.*, c.*
FROM jobs a
INNER JOIN
(
SELECT jobID, MAX(time) maxVal
FROM purchaseOrders
GROUP BY jobID
) b ON a.jobID = b.jobID
INNER JOIN purchaseOrders c
ON c.jobID = b.JobID AND
c.time = b.maxVal
SQLFiddle Demo

Related

Trying to get latest status for related shipment but the results I receive are incorrect

I am currently working on a project while trying to learn MySQL and I would like to join three tables and get the latest status for each related shipment. Here are the tables I'm working with (with example data):
shipments
id
consignee
tracking_number
shipper
weight
import_no
1
JOHN BROWN
TBA99900000121
AMAZON
1
101
2
HELEN SMITH
TBA99900000190
AMAZON
1
102
3
JACK BLACK
TBA99900000123
AMAZON
1
103
4
JOE BROWM
TBA99900000812
AMAZON
1
104
5
JULIA KERR
TBA99900000904
AMAZON
1
105
statuses
id
name
slug
1
At Warehouse
at_warehouse
2
Ready For Pickup
ready_for_pickup
3
Delivered
delivered
shipment_status (pivot table)
id
shipment_id
status_id
1
1
1
2
2
1
3
3
1
4
4
1
5
5
1
6
1
2
7
2
2
8
3
2
9
4
2
10
5
2
all tables do have created_at and updated_at timestamp columns
Example of the results I'm trying to achieve
slug
shipment_id
status_id
ready_for_pickup
1
2
ready_for_pickup
2
2
ready_for_pickup
3
2
ready_for_pickup
4
2
ready_for_pickup
5
2
Here's the query I wrote to try to achieve what I'm looking for based on examples and research I did during the past couple of days. I find that sometimes there is sometimes a mismatch with the latest status that relates to the shipment
SELECT
statuses.slug AS slug,
MAX(shipments.id) AS shipment_id,
statuses.id AS status_id,
FROM
`shipments`
INNER JOIN `shipment_status` ON `shipment_status`.`shipment_id` = `shipments`.`id`
INNER JOIN `statuses` ON `shipment_status`.`status_id` = `statuses`.`id`
GROUP BY
`shipment_id`
Because we need to reference other fields from the same record that evaluates from the MAX aggregation, you need to do it in two steps, there are other ways, but I find this syntax simpler:
SELECT
shipments.id AS id,
statuses.slug AS slug,
statuses.id AS status_id,
shipment_status.shipment_id as shipment_id
FROM
`shipments`
INNER JOIN `shipment_status` ON `shipment_status`.`shipment_id` = `shipments`.`id`
INNER JOIN `statuses` ON `shipment_status`.`status_id` = `statuses`.`id`
WHERE
shipment_status.id = (
SELECT MAX(shipment_status.id)
FROM `shipment_status`
WHERE shipment_status.shipment_id = shipments.id
)
try it out!
This query makes the assumption that the id field is an identity column, so the MAX(shipment_status.id) represents only the most recent status for the given shipment_id
You can use window functions:
SELECT s.id, st.slug, st.id
FROM shipments s JOIN
(SELECT ss.*,
ROW_NUMBER() OVER (PARTITION BY shipment_id ORDER BY ss.id DESC) as seqnum
FROM shipment_status ss
) ss
ON ss.shipment_id = s.id JOIN
statuses st
ON ss.status_id` = st.id
WHERE ss.seqnum = 1;
Also note the use of table aliases so the query is easier to write and to read.

How to select a row from table1 if the row id isn't present in table2 more than x times

accounts as a1 | team_logs as tl1
--------------------------------------------------------
id Name counter | id team_id user_id account_id
1 Account 1 2 | 1 1 100 1
2 Account 2 2 | 2 2 200 1
3 Account 3 0 | 3 3 300 2
... | 4 2 200 2
This is an account review app. Based on the 2 tables above a query is needed that will output 1 account from a1 table based on the tl1 records as below:
A team member is requesting an account, and once an account is assigned to him a log entry is made in tl1 that an account_id is assigned to him.
An account can be assigned to a Team only once.
An account can be assigned to x teams (In the above example we have only 3 teams).
An record can be reviewed x times(In the example above it can be reviewed 3 times).
I had a project where I had only 3 teams and each teams logs were stored in its own table, and I had this query which worked:
Example for Team1
SELECT `a1`.*
FROM `accounts` AS `a1`
LEFT JOIN `team1_logs` AS `tl1` ON tl1.account_id = a1.id
WHERE (tl1.account_id IS NULL)
AND (a1.counter < '3')
ORDER BY RAND()
LIMIT 1
a1 has a counter column which has a value that represents the number of times a row was shown to teams. Now my project can house x teams, we made the teams dynamic, so making a table log for each team isn't an option.
So in the above tables if i want an account to be reviewed(assigned to a team member) 3 times.
Account 1 can be reviewed 1 more time by any team that isn't 1 and 2
Account 2 can be reviewed 1 more time by any team that isn't 2 and 3
What would my new query need to look like if i want to get the next first available record, based on the 1-4 criteria from above?
The data in Table 2 is more than enough, you don't need to know any other
data to make the needed query.
team_id is an query input (since we need to output an account to the team
member)
Answer
Assuming that I am a team member of team 1
SELECT DISTINCT a.*
FROM accounts AS a
LEFT JOIN (
SELECT account_id, team_id FROM team_logs) AS tl1 ON a.id = tl1.account_id
WHERE a.id NOT IN (
SELECT account_id FROM team_logs WHERE team_id =1)
AND a.counter < 3
ORDER BY a.id ASC
If you just want to see which teams are not allowed to review the account again, join with a subquery that uses GROUP_CONCAT to get the list of teams that have reviewed it.
SELECT a.*, 3 - counter AS remaining_reviews, IFNULL(tl.already_reviewed, '') AS already_reviewed
FROM accounts AS a
LEFT JOIN (
SELECT account_id, GROUP_CONCAT(team_id ORDER BY team_id) AS already_reviewed
FROM team_logs
GROUP BY account_id) AS tl ON a.id = tl.account_id
WHERE a.counter < 3
DEMO

Get the last entry of a group SQL

I have in my database 3 tables. One for the Apps, other for the historic of the state of the apps and a last table with the description of each state.
Table Apps:
ID Name
1 App1
2 App2
Table Historic:
ID IDApp IDState DateChanged
1 1 2 2016-06-01
2 1 4 2016-06-07
3 2 1 2016-06-05
4 2 2 2016-06-12
Table State:
ID Description
1 Open
2 Working
3 Pending
4 Closed
I want a query that returns the last state of each App. I want the return like this:
Name Description Date
App1 Closed 2016-06-07
App2 Working 2016-06-12
You should consider making your DateChanged field a date/time field if there can be multiple possible states witnessed for a given app on a single day. As-is this should work but state reporting for a given app for a given day will arbitrarily choose the status with the highest ID if there are > 1 status witnessed for a given app on a the most recent day reported in history.
SELECT a.Name,
COALESCE(s.Description, '(No History)') as Description,
h.DateChanged as Date
FROM Apps a LEFT JOIN (
SELECT IDApp,
MAX(IDState) as IDState, -- arbitrary tie breaker for > 1 state in a day
DateChanged
FROM Historic h1 INNER JOIN (
SELECT IDApp, MAX(DateChanged) as MaxDateChanged
FROM Historic
GROUP BY IDApp
) h2 ON h1.IDApp = h2.IDApp
AND h1.DateChanged = h2.MaxDateChanged
GROUP BY IDApp, DateChanged
) h ON a.ID = h.IDApp
LEFT JOIN State s ON s.ID = h.IDState
SELECT Name, Description, MAX(DateChanged) from Apps
INNER JOIN Historic ON Apps.ID=Historic.IDapp
INNER JOIN State ON State.ID=Historic.IDState
GROUP BY Name, Description

Mysql query to fetch records based on max order

To be more specific I have 2 tables:
1. product table:
id pname
1 camera
2 tv
3 fridge
2. Approval table:
id user_id order status
2 suneel 1 approved
2 raj 2 approved
2 kumar 3 pending
3 suneel 1 approved
3 raj 2 pending
3 kumar 3 pending
3 xxxx 4 pending
Each product will go through an approval chain in the order. So I am looking for a query that will list the records that are pending and the next approval order item.
The query will display only one record for each item.
Expected output:
id pname user_id order status
2 tv kumar 3 pending
3 fridge raj 2 pending
If I understand your logic correctly I would start with a query like this that will return the maximum order for every approval:
SELECT id, MAX(`order`) as max_approval
FROM approval
WHERE status='approved'
GROUP BY id
then you can join it back to the original table:
SELECT
a.id,
p.pname,
max_approval as `order`,
a2.user_id,
a2.status
FROM
(
SELECT id, MAX(`order`) as max_approval
FROM approval
WHERE status='approved'
GROUP BY id
) a INNER JOIN product
ON a.id = product.id
LEFT JOIN approval a2
ON a.id = a2.id AND a.max_approval=a2.`order`-1
this will return the next row after the status approved, but this does not consider the fact that a product could not be approved, or can just be approved and never pending. Depending on the logic of your application this might or might not be a problem.

MySQL Join Multiple (More than 2) Tables with Conditions

Assume I have 4 tables:
Table 1: Task
ID Task Schedule
1 Cut Grass Mon
2 Sweep Floor Fri
3 Wash Dishes Fri
Table 2: Assigned
ID TaskID (FK) PersonID (FK)
1 1 1
2 1 2
3 2 3
4 3 2
Table 3: Person
ID Name
1 Tom
2 Dick
3 Harry
Table 4: Mobile
ID PersonID (FK) CountryCode MobileNumber
1 1 1 555-555-5555
2 2 44 555-555-1234
3 3 81 555-555-5678
4 3 81 555-555-0000
I'm trying to display the
Task on a certain day
Name of person assigned to task
Phone numbers of said person
I think it should be something like the following, but I'm not sure how to set up the conditions so that the results are limited correctly:
SELECT T.ID, T.Task, P.Name, M.MobileNumber
FROM Task AS T
LEFT JOIN Assigned AS A
ON T.ID = A.TaskID
LEFT JOIN Person AS P
ON A.PersonID = P.ID
LEFT JOIN Mobile AS M
ON M.PersonID = P.ID
WHERE T.Schedule = Fri
My goal is to fetch the following information (it will be displayed differently):
Tasks Name MobileNumber
Sweep Floor, Wash Dishes Dick, Harry 44-555-555-1234, 81-555-555-5678, 81-555-555-0000
Of course, if JOIN is the wrong way to do this, please say so.
It's unclear what you want to do with duplicate data in this case, but you should be looking at using inner joins instead of outer joins, and using something like group_concat() to combine the phone numbers.