Consulta sin bucle WHILE

18

Tenemos mesa de citas como se muestra a continuación. Cada cita debe clasificarse como "Nueva" o "Seguimiento". Cualquier cita (para un paciente) dentro de los 30 días de la primera cita (de ese paciente) es Seguimiento. Después de 30 días, la cita vuelve a ser "nueva". Cualquier cita dentro de los 30 días se convierte en "Seguimiento".

Actualmente estoy haciendo esto escribiendo while loop.
¿Cómo lograr esto sin WHILE loop?

ingrese la descripción de la imagen aquí

Mesa

CREATE TABLE #Appt1 (ApptID INT, PatientID INT, ApptDate DATE)
INSERT INTO #Appt1
SELECT  1,101,'2020-01-05' UNION
SELECT  2,505,'2020-01-06' UNION
SELECT  3,505,'2020-01-10' UNION
SELECT  4,505,'2020-01-20' UNION
SELECT  5,101,'2020-01-25' UNION
SELECT  6,101,'2020-02-12'  UNION
SELECT  7,101,'2020-02-20'  UNION
SELECT  8,101,'2020-03-30'  UNION
SELECT  9,303,'2020-01-28' UNION
SELECT  10,303,'2020-02-02' 
LCJ
fuente
No puedo ver su imagen, pero quiero confirmar que si hay 3 citas, cada una de 20 días entre ellas, la última sigue siendo 'seguimiento' correcto, porque a pesar de que son más de 30 días desde la primera, aún quedan menos de 20 días desde el medio. ¿Es esto cierto?
pwilcox
@pwilcox No. La tercera será una nueva cita como se muestra en la imagen
LCJ
Si bien el bucle sobre el fast_forwardcursor probablemente sea su mejor opción, en cuanto al rendimiento.
David דודו Markovitz

Respuestas:

14

Necesita usar la consulta recursiva.

El período de 30 días se cuenta a partir de prev (y no, no es posible hacerlo sin recurrencia / actualización peculiar / bucle). Es por eso que todas las respuestas existentes usando solo ROW_NUMBERfallaron.

WITH f AS (
  SELECT *, rn = ROW_NUMBER() OVER(PARTITION BY PatientId ORDER BY ApptDate) 
  FROM Appt1
), rec AS (
  SELECT Category = CAST('New' AS NVARCHAR(20)), ApptId, PatientId, ApptDate, rn, startDate = ApptDate
  FROM f
  WHERE rn = 1
  UNION ALL
  SELECT CAST(CASE WHEN DATEDIFF(DAY,  rec.startDate,f.ApptDate) <= 30 THEN N'FollowUp' ELSE N'New' END AS NVARCHAR(20)), 
         f.ApptId,f.PatientId,f.ApptDate, f.rn,
         CASE WHEN DATEDIFF(DAY, rec.startDate, f.ApptDate) <= 30 THEN rec.startDate ELSE f.ApptDate END
  FROM rec
  JOIN f
    ON rec.rn = f.rn - 1
   AND rec.PatientId = f.PatientId
)
SELECT ApptId, PatientId, ApptDate, Category
FROM rec
ORDER BY PatientId, ApptDate;  

demostración de violín db <>

Salida:

+---------+------------+-------------+----------+
| ApptId  | PatientId  |  ApptDate   | Category |
+---------+------------+-------------+----------+
|      1  |       101  | 2020-01-05  | New      |
|      5  |       101  | 2020-01-25  | FollowUp |
|      6  |       101  | 2020-02-12  | New      |
|      7  |       101  | 2020-02-20  | FollowUp |
|      8  |       101  | 2020-03-30  | New      |
|      9  |       303  | 2020-01-28  | New      |
|     10  |       303  | 2020-02-02  | FollowUp |
|      2  |       505  | 2020-01-06  | New      |
|      3  |       505  | 2020-01-10  | FollowUp |
|      4  |       505  | 2020-01-20  | FollowUp |
+---------+------------+-------------+----------+

Cómo funciona:

  1. f - obtener el punto de partida (anclaje - por cada Id. de paciente)
  2. rec - recursibe parte, si la diferencia entre el valor actual y anterior es> 30, cambie la categoría y el punto de partida, en el contexto de PatientId
  3. Principal: muestra el conjunto de resultados ordenados

Clase similar:

SUM condicional en Oracle : limitar una función con ventana

Ventana de sesión (Azure Stream Analytics)

