SQL Join to get most recent record - sql

I have three tables:
Measurements (MeasureID, Time, Distance, Value)
Events(EventID, Time Value)
EventValues (EventDataID, EventID, Type, Value)
I need to get for every measurement, the most recent event (in the past) and its associated eventvalues data.
My current query is quite ugly:
SELECT
M.*,
(SELECT TOP 1 EV.value FROM [Event] E JOIN EventValues EV ON E.EventID = EV.EventID
WHERE M.Time >= E.Time ORDER BY M.Time-E.Time) AS Data,
FROM [Measure] M
ORDER BY M.Distance
and it only allows me to select one column from the EventValues table (I need more)
Is there any way this can be done using a join?
EDIT: I also need to select ALL entries from the measurement table even if they are before the first event (i.e. just select null data for the join)

You can use CROSS APPLY.
SELECT
M.*, Data.*
FROM [Measure] M
CROSS APPLY
(SELECT TOP 1 EV.* FROM [Event] E JOIN EventValues EV ON E.EventID = EV.EventID
WHERE M.Time >= E.Time ORDER BY E.Time DESC) AS Data
ORDER BY M.Distance

Try something like this (not tested)
SELECT * FROM
(
SELECT M.*, E.*, EV.EventDataID, EV.Type, EV.Value,
Rank() over (Partition BY M.MeasureID order by M.Time - E.Time) as Rank
FROM [Measure] M
INNER JOIN [Event] E ON M.Time >= E.Time
INNER JOIN EventValues EV ON E.EventID = EV.EventID
) T
WHERE Rank = 1
EDIT
SELECT * FROM
(
SELECT M.*, E.*, EV.EventDataID, EV.Type, EV.Value,
Rank() over (Partition BY M.MeasureID order by M.Time - E.Time) as Rank
FROM [Measure] M
LEFT JOIN [Event] E ON M.Time >= E.Time
LEFT JOIN EventValues EV ON E.EventID = EV.EventID
) T
WHERE Rank = 1

Related

Left join tables in sqlite ordered by desc limit

I want to left join two tables in sqlite. Short summary:
I have a table named "_Menu", one of the fields in this table is named "menu_id" with unique numbers.
Another table is called "_Approvals". This table has a history of which items has been "approved" or "unapproved". This table also has a field named "menu_id".
I want to get the lowest row (if there is any) from "_Approvals" for a given menu_id and join the two tables.
What I have so far is:
SELECT m.menu_id, m.p_id AS parent_id, m.name, m.url, a.status, a.auth
FROM _Menu AS m
LEFT JOIN (
SELECT * FROM _Approvals ORDER BY _Approvals.approval_id DESC LIMIT 1) as a
ON a.menu_id = m.menu_id
GROUP BY m.menu_id
ORDER BY m.menu_id
My problem is only the absolute last row in "_Approvals" gets joined. I.e I only know the status of the last item approved/unapproved.
Any help is greatly appreciated!
You want the lowest row (if there is any) from "_Approvals" for a given menu_id and not the the lowest row from "_Approvals" which is what your code does.
One way to do what you need is by using NOT EXISTS in the subquery that you join:
SELECT m.menu_id, m.p_id AS parent_id, m.name, m.url, a.status, a.auth
FROM _Menu AS m
LEFT JOIN (
SELECT * FROM _Approvals t
WHERE NOT EXISTS (
SELECT 1 FROM _Approvals
WHERE menu_id = t.menu_id AND approval_id > t.approval_id
)
) AS a
ON a.menu_id = m.menu_id
GROUP BY m.menu_id
ORDER BY m.menu_id
Another way with a CTE:
WITH cte AS (
SELECT t.* FROM _Approvals t
INNER JOIN (
SELECT menu_id, MAX(approval_id)
FROM _Approvals
GROUP BY menu_id
) g
ON g.menu_id = t.menu_id
)
SELECT m.menu_id, m.p_id AS parent_id, m.name, m.url, a.status, a.auth
FROM _Menu AS m LEFT JOIN cte AS a
ON a.menu_id = m.menu_id
GROUP BY m.menu_id
ORDER BY m.menu_id
Or with window function ROW_NUMBER() if your version of SQLite is 3.25.0+:
WITH cte AS (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY menu_id ORDER BY approval_id DESC) rn
FROM _Approvals
)
SELECT m.menu_id, m.p_id AS parent_id, m.name, m.url, a.status, a.auth
FROM _Menu AS m LEFT JOIN (
SELECT * FROM cte
WHERE rn = 1
) AS a
ON a.menu_id = m.menu_id
GROUP BY m.menu_id
ORDER BY m.menu_id
I would recommend writing this using row_number():
SELECT m.menu_id, m.p_id AS parent_id, m.name, m.url,
a.status, a.auth
FROM _Menu m LEFT JOIN
(SELECT a.*,
ROW_NUMBER() OVER (PARTITION BY a.menu_id ORDER BY a.approval_id DESC) as seqnum
FROM _Approvals a
) a
ON a.menu_id = m.menu_id AND a.seqnum = 1
ORDER BY m.menu_id;
No aggregation should be needed for the query, assuming that menu_id is the primary key in _Menu.
If you are using an old version of SQLite that doesn't support window functions, then there are several options. Probably the simplest is:
SELECT m.menu_id, m.p_id AS parent_id, m.name, m.url,
a.status, a.auth
FROM _Menu m LEFT JOIN
_Approvals a
ON a.menu_id = m.menu_id LEFT JOIN
(SELECT a.menu_id, MAX(a.approval_id) as max_approval_id
FROM _Approvals a
GROUP BY a.menu_id
) aa
ON aa.menu_id = a.menu_id AND
aa.max_approval_id = a.approval_id
ORDER BY m.menu_id;

