我有两张桌子:
患者
PatientStatus
患者 - > PatientStatus 是一对多关系。
我想知道在SQL中是否可以进行连接,该连接仅返回每个患者的前两个PatientStatus记录。如果只存在一个PatientStatus记录,则不应在结果中返回。
我的查询的正常连接是:
SELECT FROM Patient p INNER JOIN PatientStatus ps ON p.pkPatientId = ps.fkPatientId
ORDER BY ps.fkPatientId, ps.StartDate
答案 0 :(得分:6)
如果您使用的是SQL Server 2005或更高版本,CTE可能是您最好的选择,但如果您想要与其他平台更兼容的东西,这应该可行:
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
对我来说,你想要前两个状态而不是最后两个状态似乎有点奇怪,但我会假设你知道你想要什么。
如果你的性能更好,你也可以使用WHERE NOT EXISTS而不是PS3和PS4连接。
答案 1 :(得分:4)
这是我的尝试 - 它应该适用于SQL Server 2005和SQL Server 2008(在SQL Server 2008上测试),因为使用了公用表表达式:
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
答案 2 :(得分:2)
编辑:以下两种解决方案都要求PatientStatus.StartDate
在每位患者中都是唯一的。
传统方式(SQL Server 2000兼容):
SELECT
p.pkPatientId,
p.FirstName,
p.Surname,
ps.StatusCode,
ps.StartDate,
ps.EndDate
FROM
Patient p
INNER JOIN PatientStatus ps ON
p.pkPatientId = ps.fkPatientId
AND ps.StartDate IN (
SELECT TOP 2 StartDate
FROM PatientStatus
WHERE fkPatientId = ps.fkPatientId
ORDER BY StartDate /* DESC (to switch between first/last records) */
)
WHERE
EXISTS (
SELECT 1
FROM PatientStatus
WHERE fkPatientId = p.pkPatientId
GROUP BY fkPatientId
HAVING COUNT(*) >= 2
)
ORDER BY
ps.fkPatientId,
ps.StartDate
一个更有趣的替代方案(你必须尝试比较它的表现):
SELECT
p.pkPatientId,
p.FirstName,
p.Surname,
ps.StatusCode,
ps.StartDate,
ps.EndDate
FROM
Patient p
INNER JOIN PatientStatus ps ON p.pkPatientId = ps.fkPatientId
WHERE
/* the "2" is the maximum number of rows returned */
2 > (
SELECT
COUNT(*)
FROM
Patient p_i
INNER JOIN PatientStatus ps_i ON p_i.pkPatientId = ps_i.fkPatientId
WHERE
ps_i.fkPatientId = ps.fkPatientId
AND ps_i.StartDate < ps.StartDate
/* switch between "<" and ">" to get the first/last rows */
)
AND EXISTS (
SELECT 1
FROM PatientStatus
WHERE fkPatientId = p.pkPatientId
GROUP BY fkPatientId
HAVING COUNT(*) >= 2
)
ORDER BY
ps.fkPatientId,
ps.StartDate
旁注:对于MySQL,后一个查询可能是唯一的选择 - 直到子查询支持LIMIT。
编辑:我添加了一个条件,排除只有一个PatientStatus
记录的患者。 (感谢您的提示,Ryan!)
答案 3 :(得分:1)
我没有尝试,但这可行;
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
如果这不起作用,请参阅this链接。
答案 4 :(得分:1)
将此WHERE子句添加到Tomalak的第一个解决方案的外部查询将阻止返回少于2个状态记录的患者。您也可以在第二个查询的WHERE子句中“和”它以获得相同的结果。
WHERE pkPatientId IN (
SELECT pkPatientID
FROM Patient JOIN PatientStatus ON pkPatientId = fkPatientId
GROUP BY pkPatientID HAVING Count(*) >= 2
)
答案 5 :(得分:1)
检查您的服务器是否支持窗口函数:
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
另一种可能与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
答案 6 :(得分:0)
以下是我将如何做到这一点:
-- Patients with at least 2 status records
with PatientsWithEnoughRecords as (
select fkPatientId
from PatientStatus as ps
group by
fkPatientId
having
count(*) >= 2
)
select top 2 *
from PatientsWithEnoughRecords as er
left join PatientStatus as ps on
er.fkPatientId = ps.fkPatientId
order by StartDate asc
我不确定在你的情况下是什么决定了“第一”两个状态记录,所以我假设你想要最早的两个 StartDate
**。修改最后一个** order by
子句以获取您感兴趣的记录。
编辑:SQL Server 2000不支持CTE,因此该解决方案确实只能直接在2005及更高版本上运行。
答案 7 :(得分:0)
丑陋,但这个不依赖于StartDate的唯一性并适用于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
)