Total de ejecución hasta que se cumpla una condición específica : actualización peculiar


Apéndice

¡Nunca use este código en producción!

Pero otra opción, que vale la pena mencionar además de usar cte, es usar la tabla temporal y actualizar en "rondas"

Se podría hacer en ronda "única" (actualización peculiar):

CREATE TABLE Appt_temp (ApptID INT , PatientID INT, ApptDate DATE, Category NVARCHAR(10))

INSERT INTO Appt_temp(ApptId, PatientId, ApptDate)
SELECT ApptId, PatientId, ApptDate
FROM Appt1;

CREATE CLUSTERED INDEX Idx_appt ON Appt_temp(PatientID, ApptDate);

Consulta:

DECLARE @PatientId INT = 0,
        @PrevPatientId INT,
        @FirstApptDate DATE = NULL;

UPDATE Appt_temp
SET  @PrevPatientId = @PatientId
    ,@PatientId     = PatientID 
    ,@FirstApptDate = CASE WHEN @PrevPatientId <> @PatientId THEN ApptDate
                           WHEN DATEDIFF(DAY, @FirstApptDate, ApptDate)>30 THEN ApptDate
                           ELSE @FirstApptDate
                      END
    ,Category       = CASE WHEN @PrevPatientId <> @PatientId THEN 'New'
                           WHEN @FirstApptDate = ApptDate THEN 'New'
                           ELSE 'FollowUp' 
                      END
FROM Appt_temp WITH(INDEX(Idx_appt))
OPTION (MAXDOP 1);

SELECT * FROM  Appt_temp ORDER BY PatientId, ApptDate;

db <> violín actualización peculiar

Lukasz Szozda
fuente
1
Tu lógica se parece mucho a la mía. ¿Puedes describir alguna diferencia significativa?
pwilcox
@pwilcox Cuando escribí esta respuesta, todas las existentes usaban simple row_number que no funcionaba, es por eso que proporcioné mi propia versión
Lukasz Szozda
Sí, fui demasiado rápido con la respuesta. Gracias por comentar sobre eso.
Irdis
2
Creo que rcte es la única solución para esto hasta que el servidor SQL implemente correctamente la RANGE x PRECEDINGcláusula.
Salman A
1
La actualización peculiar de @LCJ se basa en el comportamiento "indocumentado" y podría cambiar en cualquier momento sin previo aviso ( red-gate.com/simple-talk/sql/learn-sql-server/… )
Lukasz Szozda
5

Podrías hacer esto con un cte recursivo. Primero debe ordenar por fecha de cita dentro de cada paciente. Eso se puede lograr con un cte de rutina.

Luego, en la parte de anclaje de su cte recursivo, seleccione el primer pedido para cada paciente, marque el estado como 'nuevo' y marque la fecha de solicitud como la fecha del registro 'nuevo' más reciente.

En la parte recursiva de su cte recursivo, incremente a la próxima cita, calcule la diferencia en días entre la cita actual y la fecha de la cita 'nueva' más reciente. Si es mayor de 30 días, márquelo como 'nuevo' y restablezca la fecha de la nueva cita más reciente. De lo contrario, márquelo como 'seguimiento' y simplemente pase los días existentes desde la nueva fecha de la cita.

Finalmente, en la consulta base, simplemente seleccione las columnas que desee.

with orderings as (

    select       *, 
                 rn = row_number() over(
                     partition by patientId 
                     order by apptDate
                 ) 
    from         #appt1 a

),

markings as (

    select       apptId, 
                 patientId, 
                 apptDate, 
                 rn, 
                 type = convert(varchar(10),'new'),
                 dateOfNew = apptDate
    from         orderings 
    where        rn = 1

    union all
    select       o.apptId, o.patientId, o.apptDate, o.rn,
                 type = convert(varchar(10),iif(ap.daysSinceNew > 30, 'new', 'follow up')),
                 dateOfNew = iif(ap.daysSinceNew > 30, o.apptDate, m.dateOfNew)
    from         markings m
    join         orderings o 
                     on m.patientId = o.patientId 
                     and m.rn + 1 = o.rn
    cross apply  (select daysSinceNew = datediff(day, m.dateOfNew, o.apptDate)) ap

)

select    apptId, patientId, apptDate, type
from      markings
order by  patientId, rn;