Aliasing a table in a window function?

I am trying to alias a table in a window function, but not sure what I am doing wrong as when I alias it gives error that the columns cannot be resolved
SELECT e.city,
e.time,
e.day,
e.id,
m.id
FROM
(SELECT *,
rank() OVER (PARTITION BY e.id,
e.bin
ORDER BY e.time ASC) rnk
FROM table e
JOIN table2 m
on m.id = e.id
WHERE e.status = 'YES'
AND e.day BETWEEN date '2019-05-06' and date '2019-05-08')
WHERE rnk = 1
You have used the e alias in the outermost select. However, there is nothing in scope with that alias. The inner from doesn't "reach out" like that (scopes do "reach in" the other way though).
So:
SELECT e.city, e.time, e.day, e.id
FROM (SELECT e.*,
rank() OVER (PARTITION BY e.id, e.bin ORDER BY e.time ASC) as rnk
FROM table e
WHERE e.status = 'YES' AND
e.day BETWEEN date '2019-05-06' and date '2019-05-08'
) e
-------^ here
WHERE rnk = 1

Find lowest number from column in table

I have 3 tables - EVENT, MEMBER, RANK. I need to show the best result (from RANK) along with the member it belongs to, for a specific event (ex EVENT01) What is the simplest way to do this? My code below seems to select the lowest number, but duplicates itself and claims other members got the same result.
Expected output:
EVENT_ID EVENT_TYPE EVENT_NAME MEMBER_ID MEMBER_NAME RESULT
event01 swimming 100m mem001 John Smith 10
Code so far:
SELECT E.EVENT_ID, E.EVENT_TYPE, E.EVENT_NAME, R.MEMBER_ID, M.MEMBER_FIRSTNAME, M.MEMBER_LASTNAME, (SELECT MIN(RESULT)
FROM RANK WHERE E.EVENT_ID = 'EVENT003' ) AS AVG_INCOME_ALL_CLUBS
FROM EVENT E, RANK R, MEMBER M
WHERE E.EVENT_ID = R.EVENT_ID
AND R.MEMBER_ID = M.MEMBER_ID
ORDER BY MEMBER_ID;
First, learn to use explicit JOIN syntax.
Second, the answer to your question is ROW_NUMBER():
SELECT *
FROM (SELECT E.EVENT_ID, E.EVENT_TYPE, E.EVENT_NAME, R.MEMBER_ID, M.MEMBER_FIRSTNAME, M.MEMBER_LASTNAME,
ROW_NUMBER() OVER (PARTITION BY E.EVENT_ID ORDER BY R.RESULT ASC) as SEQNUM
FROM EVENT E, JOIN
RANK R
ON E.EVENT_ID = R.EVENT_ID JOIN
MEMBER M
ON R.MEMBER_ID = M.MEMBER_ID
) ERM
WHERE SEQNUM = 1
ORDER BY MEMBER_ID;
SELECT E.EVENT_ID, E.EVENT_TYPE, E.EVENT_NAME, R.MEMBER_ID,M.MEMBER_FIRSTNAME, M.MEMBER_LASTNAME,
FROM EVENT E, JOIN
RANK R
ON E.EVENT_ID = R.EVENT_ID JOIN
MEMBER M
ON R.MEMBER_ID = M.MEMBER_ID
where R.RESULT=
(SELECT MIN(RESULT) FROM RANK WHERE E.EVENT_ID = 'EVENT003') AND R.EVENT_ID='EVENT003';

Select lowest number from column and show other columns

