I have two tables:
Patient
PatientStatus
Here is my attempt - It should work on SQL Server 2005 and SQL Server 2008 (Tested on SQL Server 2008) owing to the use of a common table expression:
WITH CTE AS
(
SELECT fkPatientId
, StatusCode
-- add more columns here
, ROW_NUMBER() OVER
(
PARTITION BY fkPatientId ORDER BY fkPatientId desc) AS [Row_Number]
from PatientStatus
where fkPatientId in
(
select fkPatientId
from PatientStatus
group by fkPatientId
having COUNT(*) >= 2
)
)
SELECT p.pkPatientId,
p.FirstName,
CTE.StatusCode
FROM [Patient] as p
INNER JOIN CTE
ON p.[pkPatientId] = CTE.fkPatientId
WHERE CTE.[Row_Number] = 1
or CTE.[Row_Number] = 2
I did not try but this could work;
SELECT /*(your select columns here)*/, row_number() over(ORDER BY ps.fkPatientId, ps.StartDate) as rownumber FROM Patient p INNER JOIN PatientStatus ps ON p.pkPatientId = ps.fkPatientId
where rownumber between 1 and 2
if this did not work, see this link.
A CTE is probably your best bet if you're in SQL Server 2005 or greater, but if you want something a little more compatible with other platforms, this should work:
SELECT
P.pkPatientID,
P.FirstName,
P.LastName,
PS1.StatusCode AS FirstStatusCode,
PS1.StartDate AS FirstStatusStartDate,
PS1.EndDate AS FirstStatusEndDate,
PS2.StatusCode AS SecondStatusCode,
PS2.StartDate AS SecondStatusStartDate,
PS2.EndDate AS SecondStatusEndDate
FROM
Patient P
INNER JOIN PatientStatus PS1 ON
PS1.fkPatientID = P.pkPatientID
INNER JOIN PatientStatus PS2 ON
PS2.fkPatientID = P.pkPatientID AND
PS2.StartDate > PS1.StartDate
LEFT OUTER JOIN PatientStatus PS3 ON
PS3.fkPatientID = P.pkPatientID AND
PS3.StartDate < PS1.StartDate
LEFT OUTER JOIN PatientStatus PS4 ON
PS4.fkPatientID = P.pkPatientID AND
PS4.StartDate > PS1.StartDate AND
PS4.StartDate < PS2.StartDate
WHERE
PS3.pkPatientStatusID IS NULL AND
PS4.pkPatientStatusID IS NULL
It does seem a little odd to me that you would want the first two statuses instead of the last two, but I'll assume that you know what you want.
You can also use WHERE NOT EXISTS instead of the PS3 and PS4 joins if you get better performance with that.
Ugly, but this one does not rely on uniqueness of StartDate and works on SQL 2000
select *
from Patient p
join PatientStatus ps on p.pkPatientId=ps.fkPatientId
where pkPatientStatusId in (
select top 2 pkPatientStatusId
from PatientStatus
where fkPatientId=ps.fkPatientId
order by StartDate
) and pkPatientId in (
select fkPatientId
from PatientStatus
group by fkPatientId
having count(*)>=2
)
Check if your server supports windowed functions:
SELECT *
FROM Patient p
LEFT JOIN PatientStatus ps ON p.pkPatientId = ps.fkPatientId
QUALIFY ROW_NUMBER() OVER (PARTITION BY ps.fkPatientId ORDER BY ps.StartDate) < 3
Another possibility, which should work with SQL Server 2005:
SELECT * FROM Patient p
LEFT JOIN (
SELECT *, ROW_NUMBER(PARTITION BY fsPatientId ORDER by StartDate) rn
FROM PatientStatus) ps
ON p.pkPatientId = ps.fkPatientID
and ps.rn < 3
Adding this WHERE clause to the outer query of Tomalak's first solution will prevent Patients with less than 2 status records from being returned. You can also "and" it in the WHERE clause of the second query for the same results.
WHERE pkPatientId IN (
SELECT pkPatientID
FROM Patient JOIN PatientStatus ON pkPatientId = fkPatientId
GROUP BY pkPatientID HAVING Count(*) >= 2
)