SELECT max date from an inner join relation - sql

I have 2 tables, Staff and updateStaff.
Staff:
Sid Sname
---|--------|
1 | test1 |
2 | test2 |
3 | test3 |
4 | test4 |
5 | test5 |
updateStaff:
Sid Sprice SDate STime
---|--------|----------|--------|
1 | 150 |2015/10/09|6:35:00 |
2 | 250 |2015/10/10|5:21:00 |
3 | 75 |2015/11/11|17:30:00|
3 | 95 |2015/11/11|18:21:00|
4 | 300 |2015/12/12|2:25:00 |
I need result shows as:
Sid SDate STime Sname | Sprice |
---|----------|--------|---------|------------
1 |2015/10/09|6:35:00 |test1 |150 |
2 |2015/10/10|5:21:00 |test2 |250 |
3 |2015/11/11|17:30:00|test3 |95 |
3 |2015/11/11|18:21:00|test3 |300 |
4 |2015/12/12|2:25:00 |test5 |NULL |
In the other case, my below code show me both staff Id 3 on 2015/11/11 date.
SELECT R.SId ,R.SName,R.Sprice
FROM (SELECT Staff.SId ,Staff.SName,Sprice,updateStaff.SDate
FROM Staff
LEFT JOIN updateStaff ON Staff.SId = updateStaff.SId ) AS R
WHERE R.date = (SELECT MAX(date) FROM updateStaff WHERE updateStaff.SId =R.SId)
ORDER BY R.SId , R.SName
I need only the last staff's price order by date, time.

I'm not sure about sql server ce syntax, so I'm pretty sure there is a better way of doing it but you can do this:
SELECT R.SId ,R.SName,R.Sprice
FROM (SELECT Staff.SId ,Staff.SName,Sprice,updateStaff.SDate,updateStaff.stime
FROM Staff
LEFT JOIN updateStaff ON Staff.SId = updateStaff.SId ) AS R
WHERE R.stime = (SELECT MAX(stime) FROM updateStaff us WHERE us.SId =R.SId
and us.sdate =(select max(sdate) from updateStaff us2 where us2.sid = us.sid))
ORDER BY R.SId , R.SName

Related

Issue with SQL join and group

I have 4 tables I am trying to join and then group data. The data consists of jobs, invoices and accounts. I want to generate a total of each account in each job.
I have the following tables:
Jobs
| ID | JobNumber |
|----|-----------|
| 1 | J200 |
| 2 | J201 |
Job_Invoices
| ID | InvoiceNumber | JobID |
|----|---------------|-------|
| 10 | I300 | 1 |
| 11 | I301 | 2 |
Invoice_Accounts
| ID | InvoiceId | AccountID | Amount |
|----|-----------|-----------|--------|
| 23 | 10 | 40 | 200 |
| 24 | 10 | 40 | 300 |
| 25 | 10 | 41 | 100 |
| 26 | 11 | 40 | 100 |
Accounts
| ID | Name |
|----|------|
| 40 | Sales|
| 41 | EXP |
I am trying the following:
SELECT
J.JobNumber,
A.Name AS "Account",
SUM(JA.Amount) AS 'Total'
FROM
Job J
LEFT JOIN
Job_Invoices JI ON JI.JobID = J.JobID
INNER JOIN
Invoice_Accounts JA ON JA.InvoiceId = JI.ID
INNER JOIN
Accounts A ON A.ID = JA.AccountID
GROUP BY
J.JobNumber, A.Name, JA.Amount
ORDER BY
J.JobNumber
What I expect:
| JobNumber | Account | Total |
|-----------|-----------|-------|
| J200 | EXP | 100 |
| J200 | Sales | 500 |
| J201 | Sales | 100 |
What I get:
| JobNumber | Account | Total |
|-----------|-----------|-------|
| J200 | EXP | 100 |
| J200 | Sales | 200 |
| J200 | Sales | 300 |
| J201 | Sales | 100 |
You don't need the Job table in the query. The INNER JOINs are to the Job_Invoices table, so the outer join is turned into an inner join anyway.
So, you can simplify this to:
SELECT JI.JobNumber, A.Name AS Account, SUM(JA.Amount) AS Total
FROM Job_Invoices JI JOIN
Invoice_Accounts JA
ON JA.InvoiceId = JI.ID JOIN
Accounts A
ON A.ID = JA.AccountID
GROUP BY JI.JobNumber, A.Name
ORDER BY JI.JobNumber;
Also note that you don't need to escape the column aliases. The just makes the query harder to type.
The problem is you have the JA.Amount in your GROUP BY clause. Try taking it out:
SELECT J.JobNumber, A.Name AS "Account", SUM(JA.Amount) AS 'Total'
FROM Job J
LEFT JOIN Job_Invoices JI ON JI.JobID = J.JobID
INNER JOIN Invoice_Accounts JA ON JA.InvoiceId = JI.ID
INNER JOIN Accounts A ON A.ID = JA.AccountID
GROUP BY J.JobNumber, A.Name
ORDER BY J.JobNumber
You can write a query as:
select sum (IA.Amount) as Amount, J.JobNumber,A.Name
from #Invoice_Accounts IA --as it holds the base data
join #Job_Invoices JI on IA.InvoiceId = JI.ID
join #Jobs J on J.id = JI.JobID
join #Accounts A on A.ID = IA.AccountID
group by J.JobNumber,A.Name
Included the Jobs table as it has the JobNumber column. Sample code here..