I need to find the lowest number in my RESULT column which I know can be done with MIN but I am not sure how to do this as well as select my other columns. My code displays all the results for all members but I just want it to show the best result (lowest number) along with the other details for that member.
Right now I have multiple entries that look like this:
EVENT_ID EVENT_TYPE EVENT_NAME MEMBER_ID MEMBER_FIRSTNAME MEMBER_LASTNAME RESULT
event1 track 100M 0001 John Smith 11.3
I just need to select the row with the lowest result and show that one only. Here is my code so far:
SELECT E.EVENT_ID, E.EVENT_TYPE, E.EVENT_NAME, R.MEMBER_ID, M.MEMBER_FIRSTNAME, M.MEMBER_LASTNAME, R.RESULT
FROM EVENT E, MEMBER M, RANK R
WHERE E.EVENT_ID = R.EVENT_ID
AND R.MEMBER_ID = M.MEMBER_ID;
SGEDDES - I tried following your last method and couldn't get it to work:
SELECT E.EVENT_ID, E.EVENT_TYPE, E.EVENT_NAME, R.MEMBER_ID, M.MEMBER_FIRSTNAME, M.MEMBER_LASTNAME, R.RESULT
FROM (EVENT E, MEMBER M, RANK R
WHERE E.EVENT_ID = R.EVENT_ID
AND R.MEMBER_ID = M.MEMBER_ID;
ORDER BY RESULT)
WHERE ROWNUM = 1;
Here's one option using row_number (also please note the join syntax -- in general you shouldn't use commas in the from clause):
SELECT *
FROM (
SELECT E.EVENT_ID, E.EVENT_TYPE, E.EVENT_NAME,
R.MEMBER_ID, M.MEMBER_FIRSTNAME, M.MEMBER_LASTNAME, R.RESULT,
row_number() over (order by R.RESULT) rn
FROM EVENT E JOIN RANK R ON E.EVENT_ID = R.EVENT_ID
JOIN MEMBER M ON R.MEMBER_ID = M.MEMBER_ID
) t
WHERE rn = 1
If you need to group the results by a specific field(s), you an use partition by. For example:
row_number() over (partition by e.event_id order by result) rn
And here's another option using rownum:
SELECT *
FROM (<<YOUR QUERY HERE>> ORDER BY R.Result )
WHERE ROWNUM = 1;
In the most recent versions of Oracle, you can use fetch first 1 row only:
SELECT E.EVENT_ID, E.EVENT_TYPE, E.EVENT_NAME, R.MEMBER_ID,
M.MEMBER_FIRSTNAME, M.MEMBER_LASTNAME, R.RESULT
FROM EVENT E JOIN
RANK R
ON E.EVENT_ID = R.EVENT_ID JOIN
MEMBER M
ON R.MEMBER_ID = M.MEMBER_ID
ORDER BY R.RESULT
FETCH FIRST 1 ROW ONLY;
If you have a less recent version of Oracle, then one of sgeddes's solutions is fine.

Find average score from event at specific meeting

I need to find the average score from everyone that took part in a high jump event at a specific event (ex event01)
My tables are MEETING, EVENT, RANK and my broken code is below:
SELECT *
FROM(
SELECT M.MEETING_ID, M.MEETING_NAME, E.EVENT_ID, E.EVENT_NAME,
ROW_NUMBER() OVER (ORDER BY R.RESULT) RN, (SELECT AVG(RESULT) FROM RANK WHERE E.EVENT_NAME = 'HIGH JUMP' AND M.MEETING_ID = 'MEET0002' ) AS HG
FROM MEETING M JOIN RANK R ON M.MEETING_ID = R.MEETING_ID
JOIN EVENT E ON E.EVENT_ID = R.EVENT_ID
)
WHERE RN = 1
Expected outcome:
MEETING_ID MEETING_NAME EVENT_ID EVENT_NAME AVERAGESCORE
meet01 sports day event01 high jump 10.6
Really appreciate any help!
This is just an aggregation query with filtering:
SELECT M.MEETING_ID, M.MEETING_NAME, E.EVENT_ID, E.EVENT_NAME,
AVG(RESULT) AS HG
FROM MEETING M JOIN
RANK R
ON M.MEETING_ID = R.MEETING_ID JOIN
EVENT E
ON E.EVENT_ID = R.EVENT_ID
WHERE E.EVENT_NAME = 'HIGH JUMP' AND M.MEETING_ID = 'MEET0002'
GROUP BY M.MEETING_ID, M.MEETING_NAME, E.EVENT_ID, E.EVENT_NAME;
You need the GROUP BY to include the first four columns in the result set.
Why filter at WHERE clause when you can filter things at JOIN itself.
SELECT M.MEETING_ID, M.MEETING_NAME, E.EVENT_ID, E.EVENT_NAME,
AVG(RESULT) over (partition by E.EVENT_ID) AS avg
FROM EVENT E
JOIN RANK R
ON E.EVENT_ID = R.EVENT_ID AND E.EVENT_ID = '001' AND E.EVENT_NAME='HIGH JUMP'
JOIN
MEETING M
ON M.MEETING_ID = R.MEETING_ID;