Zapytanie bez WHILE Loop

18

Mamy tabelę spotkań, jak pokazano poniżej. Każde spotkanie należy sklasyfikować jako „Nowe” lub „Kontynuacja”. Każda wizyta (dla pacjenta) w ciągu 30 dni od pierwszej wizyty (tego pacjenta) jest kontynuacją. Po 30 dniach spotkanie jest ponownie „Nowe”. Każde spotkanie w ciągu 30 dni staje się „kontynuacją”.

Obecnie robię to, pisząc w pętli while.
Jak to zrobić bez PĘTLI?

wprowadź opis zdjęcia tutaj

Stół

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
źródło
Nie widzę twojego obrazu, ale chcę potwierdzić, że jeśli są 3 spotkania, co 20 dni od siebie, ostatni jest nadal „kontynuacją”, bo chociaż upłynęło ponad 30 dni od pierwszego, od środka jest jeszcze mniej niż 20 dni. Czy to prawda?
pwilcox
@pwilcox Nie. Trzecim będzie nowe spotkanie, jak pokazano na zdjęciu
LCJ
Podczas gdy pętla nad fast_forwardkursorem byłaby prawdopodobnie najlepszą opcją, pod względem wydajności.
David Markודו Markovitz

Odpowiedzi:

14

Musisz użyć zapytania rekurencyjnego.

30-dniowy okres liczony jest od poprzedniego (i nie, nie można tego zrobić bez rekurencji / dziwacznej aktualizacji / pętli). Właśnie dlatego wszystkie istniejące odpowiedzi przy użyciu tylko ROW_NUMBERnie powiodły się.

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;  

db <> demo skrzypiec

Wynik:

+---------+------------+-------------+----------+
| 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 |
+---------+------------+-------------+----------+

Jak to działa:

  1. f - uzyskaj punkt początkowy (kotwica - na każdy identyfikator pacjenta)
  2. rec - recursibe part, jeśli różnica między wartością bieżącą a poprzednią wynosi> 30, zmień kategorię i punkt początkowy, w kontekście PatientId
  3. Główny - wyświetla posortowany zestaw wyników

Podobna klasa:

Warunkowe SUM na Oracle - Ograniczenie funkcji okna

Okno sesji (Azure Stream Analytics)

Działająca suma do momentu spełnienia określonego warunku - Dziwaczna aktualizacja


Uzupełnienie

Nigdy nie używaj tego kodu w produkcji!

Ale inną opcją, o której warto wspomnieć oprócz używania cte, jest użycie tabeli temp i aktualizacja w „rundach”

Można to zrobić w rundzie pojedynczej (dziwaczna aktualizacja):

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);

Pytanie:

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 <> skrzypce Dziwaczna aktualizacja

Łukasz Szozda
źródło
1
twoja logika wygląda bardzo podobnie do mojej. Czy możesz opisać jakieś znaczące różnice?
pwilcox
@pwilcox Kiedy pisałem tę odpowiedź, każdy istniejący używał prostego numeru wiersza, który nie działał, dlatego podałem własną wersję
Łukasz Szozda
Tak, byłem zbyt szybki z odpowiedzią. Dziękujemy za komentowanie tego.
Irdis
2
Wierzę, że rcte jest jedynym rozwiązaniem tego problemu, dopóki serwer SQL poprawnie nie zaimplementuje RANGE x PRECEDINGklauzuli.
Salman A
1
@LCJ Dziwaczna aktualizacja oparta jest na „nieudokumentowanym” zachowaniu i może ulec zmianie w dowolnym momencie bez powiadomienia ( red-gate.com/simple-talk/sql/learn-sql-server/… )
Łukasz Szozda
5

Możesz to zrobić z rekurencyjnym cte. Najpierw należy zamówić według apptDate w obrębie każdego pacjenta. Można to osiągnąć za pomocą cte-run-of-the-mill.

Następnie, w części kotwiczącej swojego rekurencyjnego cte, wybierz pierwsze zamówienie dla każdego pacjenta, zaznacz status jako „nowy”, a także zaznacz apptDate jako datę najnowszego „nowego” rekordu.

W rekursywnej części Twojego rekurencyjnego cte, przyrostu do następnego spotkania, oblicz różnicę dni między obecnym spotkaniem a ostatnią „nową” datą spotkania. Jeśli jest on dłuższy niż 30 dni, zaznacz go jako „nowy” i zresetuj ostatnią nową datę spotkania. W przeciwnym razie zaznacz go jako „kontynuuj” i po prostu przekaż dni, które upłynęły od nowej daty spotkania.

Na koniec, w zapytaniu podstawowym, po prostu wybierz odpowiednie kolumny.

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;

Powinienem wspomnieć, że początkowo usunąłem tę odpowiedź, ponieważ odpowiedź Abhijeeta Khandagale'a wydawała się odpowiadać twoim potrzebom za pomocą prostszego zapytania (po nieco przerobionej przeróbce). Ale wraz z twoim komentarzem na temat twoich wymagań biznesowych i dodanych przez ciebie przykładowych danych, usunąłem moje, ponieważ wierzę, że ten spełnia twoje potrzeby.