Debo mencionar que inicialmente eliminé esta respuesta porque la respuesta de Abhijeet Khandagale parecía satisfacer sus necesidades con una consulta más simple (después de reelaborarla un poco). Pero con su comentario sobre los requisitos de su negocio y sus datos de muestra agregados, eliminé los míos porque creo que este satisface sus necesidades.

pwilcox
fuente
4

No estoy seguro de que sea exactamente lo que implementaste. Pero otra opción, que vale la pena mencionar además de usar cte, es usar la tabla temporal y actualizar en "rondas". Así que vamos a actualizar la tabla temporal mientras que todos los estados no están configurados correctamente y generar resultados de forma iterativa. Podemos controlar el número de iteraciones usando simplemente una variable local.

Entonces dividimos cada iteración en dos etapas.

  1. Establezca todos los valores de Seguimiento cercanos a Nuevos registros. Eso es bastante fácil de hacer simplemente usando el filtro correcto.
  2. Para el resto de los registros que no tienen un estado establecido, podemos seleccionar primero en el grupo con el mismo Id. De paciente. Y decir que son nuevos ya que no se procesaron en la primera etapa.

Entonces

CREATE TABLE #Appt2 (ApptID INT, PatientID INT, ApptDate DATE, AppStatus nvarchar(100))

select * from #Appt1
insert into #Appt2 (ApptID, PatientID, ApptDate, AppStatus)
select a1.ApptID, a1.PatientID, a1.ApptDate, null from #Appt1 a1
declare @limit int = 0;

