Microsoft SQL Server : max date joining two tables - sql

I am attempting to join two tables while pulling the max date from one.
I have a student table and a communication table. Each student is unique in the student table and has many communication entries.
I would like to create a SQL script that pulls each student's ID, name, latest communication date, and the communication message for that date.
I am able to pull the latest date for each student using max(comm_date) and group by, but things get messy (many duplications) when pulling the corresponding communication message.
Table: Student
studentid, name
Table: Communications
studentid, comm_date, comm_msg
Result:
student.studentid, student.name, communications.comm_date, communications.comm_msg
How can I pull the corresponding communication message given max(comm_date)?

This should get you what you need. I don't know if there's a performance hit doing this via nested subquery, but I like the clean syntax of this:
SELECT
s.studentid,
s.name,
LastCommDate = MAX(c.comm_date),
LastCommMessage = (SELECT comm_msg FROM Communications WHERE studentid = s.studentid AND comm_date = MAX(c.comm_date))
FROM Student AS s
INNER JOIN Communications AS c
ON s.studentid = c.studentid
GROUP BY s.studentid, s.name

This should get what you need...
select
s.studentid,
s.name,
c2.comm_date,
c2.comm_msg
from
Student s
LEFT JOIN
( select
c1.studentid,
max( c1.comm_Date ) as MaxDate
from
Communications c1
group by
c1.studentid ) PreMax
on s.studentid = PreMax.StudentID
LEFT JOIN Communications c2
on PreMax.StudentID = c2.StudentID
AND PreMax.MaxDate = c2.comm_Dat
Additionally, I would suggest adding a column to your student table for most recent communication date (or even an ID if communications has an auto-increment column such as multiple entries on the same day). Then, via an insert trigger to the communications table, you update the student table with that newest date (or communication ID). Then you never need to keep requerying the MAX() and re-joining multiple times as this one does.

Related

outer join with not in and for each in sql

This one has only one table with three columns student, lect and score. for each lecture I need to find the students who have not got any score.
I have written the below query which uses outer joins, but it can do so only for one lect at a time.
Eg: see below I passed 'L02'
How do I get this working for all the lect values as in (L01,L02,L03...etc)
select distinct * from
(
select distinct Student from import1
where lect ='L02'
)i1
right outer join
(select distinct Student from import1) i2
on i1.Student=i2.Student
where i1.Student is null
output of above is
This works for L02. But, how do I modify above to include for all lect values without hardcoding the values of lect?
Sample data from table:
Need a dataset of all possible student/lecture pairs. If the one table contains all the students and lectures that need to be considered, this dataset can be built with:
SELECT Student, Lect FROM (SELECT DISTINCT Student FROM import1) AS S, (SELECT DISTINCT Lect FROM import1) AS L
Otherwise, need a table of all students and a table of all lectures then query:
SELECT Student, Lect FROM Students, Lectures
Now join that query to the scores table with compound link on both identifier fields and use appropriate filter criteria.
SELECT Query1.Student, Query1.Lect, import1.Score
FROM Query1 LEFT JOIN import1
ON (Query1.Lect = import1.Lect) AND (Query1.Student = import1.Student)
WHERE Score Is Null;
Tested with an Access database.

SQL Server 2016 Sub Query Guidance