Sql Query to fetch highest marks

The table structure is like this:
Student (StudentID int, StudentName varchar(20))
Subject (SubjectID int, SubjectName varchar(20))
Score (ScoreID int, StudentID int, SubjectID int, Score int)
Here's the data
Student
| StudentID | StudentName |
| 1 | John |
| 2 | Nash |
| 3 | Albert |
---------------------------
Subject
| SubjectID | SubjectName |
| 1 | Maths |
| 2 | Physics |
| 3 | Chemistry|
| 4 | English |
---------------------------
Score
| ScoreID | StudentID | SubjectID | Score |
| 1 | 1 | 1 | 34 |
| 2 | 1 | 2 | 45 |
| 3 | 1 | 3 | 56 |
| 4 | 2 | 1 | 78 |
| 5 | 2 | 3 | 23 |
| 6 | 2 | 4 | 44 |
| 7 | 3 | 1 | 45 |
| 8 | 3 | 2 | 10 |
| 9 | 3 | 3 | 54 |
| 10 | 3 | 4 | 74 |
-------------------------------------------
Output:
|StudentName | Score | SubjectName |
| John | 45 | Physics |
| John | 56 | Chemistry |
| Nash | 78 | Maths |
| Albert | 74 | English |
------------------------------------
I want to write a query to fetch the top scorer of every subject along with their scores and subject names, without using Row_Number(), Rank() and Dense_Rank().
I've written this query, but I think it can be improved:
select
st.StudentName, Score, B.SubjectID
from
(select
StudentID, Sc.SubjectID, Sc.Score
from
(select
SubjectID, MAX(Score) as 'Score'
from
Score Sc
inner join
subject sb on sc.subjectid = sb.subjectid
group by
SubjectID) A
inner join
score sc on sc.SubjectID = a.SubjectID and sc.Score = A.Score) B
inner join
Student st on st.studentID = B.StudentID
This is a solution without using RANK or ROW_NUMBER. Also this is much simplified to meet your output. Hope it helps
SELECT st.StudentName ,s.Score ,su.SubjectName FROM (SELECT
SubjectID,MAX(Score) as Max FROM Score GROUP BY SubjectID) a
INNER JOIN Score s on a.SubjectID=s.SubjectID AND a.MAX=s.Score
INNER JOIN Student st on s.StudentID=st.StudentID
INNER JOIN Subject su on s.SubjectID=su.SubjectID
You would use the ANSI standard rank() or row_number() functions. Assuming you want all duplicates, use rank():
select StudentName, SubjectName, Score
from (select st.StudentName, su.SubjectName, s.Score,
rank() over (partition by su.SubjectName order by s.Score desc) as seqnum
from score s join
student st
on s.studentid = st.studentid join
subject su
on s.subjectid = su.subjectid
) s
where seqnum = 1;
If you wanted exactly one row per subject with an arbitrary top student, use row_number().
In SQL Server, you can also do this without a subquery:
select top (1) with ties st.StudentName, su.SubjectName, s.Score
from score s join
student st
on s.studentid = st.studentid join
subject su
on s.subjectid = su.subjectid
order by rank() over (partition by su.SubjectName order by s.Score desc)
Another fun option is apply -- and this doesn't use window functions and can have quite good performance:
select ss.StudentName, su.SubjectName, ss.Score
from subject su cross apply
(select top (1) with ties s.*
from score s join
student st
on s.studentid = st.studentid join
where s.subjectid = su.subjectid
order by su.score desc
) ss;
You can use ROW_NUMBER like below
SELECT * FROM
(SELECT st.StudentName, sub.SubjectName, s.Score,
ROW_NUMBER() OVER(partition by sub.SubjectName order by s.Score Desc) as row_no
FROM Score s
LEFT OUTER JOIN Student st on s.StudentID = st.StudentID
LEFT OUTER JOIN Subject sub on s.SubjectID = sub.SubjectID) as tblMain
WHERE row_no = 1

