SQL-запрос - Join, который возвращает первые две записи присоединяющейся таблицы. - PullRequest
7 голосов
/ 10 марта 2009

У меня есть две таблицы:

Пациент

  • pkPatientId
  • 1010 * FirstName *
  • Фамилия

PatientStatus

  • pkPatientStatusId
  • fkPatientId
  • StatusCode
  • StartDate
  • EndDate

Пациент -> PatientStatus - это отношения один ко многим.

Мне интересно, возможно ли в SQL выполнить объединение, которое возвращает только первые две записи PatientStatus для каждого пациента. Если существует только одна запись PatientStatus, ее не следует возвращать в результатах.

Обычное соединение моего запроса:

SELECT FROM Patient p INNER JOIN PatientStatus ps ON p.pkPatientId = ps.fkPatientId
ORDER BY ps.fkPatientId, ps.StartDate

Ответы [ 8 ]

6 голосов
/ 10 марта 2009

CTE - это, вероятно, ваш лучший выбор, если вы используете SQL Server 2005 или более позднюю версию, но если вы хотите что-то более совместимое с другими платформами, это должно сработать:

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, если вы получите более высокую производительность с этим.

4 голосов
/ 10 марта 2009

Вот моя попытка - он должен работать на 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 голосов
/ 10 марта 2009

РЕДАКТИРОВАТЬ: оба из следующих решений требуют, чтобы 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 записью. (Спасибо за подсказку, Райан !)

1 голос
/ 10 марта 2009

Проверьте, поддерживает ли ваш сервер оконные функции:

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
1 голос
/ 10 марта 2009

Добавление этого предложения WHERE во внешний запрос первого решения Tomalak предотвратит возврат пациентов с менее чем 2 записями статуса. Вы также можете использовать «и» в предложении WHERE второго запроса для тех же результатов.

WHERE pkPatientId IN (
    SELECT pkPatientID 
    FROM Patient JOIN PatientStatus ON pkPatientId = fkPatientId
    GROUP BY pkPatientID HAVING Count(*) >= 2
)
1 голос
/ 10 марта 2009

Я не пробовал, но это могло сработать;

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

если это не сработало, см. эту ссылку.

0 голосов
/ 10 марта 2009

Ужасно, но этот не полагается на уникальность 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
)
0 голосов
/ 10 марта 2009

Вот как бы я подошел к этому:

-- 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 и более поздних версиях.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...