I am currently working on an assignment for my SQL class and I am stuck. I'm not looking for full code to answer the question, just a little nudge in the right direction. If you do provide full code would you mind a small explanation as to why you did it that way (so I can actually learn something.)
Here is the question:
Write a SELECT statement that returns three columns: EmailAddress, ShipmentId, and the order total for each Client. To do this, you can group the result set by the EmailAddress and ShipmentId columns. In addition, you must calculate the order total from the columns in the ShipItems table.
Write a second SELECT statement that uses the first SELECT statement in its FROM clause. The main query should return two columns: the Client’s email address and the largest order for that Client. To do this, you can group the result set by the EmailAddress column.
I am confused on how to pull in the EmailAddress column from the Clients table, as in order to join it I have to bring in other tables that aren't being used. I am assuming there is an easier way to do this using sub Queries as that is what we are working on at the time.
Think of SQL as working with sets of data as opposed to just tables. Tables are merely a set of data. So when you view data this way you immediately see that the query below returns a set of data consisting of the entirety of another set, being a table:
SELECT * FROM MyTable1
Now, if you were to only get the first two columns from MyTable1 you would return a different set that consisted only of columns 1 and 2:
SELECT col1, col2 FROM MyTable1
Now you can treat this second set, a subset of data as a "table" as well and query it like this:
SELECT
*
FROM (
SELECT
col1,
col2
FROM
MyTable1
)
This will return all the columns from the two columns provided in the inner set.
So, your inner query, which I won't write for you since you appear to be a student, and that wouldn't be right for me to give you the entire answer, would be a query consisting of a GROUP BY clause and a SUM of the order value field. But the key thing you need to understand is this set thinking: you can just wrap the ENTIRE query inside brackets and treat it as a table the way I have done above. Hopefully this helps.
You need a subquery, like this:
select emailaddress, max(OrderTotal) as MaxOrder
from
( -- Open the subquery
select Cl.emailaddress,
Sh.ShipmentID,
sum(SI.Value) as OrderTotal -- Use the line item value column in here
from Client Cl -- First table
inner join Shipments Sh -- Join the shipments
on Sh.ClientID = Cl.ClientID
inner join ShipItem SI -- Now the items
on SI.ShipmentID = Sh.ShipmentID
group by C1.emailaddress, Sh.ShipmentID -- here's your grouping for the sum() aggregation
) -- Close subquery
group by emailaddress -- group for the max()
For the first query you can join the Clients to Shipments (on ClientId).
And Shipments to the ShipItems table (on ShipmentId).
Then group the results, and count or sum the total you need.
Using aliases for the tables is usefull, certainly when you select fields from the joined tables that have the same column name.
select
c.EmailAddress,
i.ShipmentId,
SUM((i.ShipItemPrice - i.ShipItemDiscountAmount) * i.Quantity) as TotalPriceDiscounted
from ShipItems i
join Shipments s on (s.ShipmentId = i.ShipmentId)
left join Clients c on (c.ClientId = s.ClientId)
group by i.ShipmentId, c.EmailAddress
order by i.ShipmentId, c.EmailAddress;
Using that grouped query in a subquery, you can get the Maximum total per EmailAddress.
select EmailAddress,
-- max(TotalShipItems) as MaxTotalShipItems,
max(TotalPriceDiscounted) as MaxTotalPriceDiscounted
from (
select
c.EmailAddress,
-- i.ShipmentId,
-- count(*) as TotalShipItems,
SUM((i.ShipItemPrice - i.ShipItemDiscountAmount) * i.Quantity) as TotalPriceDiscounted
from ShipItems i
join Shipments s on (s.ShipmentId = i.ShipmentId)
left join Clients c on (c.ClientId = s.ClientId)
group by i.ShipmentId, c.EmailAddress
) q
group by EmailAddress
order by EmailAddress
Note that an ORDER BY is mostly meaningless inside a subquery if you don't use TOP.

SQL select multiple rows and order them by id

I got a Student with a few parameters, like Id, Name, etc.
Now I got another one table called ACCOUNTS, they have an id and a name...
the relation between these two is OneToMany (one student can have more accounts)
and i need a SQL query to show all accounts for the student...here is what i have, but it's not working...
"Select distinct s from Student s left join fetch s.accounts where s.id=:studId"
I should say that the Student has a field called accounts
And at the end of the query, i placed already ORDER BY and then the following code didn't work:
s.accounts.id
account.id
student.account.id
So...long story short...the query above, shows the accounts of the specific student...now i just need to order them by the id of the accounts
Any suggestions?
As OP confirmed adding it as answer.
select distinct s.id, s.name, a.id
from students s left outer join accounts a on s.id = a.id
order by s.id, a.id;
one student can have more accounts
So it's two tables:
student (id, name, ...)
accounts (id, student_id, ...)
I should say that the Student has a field called accounts
That makes no sense. By this you would store one account per student and several students could share one account. So I stick to the first statement that one student can have several accounts and the tables look more or less like I've shown above.
You want to see accounts for one student. So you select from accounts where the student ID matches:
select * from accounts where student_id = :studId order by account.id;
If you want to show student data along, you'd join the tables instead:
select *
from student s
left join accounts a on a.student_id = s.id
where s.id = :studId
order by a.id;