pwilcox
źródło
4

Nie jestem pewien, czy to dokładnie to, co zaimplementowałeś. Ale inną opcją, o której warto wspomnieć oprócz używania cte, jest użycie tabeli temp i aktualizacja w „rundach”. Zamierzamy więc zaktualizować tabelę temp, gdy wszystkie statusy nie są ustawione poprawnie, i zbudować wynik w sposób iteracyjny. Możemy kontrolować liczbę iteracji za pomocą po prostu zmiennej lokalnej.

Dlatego podzieliliśmy każdą iterację na dwa etapy.

  1. Ustaw wszystkie wartości śledzenia, które są zbliżone do nowych rekordów. Jest to dość łatwe, wystarczy użyć odpowiedniego filtra.
  2. Dla pozostałych rekordów, które nie mają ustawionego statusu, możemy wybrać najpierw w grupie o tym samym ID pacjenta. I powiedz, że są nowe, ponieważ nie zostały przetworzone przez pierwszy etap.

Więc

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

Aktualizacja. Przeczytaj komentarz podany przez Łukasza. To zdecydowanie mądrzejszy sposób. Swoją odpowiedź zostawiam jako pomysł.

Irdis
źródło
4

Uważam, że rekurencyjne wspólne wyrażenie jest świetnym sposobem na optymalizację zapytań, unikając pętli, ale w niektórych przypadkach może prowadzić do złej wydajności i należy tego unikać, jeśli to możliwe.

Korzystam z poniższego kodu, aby rozwiązać problem i przetestować, czy przyniesie więcej wartości, ale zachęcam do przetestowania go również na prawdziwych danych.

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];

wprowadź opis zdjęcia tutaj

Pomysł jest dość prosty - chcę oddzielić rekordy w grupie (30 dni), w której grupie jest najmniejszy rekord new, a pozostałe są follow ups. Sprawdź, jak zbudowana jest instrukcja:

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];

wprowadź opis zdjęcia tutaj

Więc:

  1. po pierwsze, otrzymujemy pierwszą datę dla każdej grupy i obliczamy różnice dni w stosunku do bieżącej
  2. następnie chcemy uzyskać grupy - * 1.0 / 30dodaje
  3. co do 30, 60, 90 itd. dni otrzymujemy liczbę całkowitą i chcieliśmy rozpocząć nowy okres, dodałem + 0.000001; używamy również funkcji sufitu, aby uzyskaćsmallest integer greater than, or equal to, the specified numeric expression

Otóż ​​to. Mając taką grupę, po prostu używamy, ROW_NUMBERaby znaleźć naszą datę rozpoczęcia i ustawić ją jako, newa resztę pozostawić jako follow ups.

gotqn
źródło
2
Cóż, pytanie jest nieco inne, a to apporach jest nadmiernym uproszczeniem. Ale jest to dobry przykład, jak zaimplementować zawijane okno
Łukasz Szozda
Chodzi również o wydajność. Uważam, że rekurencyjne powinny być wolniejsze.
gotqn
3

Z należytym szacunkiem dla wszystkich i w IMHO,

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

Nie ma większego wzrostu wydajności podczas używania Recursive CTEi Window Partition functionwszystko w jednym.

Appidpowinno być int identity(1,1)lub powinno stale rosnąć clustered index.

Oprócz innych korzyści zapewnia również, że wszystkie kolejne rzędy APPDatetego pacjenta muszą być większe.

W ten sposób możesz łatwo grać APPIDw swoim zapytaniu, które będzie bardziej wydajne niż wstawianie inequalityoperatora takiego jak>, <w APPDate. Umieszczenie inequalityoperatora takiego jak>, <w APPID pomoże Sql Optimizer.

W tabeli powinny znajdować się dwie kolumny daty

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

Ponieważ są to najważniejsze kolumny w najważniejszej tabeli, więc niewiele rzutów, konwersja.

Non clustered indexMożna go więc utworzyć w Appdate

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

Przetestuj mój skrypt z innymi przykładowymi danymi i dowiedz się, dla których przykładowych danych nie działa. Nawet jeśli to nie działa, jestem pewien, że można to naprawić w samej logice skryptu.

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
źródło
3

Chociaż nie jest to wyraźnie określone w pytaniu, łatwo jest stwierdzić, że dat spotkań nie można po prostu podzielić na kategorie według grup 30-dniowych. To nie ma sensu biznesowego. Nie można również użyć identyfikatora appt. Można dziś umówić się na nowy termin2020-09-06. Oto jak rozwiązuję ten problem. Najpierw zdobądź pierwsze spotkanie, a następnie oblicz różnicę dat między każdym spotkaniem a pierwszym appt. Jeśli wynosi 0, ustaw „Nowy”. Jeśli <= 30 „Kontynuacja”. Jeśli> 30, ustaw jako „Niezdecydowany” i wykonaj kolejną kontrolę rundy, aż nie będzie już „Niezdecydowany”. I do tego naprawdę potrzebujesz pętli while, ale nie przechodzi ona przez każdą datę spotkania, a jedynie kilka zestawów danych. Sprawdziłem plan wykonania. Mimo że jest tylko 10 wierszy, koszt zapytania jest znacznie niższy niż przy użyciu rekurencyjnej CTE, ale nie tak niski, jak metoda addendum Łukasza Szozdy.

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
źródło
2

