Write Cross Apply to select last row with condition - sql

I'm trying to make request for SQL Table which looks like:
CREATE TABLE StudentMark (
Id int NOT NULL IDENTITY(1,1),
StudentId int NOT NULL,
Mark int
);
Is that possible to select StudentMark rows where row should be last row for each user with mark greater than 4.
I'm trying to accomplish that by doing:
SELECT *
FROM [dbo].StudentMark outer
CROSS APPLY (
SELECT TOP(1) *
FROM [dbo].StudentMark inner
WHERE inner.StudentId= outer.StudentId AND inner.Mark>4
) cApply
But that doesn't do what's needed. Could anyone help?

I am curious if something like this will actually get what you are looking for:
SELECT Data.StudentId,
Data.Id,
Data.Mark
FROM
(
SELECT ROW_NUMBER() OVER (PARTITION BY StudentMark.StudentId ORDER BY StudentMark.Id DESC) AS RowNumber,
StudentMark.StudentId,
StudentMark.Id,
StudentMark.Mark
FROM dbo.StudentMark
) AS Data
WHERE Data.RowNumber = 1
What this will do is get the row number of each and then let you filter by the row number on what is returned. I changed it to look for the first row instead of the last row, but sorted DESC, such that you will get what effectively would have been the last row entered for a student id.

Presumably, "last row" is based on id. If so:
SELECT *
FROM [dbo].StudentMark sm CROSS APPLY
(SELECT TOP (1) sm2.*
FROM [dbo].StudentMark sm2
WHERE sm2.StudentId = sm.StudentId AND sm2.Mark > 4
ORDER BY sm2.id DESC
) sm2;
EDIT:
If you only want the last row per student with Mark > 4, then use filtering:
select sm.*
from dbo.StudentMark sm
where sm.id = (select max(sm2.id)
from dbo.StudentMark sm2
where sm2.studentId = sm.studentId and sm2.Mark > 4
);

SELECT *
FROM StudentMark A
CROSS APPLY
(
SELECT TOP 1 Mark
FROM StudentMark B
WHERE A.StudentId = B.StudentId
ORDER BY StudentId
)M
WHERE M.Mark > 4

Related

Select every second record then determine earliest date

I have table that looks like the following
I have to select every second record per PatientID that would give the following result (my last query returns this result)
I then have to select the record with the oldest date which would be the following (this is the end result I want)
What I have done so far: I have a CTE that gets all the data I need
WITH cte
AS
(
SELECT visit.PatientTreatmentVisitID, mat.PatientMatchID,pat.PatientID,visit.RegimenDate AS VisitDate,
ROW_NUMBER() OVER(PARTITION BY mat.PatientMatchID, pat.PatientID ORDER BY visit.VisitDate ASC) AS RowNumber
FROM tblPatient pat INNER JOIN tblPatientMatch mat ON mat.PatientID = pat.PatientID
LEFT JOIN tblPatientTreatmentVisit visit ON visit.PatientID = pat.PatientID
)
I then write a query against the CTE but so far I can only return the second row for each patientID
SELECT *
FROM
(
SELECT PatientTreatmentVisitID,PatientMatchID,PatientID, VisitDate, RowNumber FROM cte
) as X
WHERE RowNumber = 2
How do I return the record with the oldest date only? Is there perhaps a MIN() function that I could be including somewhere?
If I follow you correctly, you can just order your existing resultset and retain the top row only.
In standard SQL, you would write this using a FETCH clause:
SELECT *
FROM (
SELECT
visit.PatientTreatmentVisitID,
mat.PatientMatchID,
pat.PatientID,
visit.RegimenDate AS VisitDate,
ROW_NUMBER() OVER(PARTITION BY mat.PatientMatchID, pat.PatientID ORDER BY visit.VisitDate ASC) AS rn
FROM tblPatient pat
INNER JOIN tblPatientMatch mat ON mat.PatientID = pat.PatientID
LEFT JOIN tblPatientTreatmentVisit visit ON visit.PatientID = pat.PatientID
) t
WHERE rn = 2
ORDER BY VisitDate
OFFSET 0 ROWS FETCH FIRST 1 ROW ONLY
This syntax is supported in Postgres, Oracle, SQL Server (and possibly other databases).
If you need to get oldest date from all selected dates (every second row for each patient ID) then you can try window function Min:
SELECT * FROM
(
SELECT *, MIN(VisitDate) OVER (Order By VisitDate) MinDate
FROM
(
SELECT PatientTreatmentVisitID,PatientMatchID,PatientID, VisitDate,
RowNumber FROM cte
) as X
WHERE RowNumber = 2
) Y
WHERE VisitDate=MinDate
Or you can use SELECT TOP statement. The SELECT TOP clause allows you to limit the number of rows returned in a query result set:
SELECT TOP 1 PatientTreatmentVisitID,PatientMatchID,PatientID, VisitDate FROM
(
SELECT *
FROM
(
SELECT PatientTreatmentVisitID,PatientMatchID,PatientID, VisitDate,
RowNumber FROM cte
) as X
WHERE RowNumber = 2
) Y
ORDER BY VisitDate
For simplicity add order desc on date column and use TOP to get the first row only
SELECT TOP 1 *
FROM
(
SELECT PatientTreatmentVisitID,PatientMatchID,PatientID, VisitDate, RowNumber FROM cte
) as X
WHERE RowNumber = 2
order by VisitDate desc