SQL - Join with multiple condition

I'm trying to join my users table with my jobs table based on a mapping table users_jobs:
Here is what the users table looks like:
users
|--------|------------------|
| id | name |
|--------|----------------- |
| 1 | Ozzy Osbourne |
| 2 | Lemmy Kilmister |
| 3 | Ronnie James Dio |
| 4 | Jimmy Page |
|---------------------------|
jobs table looks like this:
|--------|-----------------|
| id | title |
|--------|-----------------|
| 1 | Singer |
| 2 | Guitar Player |
|--------------------------|
And users_jobs table looks like this:
|--------|-------------|-------------|---------------|-------------|
| id | user_id | job_id | column3 | column4 |
|--------|-------------|-------------|---------------|-------------|
| 1 | 1 | 1 | 0 | 1 |
| 2 | 2 | 1 | 1 | 0 |
| 3 | 3 | 1 | 0 | 1 |
| 4 | 4 | 2 | 1 | 0 |
|----------------------|-------------|---------------|-------------|
For example, let's say the ozzy does a query.
Here is what should expect:
|--------|------------------|------------|--------- |
| id | name | column3 | column4 |
|--------|----------------- |------------|----------|
| 1 | Ozzy Osbourne | 0 | 1 |
| 2 | Lemmy Kilmister | 1 | 0 |
| 3 | Ronnie James Dio | 0 | 1 |
|---------------------------|------------|----------|
Basically, he can only see the job in which he is registered (role) and the users included.
I tried to do this:
SELECT u1.*, uj1.colum3, uj1.column4
FROM users AS u1
JOIN users_jobs AS uj1 ON uj1.user_id = 1
JOIN jobs AS j1 ON j1.id = up1.job_id
WHERE uj1.job_id = 1
Any help would be great!
Looks like you need INNER JOIN Try this :
select u.id, u.column3 , u.column4 from users u
inner join user_jobs uj on u.id=uj.user_id
inner join jobs j on j.id=uj.job_id
where uj.job_id=1;
If you need by certain user_id
select u.id, u.column3 , u.column4 from users u
inner join user_jobs uj on u.id=uj.user_id
inner join jobs j on j.id=uj.job_id
where uj.job_id=1
and u.id=1;
I found a solution.
Using #stackFan approach adding an EXISTS clause to make sure that the user is in.
SELECT u.id, u.column3 , u.column4
FROM users u
INNER JOIN user_jobs uj on u.id = uj.user_id
INNER JOIN jobs j on j.id = uj.job_id
WHERE uj.job_id = <job-ID>
AND
EXISTS (
SELECT *
FROM users_jobs AS uj
WHERE uj.job_id = <job-ID>
AND uj.user_id = <user-ID>
);
Try LEFT JOIN. It will display all users, whether they have job or not.
SELECT u.id, u.name, uj.colum3, uj.column4
FROM users AS u
LEFT JOIN users_jobs uj ON uj.user_id = u.id
LEFT JOIN jobs j ON j.id = uj.job_id