Mam nadzieję, że to Ci pomoże.

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
źródło
Dzięki @ x00 za edycję kodu w czytelnym formacie, używam telefonu komórkowego do publikowania odpowiedzi, więc nie byłem w stanie podać odpowiednich wcięć.
Abhijeet Khandagale
Myślę, że to właściwie właściwa odpowiedź. Ale jest to odpowiedź niskiej jakości, ponieważ nie jest wyjaśniona, a kod ma niepotrzebne zewnętrzne zapytanie, gdy modyfikacja wewnętrznej części da się dobrze. Jeśli możesz rozwiązać te problemy, chętnie cię głosuję.
pwilcox
1
@pwilcox, dzięki za cenną sugestię, zredagowałem odpowiedź i opublikowałem ją na teraz. Gdy podróżuję i nie mam przy sobie laptopa, będę zamieszczać wyjaśnienia za dzień lub dwa.
Abhijeet Khandagale
1
@AbhijeetKhandagale Nie spełnia to całkowicie wymagań biznesowych. W pytaniu dodałem scenariusz niepowodzenia. W przypadku pacjenta 303, 2 lutego należy umówić się na wizytę kontrolną; ale zapytanie mówi, że jest „Nowy”
LCJ
1

Możesz użyć Caseoświadczenia .

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

Pytanie brzmi: czy tę kategorię należy przypisać na podstawie pierwszego spotkania, czy poprzedniego? To znaczy, jeśli pacjent miał trzy wizyty, czy powinniśmy porównać trzecie spotkanie z pierwszym, czy drugim?

Twój problem stwierdza pierwszy, a ja tak odpowiedziałem. Jeśli tak nie jest, będziesz chciał użyć lag.

Pamiętaj też, DateDiffże nie stanowi to wyjątku w weekendy. Jeśli powinny to być tylko dni powszednie, musisz utworzyć własną funkcję Valared.

użytkownik
źródło
1
Nie łączy to dwóch kolejnych spotkań, łączy appt 1 do wszystkich kolejnych spotkań i oblicza dni między nimi dla wszystkich. W ten sposób zwrócisz zbyt wiele rekordów, ponieważ appt 1 ma teraz relację z 2, 3, 4, appt 2 ma relację z 3, 4 ...
steenbergh
Słuszna uwaga. Zaktualizowałem swoją odpowiedź, aby wykonać podselekcję dla A1.
użytkownik
1
Nie daje oczekiwanego rezultatu. 20 lutego spotkanie powinno odbyć się „Followup”
LCJ
Pytanie jest niejasne ... Opis plakatu jest następujący: „Każde spotkanie (dla pacjenta) w ciągu 30 dni od pierwszej wizyty (tego pacjenta) jest kontynuacją. Po 30 dniach spotkanie jest ponownie„ Nowe ”. Każde spotkanie z 30 dni zostanie „kontynuacją”. ” 5 stycznia to z pewnością więcej niż 30 dni od 20 lutego, czyli Nowość. Jednak NIE jest to 30 dni od 12 lutego. Oferuję rozwiązanie tego, co napisał, a nie dostarczony stół. Jeśli użytkownik chce dopasować się do tego, co dostarcza tabela, powinien użyć opóźnienia. Powinny również wyjaśnić ...
użytkownik
1

za pomocą funkcji 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

Demo -> https://rextester.com/TNW43808

Digvijay S
źródło
2
Działa to na bieżących przykładowych danych, ale może dać nieprawidłowe wyniki, biorąc pod uwagę inne przykładowe dane. Nawet jeśli użyjesz go apptDatejako order bykolumny lagfunkcji (co tak naprawdę powinieneś, ponieważ id nie jest gwarancją czegokolwiek), nadal można go łatwo złamać, wprowadzając kolejne spotkania kontrolne. Zobacz na przykład demo Rextester . Dobra próba ...
Zohar Peled
Dziękuję Ci. Powinien był użyć daty zamiast ID. Ale dlaczego jest to złe dla apptID = 6 25.01.2020 - 12.02.2020 -> 18 dni -> kontynuacja.
Digvijay S
2
Ponieważ powinno to być Newa nie FollowUp. Minęło ponad 30 dni od pierwszej wizyty tego pacjenta ... Powinieneś policzyć 30 dni od każdej Newwizyty, a następnie użyć Newponownie ...
Zohar Peled
Tak. Dziękuję Ci. :( Musisz utworzyć nowy, aby sprawdzić prawidłowy okres.
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

Mój jest poprawny. Autorzy byli niepoprawni, patrz

złoty Lew
źródło