DISTINCT with PagedResults

I am sure this will be answered somewhere...
Aim: Get DISTINCT DOCURL and additional columns
Tried:
1. Changing SELECT * FROM to SELECT DISTINCT DOCURL FROM which only yields the DOCURL column
2. Adding DISTINCT into the second select (as per example) but again I get all columns and rows.
Notes: Code is normally built dynamically so I've taken the print...
SELECT *
FROM
(
Select DISTINCT
isnull(d.DOCURL,'-') As DOCURL,
isnull(d.ID,'-') As ID,
isnull(d.UPRN,'-') As UPRN,
isnull(d.VFMDISCIPLINE,'-') As VFMDISCIPLINE,
isnull(d.VFMDISCIPLINEELEMENT,'-') As VFMDISCIPLINEELEMENT ,
isnull(d.SurveyDate,' ') As SurveyDate,
isnull(d.WorkOrder,'-') As WorkOrder,
ROW_NUMBER() OVER (ORDER BY DOCURL) AS ResultSetRowNumber
From TblData As D
WHERE 1 = 1
AND d.UPRN = '123XYZ'
AND (d.VFMDISCIPLINE = '1' OR d.VFMDISCIPLINE = '2' )
) As PagedResults
WHERE ResultSetRowNumber > 0 And ResultSetRowNumber <= 20
Assuming DOCURL is a unique column, the issue with the DISTINCT statement is that a new row number will be generated for each row in the sub query, therefore all rows will be considered different. You should apply distinct first and then get the row numbers.
Edit: I removed DISTINCT since your result set do not satisfy the criteria. Instead, I've added a partition inside the sub query, this way row numbers will start from 1 for each unique DOCURL and they're ordered by ID since I just assumed that's what you mean by first. Outer query reassigns row_numbers based on unique results from the sub query.
Select * From (
SELECT *, ROW_NUMBER() OVER (ORDER BY DOCURL) AS ResultSetRowNumber
FROM
(
Select
isnull(d.DOCURL,'-') As DOCURL,
isnull(d.ID,'-') As ID,
isnull(d.UPRN,'-') As UPRN,
isnull(d.VFMDISCIPLINE,'-') As VFMDISCIPLINE,
isnull(d.VFMDISCIPLINEELEMENT,'-') As VFMDISCIPLINEELEMENT ,
isnull(d.SurveyDate,' ') As SurveyDate,
isnull(d.WorkOrder,'-') As WorkOrder,
ROW_NUMBER() OVER (PARTITION BY d.DOCURL ORDER BY d.ID) As PART
From TblData As D
WHERE 1 = 1
AND d.UPRN = '123XYZ'
AND (d.VFMDISCIPLINE = '1' OR d.VFMDISCIPLINE = '2' )
) As t Where PART = 1
) As PagedResults
WHERE ResultSetRowNumber > 0 And ResultSetRowNumber <= 20

Get record with min time difference

I have an requirement where I need to get a record with min time difference with current record.
Let us assume that in a table I have insert date, group Id and Id column.
I have selected a record and not want to get another record for which difference between insert date of selected record and another record is min.
I have tried outer apply, but that query takes forever to run.
Query:
select e.id
from B.Emp t
where id = 5
outer apply (
select top 1 *
from B.Emp
where t.group_id = group_id
order by insert_time desc ) e
select * From B.Emp emp
Inner Join
(
select MAX(emp1.insert_time) maxTime, emp1.id From B.Emp emp1 group by emp1.id
) maxDateRec ON maxDateRec.id = emp.id AND maxDateRec.maxTime = emp.insert_time
where emp.id = 5
Try with this second one.
If you indeed want to find closest insert time for the selected record' group, regardless of the direction, then something like this might help:
select t.insert_date, ca.*
from B.Emp t
outer apply (
select top (1) * from B.Emp e
where e.group_id = t.group_id
and e.id != t.id
order by abs(datediff(ms, t.insert_date, e.insert_date))
) ca
where t.Id = 5;
EDIT: You will definitely need to check indexing options, though. For starters, assuming you have a clustered primary key on Id column, this should help:
create index [IX_B_Emp_GroupInsert] on B.Emp (group_id, insert_time);
This Query is not tested.
Please reply me is any error or changes needs to be done in the query.
CREATE TABLE Emp
(
group_Id INT,
ID INT,
date DATETIME
)
INSERT INTO EMP
(group_Id,ID,date)
values
(1,5,'11-Jan-2014'),
(1,5,'12-Jan-2014')
--
select ID,date from Emp t
group by
ID,
date having id = 5
and
date =
(select max(date) from Emp where id = 5)