How to query the most recent record in SQL Server 2012?

I am attempting to query the most recent status of a a document, but not having any success. The tables I am using for my query are docs, docStats and statusList.
docs table:
| docId | docTitle | venIdFk |
+-------+------------+---------+
| 1 | Contract 1 | 81 |
| 2 | Contract 2 | 37 |
+-------+------------+---------+
docStats table:
| docStatIdPk | docStat | docStatDt | docIdFk |
+-------------+---------+-------------------------+---------+
| 7 | 1 | 2016-09-16 09:00:00.000 | 1 |
| 10 | 2 | 2016-09-16 09:30:00.000 | 1 |
| 11 | 4 | 2016-09-17 08:30:00.000 | 1 |
| 12 | 1 | 2016-09-17 10:00:00.000 | 2 |
+-------------+---------+-------------------------+---------+
statusList table:
| statId | stat |
+--------+--------------+
| 1 | Needs Review |
| 2 | In Review |
| 3 | Denied |
| 4 | Accepted |
+--------+--------------+
Using the following I can get the most recent status of the documents in the docs table:
SELECT
d2.docId, MAX(ds2.docStatDt) AS maxStatDt
FROM
docs d2
INNER JOIN
docStats ds2 ON d2.docId = ds2.docIdFk
GROUP BY
d2.docId
The result is correct:
| docId | maxStatDt |
+-------+-------------------------+
| 1 | 2016-09-17 08:30:00.000 |
| 2 | 2016-09-17 10:00:00.000 |
+-------+-------------------------+
But I need to add additional columns to the query, and this is where it breaks. It adds all of the records for docId 1 because 'stat' is included in the SELECT, and there are multiple statuses for docId 1. How can I write this query so that it only returns the most recent status of docId 1?
Here is the query as I have it now:
SELECT
d.docid, d.doctitle, d.doctype, d.docorg, d.docdept,
s.stat,
mdt.maxdate AS crntDocStatDtSet
FROM
docs d
INNER JOIN
docstats ds ON d.docid = ds.docidfk
INNER JOIN
statuslist s ON ds.docstat = s.statid
INNER JOIN
(SELECT
d2.docid, Max(ds2.docstatdt) AS MaxDate
FROM
docs d2
INNER JOIN
docstats ds2 ON d2.docid = ds2.docidfk
GROUP BY
d2.docid) mdt ON d.docid = mdt.docid
WHERE
d.docid = '1'
GROUP BY
d.docid, d.doctitle, d.doctype, d.docorg, d.docdept,
s.stat, mdt.maxdate
Here are the results:
| docId | docTitle | docType | docOrg | docDept | stat | crntDocStatDtSet |
+-------+------------+---------+--------+---------+--------------+-------------------------+
| 1 | Contract 1 | 3 | 3 | 2 | Accepted | 2016-09-17 08:30:00.000 |
| 1 | Contract 1 | 3 | 3 | 2 | In Review | 2016-09-17 08:30:00.000 |
| 1 | Contract 1 | 3 | 3 | 2 | Needs Review | 2016-09-17 08:30:00.000 |
+-------+------------+---------+--------+---------+--------------+-------------------------+
;WITH cte AS (
SELECT
d.docId,
d.docTitle,
d.doctype,
d.docOrg,
d.docDept,
s.stat,
ROW_NUMBER() OVER (PARTITION BY d.docId ORDER BY ds.docStatDt DESC) as RowNumber
FROM
docs d
INNER JOIN docStats ds
ON d.docId = ds.docIdFk
INNER JOIN statusList s
ON ds.docStat = s.statId
)
SELECT *
FROM
cte
WHERE
RowNumber = 1
It would be better to use a partitioned ROW_NUMBER(). MAX() the way you are using it could end up with more than 1 result. If more than 1 would be desired if they are equal then simply use DENSE_RANK() in place of where ROW_NUMBER() is.
The route you are going is also a valid method but your ON condition for your join did not have ON ds.docStatDt = mdt.MaxDate which meant you were not actually limiting your results to the last record. Adding that condition in would probably give you what you want as well.
SELECT
d.docId,
d.docTitle,
d.doctype,
d.docOrg,
d.docDept,
s.stat,
mdt.MaxDate AS crntDocStatDtSet
FROM
docs d
INNER JOIN docStats ds
on d.docId = ds.docIdFk
INNER JOIN statusList s
ON ds.docStat = s.statId
INNER JOIN (
SELECT d2.docId, MAX(ds2.docStatDt) as MaxDate
FROM docs d2
INNER JOIN docStats ds2
ON d2.docId = ds2.docIdFk
GROUP BY d2.docId
) mdt
ON d.docId = mdt.docId
AND ds.docStatDt = mdt.MaxDate
WHERE d.docId = '1'
GROUP BY
d.docId,
d.docTitle,
d.doctype,
d.docOrg,
d.docDept,
s.stat,
mdt.MaxDate
One method that can be quite efficient is to use outer apply:
select d.*, ds.*
from docs d outer apply
(select top 1 ds.*
from docstats ds
where ds.docIdFk = d.docId
order by docStatDt desc
) ds;
You can, of course, join in the status string as well, but that doesn't seem to be the gist of your question.