while (exists(select * from #Appt2 where AppStatus IS NULL) and @limit < 1000)
begin
  set @limit = @limit+1;
  update a2
  set
    a2.AppStatus = IIF(exists(
        select * 
        from #Appt2 a 
        where 
          0 > DATEDIFF(day, a2.ApptDate, a.ApptDate) 
          and DATEDIFF(day, a2.ApptDate, a.ApptDate) > -30 
          and a.ApptID != a2.ApptID 
          and a.PatientID = a2.PatientID
          and a.AppStatus = 'New'
          ), 'Followup', a2.AppStatus)
  from #Appt2 a2

  --select * from #Appt2

  update a2
  set a2.AppStatus = 'New'
  from #Appt2 a2 join (select a.*, ROW_NUMBER() over (Partition By PatientId order by ApptId) rn from (select * from #Appt2 where AppStatus IS NULL) a) ar
  on a2.ApptID = ar.ApptID
  and ar.rn = 1

  --select * from #Appt2

end

select * from #Appt2 order by PatientID, ApptDate

drop table #Appt1
drop table #Appt2

Actualizar. Lea el comentario proporcionado por Lukasz. Es, con mucho, la forma más inteligente. Dejo mi respuesta solo como una idea.

Irdis
fuente
4

Creo que la expresión común recursiva es una excelente manera de optimizar las consultas evitando bucles, pero en algunos casos puede conducir a un mal rendimiento y debe evitarse si es posible.

Utilizo el código a continuación para resolver el problema y probarlo con más valores, pero le recomiendo que también lo pruebe con sus datos reales.

WITH DataSource AS
(
    SELECT *
          ,CEILING(DATEDIFF(DAY, MIN([ApptDate]) OVER (PARTITION BY [PatientID]), [ApptDate]) * 1.0 / 30 + 0.000001) AS [GroupID]
    FROM #Appt1
)
SELECT *
     ,IIF(ROW_NUMBER() OVER (PARTITION BY [PatientID], [GroupID] ORDER BY [ApptDate]) = 1, 'New', 'Followup')
FROM DataSource
ORDER BY [PatientID]
        ,[ApptDate];

ingrese la descripción de la imagen aquí

La idea es bastante simple: quiero separar los registros en grupo (30 días), en qué grupo está el registro más pequeño new, los demás lo están follow ups. Verifique cómo se construye la declaración:

SELECT *
      ,DATEDIFF(DAY, MIN([ApptDate]) OVER (PARTITION BY [PatientID]), [ApptDate])
      ,DATEDIFF(DAY, MIN([ApptDate]) OVER (PARTITION BY [PatientID]), [ApptDate]) * 1.0 / 30
      ,CEILING(DATEDIFF(DAY, MIN([ApptDate]) OVER (PARTITION BY [PatientID]), [ApptDate]) * 1.0 / 30 + 0.000001) 
FROM #Appt1
ORDER BY [PatientID]
        ,[ApptDate];

ingrese la descripción de la imagen aquí

Entonces:

  1. primero, estamos obteniendo la primera fecha, para cada grupo y calculando las diferencias en días con la actual
  2. entonces, queremos obtener grupos - * 1.0 / 30se agrega
  3. En cuanto a los días 30, 60, 90, etc., estamos obteniendo un número entero y queríamos comenzar un nuevo período, he agregado + 0.000001; Además, estamos utilizando la función de techo para obtener elsmallest integer greater than, or equal to, the specified numeric expression

Eso es. Teniendo dicho grupo simplemente usamos ROW_NUMBERpara encontrar nuestra fecha de inicio y que sea lo newy dejando el resto como follow ups.

gotqn
fuente
2
Bueno, la pregunta es un poco diferente y este enfoque es una simplificación excesiva. Pero es un buen ejemplo de cómo implementar la ventana de volteo
Lukasz Szozda
También se trata de rendimiento. Creo que el recursivo debería ser más lento.
gotqn
3

Con el debido respeto a todos y en mi humilde opinión,

There is not much difference between While LOOP and Recursive CTE in terms of RBAR

No hay mucho aumento de rendimiento cuando se usa Recursive CTEy Window Partition functiontodo en uno.

Appiddebería ser int identity(1,1), o debería estar aumentando cada vez más clustered index.

Además de otros beneficios, también asegura que todas las filas sucesivas APPDatede ese paciente deben ser mayores.

De esta manera, puede jugar fácilmente APPIDen su consulta, que será más eficiente que poner inequalityoperador como>, <en APPDate. Poner al inequalityoperador como>, <en APPID ayudará a Sql Optimizer.

También debe haber dos columnas de fecha en la tabla como

APPDateTime datetime2(0) not null,
Appdate date not null

Como estas son las columnas más importantes en la tabla más importante, entonces no hay mucho reparto, conversión.

Entonces Non clustered indexse puede crear en Appdate

Create NonClustered index ix_PID_AppDate_App  on APP (patientid,APPDate) include(other column which is not i predicate except APPID)

Pruebe mi script con otros datos de muestra y déjeme saber para qué datos de muestra no funciona. Incluso si no funciona, estoy seguro de que puede solucionarse en la lógica de mi script.

CREATE TABLE #Appt1 (ApptID INT, PatientID INT, ApptDate DATE)
INSERT INTO #Appt1
SELECT  1,101,'2020-01-05'  UNION ALL
SELECT  2,505,'2020-01-06'  UNION ALL
SELECT  3,505,'2020-01-10'  UNION ALL
SELECT  4,505,'2020-01-20'  UNION ALL
SELECT  5,101,'2020-01-25'  UNION ALL
SELECT  6,101,'2020-02-12'  UNION ALL
SELECT  7,101,'2020-02-20'  UNION ALL
SELECT  8,101,'2020-03-30'  UNION ALL
SELECT  9,303,'2020-01-28'  UNION ALL
SELECT  10,303,'2020-02-02' 

;With CTE as
(
select a1.* ,a2.ApptDate as NewApptDate
from #Appt1 a1
outer apply(select top 1 a2.ApptID ,a2.ApptDate
from #Appt1 A2 
where a1.PatientID=a2.PatientID and a1.ApptID>a2.ApptID 
and DATEDIFF(day,a2.ApptDate, a1.ApptDate)>30
order by a2.ApptID desc )A2
)
,CTE1 as
(
select a1.*, a2.ApptDate as FollowApptDate
from CTE A1
outer apply(select top 1 a2.ApptID ,a2.ApptDate
from #Appt1 A2 
where a1.PatientID=a2.PatientID and a1.ApptID>a2.ApptID 
and DATEDIFF(day,a2.ApptDate, a1.ApptDate)<=30
order by a2.ApptID desc )A2
)
select  * 
,case when FollowApptDate is null then 'New' 
when NewApptDate is not null and FollowApptDate is not null 
and DATEDIFF(day,NewApptDate, FollowApptDate)<=30 then 'New'
else 'Followup' end
 as Category
from cte1 a1
order by a1.PatientID

drop table #Appt1
KumarHarsh
fuente
3

Aunque no se trata con claridad en la pregunta, es fácil darse cuenta de que las fechas de las citas no se pueden clasificar simplemente por grupos de 30 días. No tiene sentido comercial. Y tampoco puedes usar el id de appt. Uno puede hacer una nueva cita hoy para2020-09-06. Así es como abordo este problema. Primero, obtenga la primera cita, luego calcule la diferencia de fecha entre cada cita y la primera cita. Si es 0, configúrelo en 'Nuevo'. Si <= 30 'Seguimiento'. Si> 30, configúrelo como 'Indeciso' y realice la siguiente ronda de verificación hasta que no haya más 'Indeciso'. Y para eso, realmente necesita un ciclo while, pero no recorre cada fecha de cita, sino solo unos pocos conjuntos de datos. Revisé el plan de ejecución. A pesar de que solo hay 10 filas, el costo de la consulta es significativamente menor que el uso de CTE recursivo, pero no tan bajo como el método de apéndice de Lukasz Szozda.

IF OBJECT_ID('tempdb..#TEMPTABLE') IS NOT NULL DROP TABLE #TEMPTABLE
SELECT ApptID, PatientID, ApptDate
    ,CASE WHEN (DATEDIFF(DAY, MIN(ApptDate) OVER (PARTITION BY PatientID), ApptDate) = 0) THEN 'New' 
    WHEN (DATEDIFF(DAY, MIN(ApptDate) OVER (PARTITION BY PatientID), ApptDate) <= 30) THEN 'Followup'
    ELSE 'Undecided' END AS Category
INTO #TEMPTABLE
FROM #Appt1

WHILE EXISTS(SELECT TOP 1 * FROM #TEMPTABLE WHERE Category = 'Undecided') BEGIN
    ;WITH CTE AS (
        SELECT ApptID, PatientID, ApptDate 
            ,CASE WHEN (DATEDIFF(DAY, MIN(ApptDate) OVER (PARTITION BY PatientID), ApptDate) = 0) THEN 'New' 
            WHEN (DATEDIFF(DAY, MIN(ApptDate) OVER (PARTITION BY PatientID), ApptDate) <= 30) THEN 'Followup'
            ELSE 'Undecided' END AS Category    
        FROM #TEMPTABLE
        WHERE Category = 'Undecided'
    )
    UPDATE #TEMPTABLE
    SET Category = CTE.Category
    FROM #TEMPTABLE t
        LEFT JOIN CTE ON CTE.ApptID = t.ApptID
    WHERE t.Category = 'Undecided'
END

SELECT ApptID, PatientID, ApptDate, Category 
FROM #TEMPTABLE
Weihui Guo
fuente
2

Espero que esto ayude.

WITH CTE AS
(
    SELECT #Appt1.*, RowNum = ROW_NUMBER() OVER (PARTITION BY PatientID ORDER BY ApptDate, ApptID) FROM #Appt1
)

SELECT A.ApptID , A.PatientID , A.ApptDate ,
Expected_Category = CASE WHEN (DATEDIFF(MONTH, B.ApptDate, A.ApptDate) > 0) THEN 'New' 
WHEN (DATEDIFF(DAY, B.ApptDate, A.ApptDate) <= 30) then 'Followup' 
ELSE 'New' END
FROM CTE A
LEFT OUTER JOIN CTE B on A.PatientID = B.PatientID 
AND A.rownum = B.rownum + 1
ORDER BY A.PatientID, A.ApptDate
Abhijeet Khandagale
fuente
Gracias @ x00 por editar el código en formato legible, estoy usando mi teléfono celular para publicar respuestas, por lo que no pude hacer las sangrías adecuadas.
Abhijeet Khandagale
Creo que esta es, en esencia, la respuesta correcta. Pero es una respuesta de baja calidad, ya que no se explica y el código tiene una consulta externa innecesaria cuando una modificación de la porción interna funcionará bien. Si puede resolver esos problemas, me complacerá votarlo.
pwilcox
1
@pwilcox, gracias por la valiosa sugerencia, he editado la respuesta y la he publicado a partir de ahora. Como viajo y no tengo una computadora portátil, publicaré explicaciones en uno o dos días.
Abhijeet Khandagale
1
@AbhijeetKhandagale Esto no cumple con los requisitos comerciales por completo. He agregado un escenario fallido en la pregunta. Para el paciente 303, la cita del 2 de febrero debe ser de seguimiento; pero su consulta dice que es "Nuevo"
LCJ
1

Podrías usar una Casedeclaración .

select 
      *, 
      CASE 
          WHEN DATEDIFF(d,A1.ApptDate,A2.ApptDate)>30 THEN 'New' 
          ELSE 'FollowUp' 
      END 'Category'
from 
      (SELECT PatientId, MIN(ApptId) 'ApptId', MIN(ApptDate) 'ApptDate' FROM #Appt1 GROUP BY PatientID)  A1, 
      #Appt1 A2 
where 
     A1.PatientID=A2.PatientID AND A1.ApptID<A2.ApptID

La pregunta es, ¿debería asignarse esta categoría según la cita inicial o la anterior? Es decir, si un paciente ha tenido tres citas, ¿deberíamos comparar la tercera cita con la primera o la segunda?

Tu problema es el primero, que es como he respondido. Si ese no es el caso, querrás usarlo lag.

Además, tenga en cuenta que DateDiffno es una excepción para los fines de semana. Si esto fuera solo de lunes a viernes, deberá crear su propia función de valor escalar.

usuario
fuente
1
Esto no vincula dos citas secuenciales, vincula la cita 1 a todas las citas siguientes y calcula los días intermedios para todas ellas. Devolvería demasiados registros de esta manera, ya que la app 1 ahora tiene una relación con 2, 3, 4, la app 2 tiene una relación con 3, 4 ...
steenbergh
Buen punto. Actualicé mi respuesta para hacer una subselección para A1.
usuario
1
No da el resultado esperado. La cita del 20 de febrero debe ser "Seguimiento"
LCJ
La pregunta no está clara ... La descripción del póster es la siguiente: "Cualquier cita (para un paciente) dentro de los 30 días posteriores a la primera cita (de ese paciente) es Seguimiento. Después de 30 días, la cita es nuevamente" Nueva ". Cualquier cita dentro de los 30 días convertirse en "Seguimiento". El 5 de enero está ciertamente a más de 30 días del 20 de febrero, es decir, Nuevo. Sin embargo, NO faltan 30 días para el 12 de febrero. Ofrezco una solución a lo que escribió, no la tabla suministrada. Si el usuario desea alinearse con lo que proporciona la tabla, debe usar el retraso. También deberían aclarar ...
usuario
1

usando la función Lag


select  apptID, PatientID , Apptdate ,  
    case when date_diff IS NULL THEN 'NEW' 
         when date_diff < 30 and (date_diff_2 IS NULL or date_diff_2 < 30) THEN  'Follow Up'
         ELSE 'NEW'
    END AS STATUS FROM 
(
select 
apptID, PatientID , Apptdate , 
DATEDIFF (day,lag(Apptdate) over (PARTITION BY PatientID order by ApptID asc),Apptdate) date_diff ,
DATEDIFF(day,lag(Apptdate,2) over (PARTITION BY PatientID order by ApptID asc),Apptdate) date_diff_2
  from #Appt1
) SRC

Demostración -> https://rextester.com/TNW43808

Digvijay S
fuente
2
Esto funciona en los datos de la muestra actual, pero podría arrojar resultados incorrectos si se tienen datos de muestra diferentes. Incluso si lo usa apptDatecomo la order bycolumna de la lagfunción (lo que realmente debería como id no es garantía de nada), aún puede romperse fácilmente al introducir más citas de seguimiento. Vea esta demostración de Rextester por ejemplo. Buen intento, sin embargo ...
Zohar Peled
Gracias. Debería haber usado la fecha en lugar de la identificación. Pero por qué está mal para apptID = 6 25.01.2020 - 12.02.2020 -> 18 días -> seguimiento.
Digvijay S
2
Porque debería ser un Newy no un FollowUp. Han pasado más de 30 días desde la primera cita de ese paciente ... Debería contar 30 días desde cada Newcita y luego usar una Newvez más ...
Zohar Peled
Si. Gracias. :( Necesidad de crear un nuevo para verificar el período de fecha válido.
Digvijay S
1
with cte
as
(
select 
tmp.*, 
IsNull(Lag(ApptDate) Over (partition by PatientID Order by  PatientID,ApptDate),ApptDate) PriorApptDate
 from #Appt1 tmp
)
select 
PatientID, 
ApptDate, 
PriorApptDate, 
DateDiff(d,PriorApptDate,ApptDate) Elapsed,
Case when DateDiff(d,PriorApptDate,ApptDate)>30 
or DateDiff(d,PriorApptDate,ApptDate)=0 then 'New' else 'Followup'   end Category   from cte

La mía es correcta. Los autores fueron incorrectos, ver transcurrido

Leon de Oro
fuente