SQL count after joining two tables

I am newbie to SQL, I would like to come up with a count, assume this example with 2 tables:
School(schoolID, name,....)
Student(StudentID, SchoolID, ...)
I tried:
SELECT COUNT(studentID)
FROM School s, Student t
WHERE s.schooldID = t.schoolID
How can I get a count of all students across all schools?
Since you have the school ID in the student table, it doesn't appear to me that you need to join to school at all. Just select a count from the student table and group by schoolID:
SELECT schoolID, COUNT(*) AS numStudents
FROM student
GROUP BY schoolID;
The only reason you'd need to join to School is if you want other information, such as school name and so on. If you just want the school id and number of students, the above will work.
To complete that last thought, possibly irrelevant to your question. If you did want school name, you just do an inner join and put school.name in your select statement, along with the count from the student table and group by school ID still:
SELECT s.name, st.COUNT(*) AS numStudents
FROM student st
JOIN school s ON s.id = st.schoolID
GROUP BY s.id;
If you want to get the count per school, you need a group by. Also, usually we prefer ANSI style joins, since in fact all database systems support them nowadays and they easier to read and maintain:
select count(t.studentID)
from Student t
join School s /* added join for your convenience, not necessary here */
on s.schooldID = t.schooldID
group
by t.schoolID

Select the row with the max value in a specific column, SQL Server

I've been working on a school project past few days and I picked to work on a DVD club database. I have six tables, but for this question, only two are relevant. The clients table and the loans table. So, what I am trying to do is count for every client how many loans he's made so far and out of all pick the client with the max number of loans, so he can be rewarded the free DVD next month. Here is the code I've written, but it doesn't pick the specific client, it shows all the clients having the max number of loans of a specific client:
SELECT tblClients.Client_ID, MAX(x.Number_Of_Loans) AS MAX_NOL
FROM
(
SELECT COUNT(tblLoans.Client_ID) AS Number_Of_Loans
FROM tblClients, tblLoans WHERE tblClients.Client_ID=tblLoans.Client_ID
GROUP BY tblLoans.Client_ID
)x, tblClients, tblLoans
WHERE tblClients.Client_ID=tblLoans.Client_ID
GROUP BY tblClients.Client_ID, tblClients.Given_Name,
tblClients.Family_Name, tblClients.Phone, tblClients.Address, tblClients.Town_ID
Use the following
SELECT TOP 1 tblClients.Client_ID,COUNT(tblLoans.Client_ID) AS MAX_NOL
FROM tblClients, tblLoans
WHERE tblClients.Client_ID=tblLoans.Client_ID
GROUP BY tblClients.Client_ID
ORDER BY COUNT(tblLoans.Client_ID) DESC
You can do this with a single aggregate GROUP, ordered by the client with the max loans:
SELECT TOP 1 tblClients.Client_ID, tblClients.Given_Name, tblClients.Family_Name,
tblClients.Phone, tblClients.Address, tblClients.Town_ID,
COUNT(x.Number_Of_Loans) AS MAX_NOL
FROM
tblClients INNER JOIN tblLoans
ON tblClients.Client_ID=tblLoans.Client_ID
GROUP BY tblClients.Client_ID, tblClients.Given_Name, tblClients.Family_Name,
tblClients.Phone, tblClients.Address, tblClients.Town_ID
ORDER BY MAX_NOL DESC;
Any selected columns from the client need to be included in the GROUP, and I would recommend using JOINs instead of WHERE joins.
Edit
What might be tidier is to split the determination of the ClientId with the most loans and the concern of fetching the rest of the client's data, like so (rather than the ungainly GROUP BY over many columns):
SELECT c.Client_ID, c.Given_Name, c.Family_Name,
c.Phone, c.Address, c.Town_ID,
x.MaxLoans
FROM
tblClients c
INNER JOIN
(SELECT TOP 1 tblClients.Client_ID, COUNT(tblLoans.Client_ID) AS MaxLoans
FROM tblClients
INNER JOIN tblLoans
ON tblClients.Client_ID=tblLoans.Client_ID
GROUP BY tblClients.Client_ID
ORDER BY MaxLoans DESC) x
ON c.Client_ID = x.Client_ID;