PostgreSQL join two tables with LIMIT 1

I have two tables:
First table "persons"
id | name |
---------------
1 | peter |
3 | martin |
5 | lucy |
Second table "meetings"
id | date | id_persons |
--------------------------------
1 | 2014-12-08 | 1 |
2 | 2013-05-10 | 2 |
3 | 2015-08-25 | 1 |
4 | 2016-10-18 | 1 |
5 | 2012-01-01 | 3 |
6 | 2016-09-28 | 5 |
I need somehow get only last date from "meeting" table for every person (or selected). And result table must be order by name. I thought, it could be like this, but WHERE clause in LEFT JOIN can't be used:
SELECT meetings.id, meetings.date, persons.name FROM persons
LEFT JOIN (SELECT meetings.date, meetings.id, meetings.id_persons FROM
meetings WHERE persons.id = meetings.id_persons ORDER BY
meetings.date DESC LIMIT 1) m ON m.id_persons = persons.id
WHERE persons.id < 6 ORDER BY persons.name
So I started with DISTINCT and it worked, but I think that it is not good idea:
SELECT * FROM
(SELECT DISTINCT ON (persons.id) persons.id, persons.name,
m.date, m.id FROM persons
LEFT JOIN (SELECT meetings.id, meetings.date, meetings.id_persons
FROM meetings ORDER BY meetings.date DESC) m
ON m.id_persons = persons.id
WHERE persons.id < 6 ORDER BY persons.id) p
ORDER BY p.name
Result what I need is:
name | date | id_meetings
-----------------------------------
lucy | 2016-09-28 | 6
martin | 2012-01-01 | 5
peter | 2016-10-18 | 4
Could you help me with better solution?
In Postgres, the easiest way is probably distinct on:
select distinct on (p.id) p.*, m.*
from persons p left join
meetings m
on m.id_persons = p.id
order by p.id, m.date desc;
Note: distinct on is specific to Postgres.