SQL query to Identify and then delete all except latest record

I have the following query which identifies duplicate records based on the employee_id field.
SELECT ROW_NUMBER() OVER(PARTITION BY c1.employee_id ORDER BY c1.lastlogon ASC ) AS Row
,[DN]
,[first_name]
,[last_name]
,[init]
,[email]
,[title]
,[display_name]
,[department]
,[phone_num]
,[mob_num]
,[fax_num]
,[pager_num]
,[logon]
,[post_code]
,[www]
,[objectSID]
,[disabled]
,[lastlogon]
,[employee_id]
,[acc_type]
FROM AD_Users_All_Staging c1
WHERE EXISTS
(
SELECT 1
FROM AD_Users_All_Staging c2
WHERE c2.employee_id = c1.employee_id
GROUP BY
employee_id
HAVING COUNT(1) > 1 -- more than one value
)
How do a select just the latest record (values in lastlogon field) for which as duplicate exists (values in employee_id field)
The follow up question is how do I delete the all the records for each duplicate apart from the latest record?
Many thanks
As I don't have your data, I can't try anything easily... But, that said, what if you changed the windowing function to use c.lastlogon Desc instead of Asc. Then, you would always keep the first record Row = 1 and delete the rest Row > 1.
You can select the latest record using:
select uas.*
from AD_Users_All_Staging uas
where not exists (select 1
from AD_Users_All_Staging uas2
where uas2.employee_id = uas.employee_id and
uas2.lastlogon > uas.lastlogon
);
You can do the delete using inverse logic:
select uas.*
from AD_Users_All_Staging uas
where exists (select 1
from AD_Users_All_Staging uas2
where uas2.employee_id = uas.employee_id and
uas2.lastlogon > uas.lastlogon
);
I did some head scratching:
I've tried this and it looks like it gives me the results it needs:
;WITH cte AS
(SELECT ROW_NUMBER() OVER(PARTITION BY c1.employee_id ORDER BY c1.lastlogon DESC) AS Row
,[DN]
,[first_name]
,[last_name]
,[init]
,[email]
,[title]
,[display_name]
,[department]
,[phone_num]
,[mob_num]
,[fax_num]
,[pager_num]
,[logon]
,[post_code]
,[www]
,[objectSID]
,[disabled]
,[lastlogon]
,[employee_id]
,[acc_type]
FROM AD_Users_All_Staging c1
WHERE EXISTS
(
SELECT 1
FROM AD_Users_All_Staging c2
WHERE c2.employee_id = c1.employee_id
GROUP BY
employee_id
HAVING COUNT(1) > 1 -- more than one value
)
)
SELECT * FROM cte
WHERE row != 1
Does this look ok?

Total Row Count in sql query---sql server 2008

My query is as follows
BEGIN
WITH MyCTE
AS (
SELECT T.MusicAlbumTitle
,D.musicTitle
,D.mVideoID
,D.musicFileName
,T.ReleaseDate AS ReleasedDate
,D.MusicLength
,D.musicSinger
,D.MusicVideoID
,D.ExternalLink
,D.CoverImg
,ROW_NUMBER() OVER (
PARTITION BY D.MusicVideoID ORDER BY D.mVideoID
) AS row_num
FROM dbo.Music_Video T
JOIN dbo.Music_Video_Details D ON T.MusicVideoID = D.MusicVideoID
WHERE T.PortalID = #PortalID
AND T.CultureCode = #CultureCode
AND T.ComingSoon <> 1
GROUP BY T.MusicAlbumTitle
,D.musicTitle
,D.mVideoID
,T.ReleaseDate
,D.musicFileName
,D.MusicLength
,D.musicSinger
,D.MusicVideoID
,D.ExternalLink
,D.CoverImg
)
SELECT a.mVideoID
,a.MusicVideoID
,a.musicFileName
,a.MusicAlbumTitle
,a.ReleasedDate
,a.row_num
,a.CoverImg
,a.ExternalLink
,a.musicTitle
,a.MusicLength
FROM MyCTE a
WHERE row_num = 1
ORDER BY MusicVideoID DESC
END
I need to achieve total row count from last select statement.
which mean total row count that is being selected.
or any idea that might be use in this condition
How can i do this ..
Please add COUNT(*) OVER() in your select, which returns total rows selected as a new column.
Ex:
SELECT
*,
COUNT(*) OVER() AS [Total_Rows]
FROM YourTable
Just to be clear, you need to add the count to the CTE, not the outer query. The outer select is returning only one row, so the count would always be one.
The CTE should start:
WITH MyCTE
AS (
SELECT T.MusicAlbumTitle
,D.musicTitle
,D.mVideoID
,D.musicFileName
,T.ReleaseDate AS ReleasedDate
,D.MusicLength
,D.musicSinger
,D.MusicVideoID
,D.ExternalLink
,D.CoverImg
,ROW_NUMBER() OVER (
PARTITION BY D.MusicVideoID ORDER BY D.mVideoID
) AS row_num,
COUNT(*) over () as total_count