Get the last entry of a group SQL - mysql

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

Related

Query items from a history table

I have a "library" history table (Excel like) to see where a book was or is.
id
date
book
action
client
1
2020-12-01
1
toClient
1
2
2020-12-02
1
returned
1
3
2020-12-03
1
toClient
2
4
2020-12-04
2
toClient
2
5
2020-12-05
3
toClient
1
6
2020-12-06
3
returned
1
7
2020-12-07
2
returned
2
8
2020-12-08
2
toClient
1
I am trying to discover how many books and what books are by client 2 (for example).
I have something like that to find everything about the books that are or was by client 2:
SELECT * FROM history WHERE book IN
(SELECT book FROM history WHERE action='toClient' AND client=2 GROUP BY book)
But so, I get also books that aren't anymore on client 2.
Is there a simple way to query items where the last action was "toClient" and not "returned" and the current client is client 2?
Edit: forgot to write that action can be also revision, timeExtension or something other.
To list books that are currently out, you can do:
select h.*
from history h
where
action = 'toClient'
and h.date = (select max(d1.date) from history h1 where h1.book = h.book)
The idea is to filter on the latest event date per book. Then we only retain the row if the corresponding event is "toClient".
This returns the results for all clients. If you want just one client, just add an additional condition to the where clause of the query:
and client = 2
You can also do this with window functions:
select *
from (
select h.*,
rank() over(partition by book order by date desc) rn
from history h
) h
where rn = 1 and action = 'toClient' -- and client = 2

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

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 query - Inner join using only the latest version of an entry

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

Grouping and Accumulating Records at the same time

I am writing a query against an advanced many-to-many table in my database. I call it an advanced table because it is a many-to-many table with and extra field. The table maps data between the fields table and the students table. The fields table holds potential fields that a student can used, kind of like a contact system (i.e. name, school, address, etc). The studentvalues table that I need to query against holds the field id, student id, and the field answer (i.e. studentid=1; fieldid=2; response=Dave Long).
So my table looks like this:
What I need to do is take a few passed in values and create a grouped accumulated report. I would like to do as much in the SQL as possible.
So that data that I have will be the group by field (a field id), the cumulative field (a field id) and I need to group the students by the group by field and then in each group count the amount of students in the cumulative fields.
So for example I have this data
ID STUDENTID FIELDID RESPONSE
1 1 2 *(city)* Wallingford
2 1 3 *(state)* CT
3 2 2 *(city)* Wallingford
4 2 3 *(state)* CT
5 3 2 *(city)* Berlin
6 3 3 *(state)* CT
7 4 2 *(city)* Costa Mesa
8 4 3 *(state)* CA
I am hoping to write one query that I can generate a report that looks like this:
CA - 1 Student
Costa Mesa 1
CT - 3 Students
Berlin 1
Wallingford 2
Is this possible to do with a single SQL statement or do I have to get all the groups and then loop over them?
EDIT Here is the code that I have gotten so far, but it doesn't give the proper stateSubtotal (the stateSubtotal is the same as the citySubtotal)
SELECT state, count(state) AS stateSubtotal, city, count(city) AS citySubtotal
FROM(
SELECT s1.response AS city, s2.response AS state
FROM studentvalues s1
INNER JOIN studentvalues s2
ON s1.studentid = s2.studentid
WHERE s1.fieldid = 5
AND s2.fieldid = 6
) t
GROUP BY city, state
So to make a table that looks like that, I would assume something like
State StateSubtotal City CitySubtotal
CA 1 Costa Mesa 1
CT 3 Berlin 1
CT 3 Wallingford 2
Would be what you want. We can't just group on Response, since if you had a student answer LA for city, and another student that responds LA for state (Louisiana) they would add. Also, if the same city is in different states, we need to first lay out the association between a city and a state by joining on the student id.
edit - indeed, flawed first approach. The different aggregates need different groupings, so really, one select per aggregation is required. This gives the right result but it's ugly and I bet it could be improved on. If you were on SQL Server I would think a CTE would help but that's not an option.
select t2.stateAbb, stateSubtotal, t2.city, t2.citySubtotal from
(
select city, count(city) as citySubTotal, stateAbb from (
select s1.Response as city, s2.Response as StateAbb
from aaa s1 inner join aaa s2 on s1.studentId = s2.studentId
where s1.fieldId = 2 and s2.fieldId=3
) t1
group by city, stateabb
) t2 inner join (
select stateAbb, count(stateabb) as stateSubTotal from (
select s1.Response as city, s2.Response as StateAbb
from aaa s1 inner join aaa s2 on s1.studentId = s2.studentId
where s1.fieldId = 2 and s2.fieldId=3
) t3
group by stateabb
) t4 on t2.stateabb = t4.stateabb