Jest to rodzaj trywialnego zadania w moim ojczystym świecie C #, ale jeszcze nie robię tego w SQL i wolałbym rozwiązywać go w oparciu o zestaw (bez kursorów). Zestaw wyników powinien pochodzić z takiego zapytania.
SELECT SomeId, MyDate,
dbo.udfLastHitRecursive(param1, param2, MyDate) as 'Qualifying'
FROM T
Jak to powinno działać
Wysyłam te trzy parametry do UDF.
UDF wewnętrznie używa parametrów, aby pobrać z widoku powiązane <= 90 dni starsze wiersze.
Funkcja UDF przechodzi przez „MyDate” i zwraca 1, jeśli powinna zostać uwzględniona w obliczeniach całkowitych.
Jeśli nie, to zwraca 0. Nazwany tutaj jako „kwalifikujący się”.
Co zrobi udf
Wyświetl wiersze w kolejności dat. Oblicz dni między rzędami. Pierwszy rząd w zestawie wyników domyślnie wynosi Hit = 1. Jeśli różnica wynosi do 90, - następnie przejdź do następnego rzędu, aż suma przerw wyniesie 90 dni (musi minąć 90 dzień) Po osiągnięciu ustaw Hit na 1 i zresetuj lukę na 0 Można także zamiast tego pominąć wiersz wyniku.
|(column by udf, which not work yet)
Date Calc_date MaxDiff | Qualifying
2014-01-01 11:00 2014-01-01 0 | 1
2014-01-03 10:00 2014-01-01 2 | 0
2014-01-04 09:30 2014-01-03 1 | 0
2014-04-01 10:00 2014-01-04 87 | 0
2014-05-01 11:00 2014-04-01 30 | 1
W powyższej tabeli kolumna MaxDiff to odstęp od daty w poprzednim wierszu. Problem z moimi dotychczasowymi próbami polega na tym, że nie mogę zignorować drugiego rzędu w powyższej próbce.
[EDYCJA]
Zgodnie z komentarzem dodaję tag, a także wklejam udf, który właśnie skompilowałem. Chociaż jest to tylko symbol zastępczy i nie przyniesie użytecznego rezultatu.
;WITH cte (someid, otherkey, mydate, cost) AS
(
SELECT someid, otherkey, mydate, cost
FROM dbo.vGetVisits
WHERE someid = @someid AND VisitCode = 3 AND otherkey = @otherkey
AND CONVERT(Date,mydate) = @VisitDate
UNION ALL
SELECT top 1 e.someid, e.otherkey, e.mydate, e.cost
FROM dbo.vGetVisits AS E
WHERE CONVERT(date, e.mydate)
BETWEEN DateAdd(dd,-90,CONVERT(Date,@VisitDate)) AND CONVERT(Date,@VisitDate)
AND e.someid = @someid AND e.VisitCode = 3 AND e.otherkey = @otherkey
AND CONVERT(Date,e.mydate) = @VisitDate
order by e.mydate
)
Mam inne zapytanie, które osobno definiuję, które jest bardziej zbliżone do tego, czego potrzebuję, ale zablokowane przez fakt, że nie mogę obliczyć kolumn w okienkach. Próbowałem też jednego podobnego, który daje mniej więcej taki sam wynik tylko z LAG () nad MyDate, otoczony datownikiem.
SELECT
t.Mydate, t.VisitCode, t.Cost, t.SomeId, t.otherkey, t.MaxDiff, t.DateDiff
FROM
(
SELECT *,
MaxDiff = LAST_VALUE(Diff.Diff) OVER (
ORDER BY Diff.Mydate ASC
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
FROM
(
SELECT *,
Diff = ISNULL(DATEDIFF(DAY, LAST_VALUE(r.Mydate) OVER (
ORDER BY r.Mydate ASC
ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING),
r.Mydate),0),
DateDiff = ISNULL(LAST_VALUE(r.Mydate) OVER (
ORDER BY r.Mydate ASC
ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING),
r.Mydate)
FROM dbo.vGetVisits AS r
WHERE r.VisitCode = 3 AND r.SomeId = @SomeID AND r.otherkey = @otherkey
) AS Diff
) AS t
WHERE t.VisitCode = 3 AND t.SomeId = @SomeId AND t.otherkey = @otherkey
AND t.Diff <= 90
ORDER BY
t.Mydate ASC;
źródło
Odpowiedzi:
Gdy czytam pytanie, podstawowy wymagany algorytm rekurencyjny to:
Jest to stosunkowo łatwe do wdrożenia z rekurencyjnym wspólnym wyrażeniem tabelowym.
Na przykład przy użyciu następujących przykładowych danych (na podstawie pytania):
Kod rekurencyjny to:
Wyniki są następujące:
Z indeksem mającym
TheDate
klucz wiodący plan wykonania jest bardzo wydajny:Możesz zawinąć to w funkcję i wykonać bezpośrednio w widoku wymienionym w pytaniu, ale moje instynkty są temu przeciwne. Zwykle wydajność jest lepsza, gdy wybierzesz wiersze z widoku do tabeli tymczasowej, podaj odpowiedni indeks w tabeli tymczasowej, a następnie zastosuj powyższą logikę. Szczegóły zależą od szczegółów widoku, ale takie jest moje ogólne doświadczenie.
Dla kompletności (i podpowiedzi przez odpowiedź ypercube) powinienem wspomnieć, że moim innym rozwiązaniem tego typu problemu (dopóki T-SQL nie otrzyma poprawnie uporządkowanych funkcji zestawu) jest kursor SQLCLR ( zobacz moją odpowiedź tutaj dla przykładu techniki ). Działa to znacznie lepiej niż kursor T-SQL i jest wygodne dla osób posiadających umiejętności posługiwania się językiem .NET i możliwość uruchamiania SQLCLR w środowisku produkcyjnym. W tym scenariuszu może nie oferować wiele w porównaniu z rozwiązaniem rekurencyjnym, ponieważ większość kosztów jest tego rodzaju, ale warto o tym wspomnieć.
źródło
Ponieważ tak jest to pytanie dotyczące programu SQL Server 2014, równie dobrze mogę dodać natywnie skompilowaną wersję „kursora” procedury składowanej.
Tabela źródłowa z niektórymi danymi:
Typ tabeli, który jest parametrem procedury składowanej. Dostosuj
bucket_count
odpowiednio .I procedura składowana, która zapętla parametr o wartości tabeli i zbiera wiersze
@R
.Kod wypełniający zmienną tabelową zoptymalizowaną pod kątem pamięci, która jest używana jako parametr w natywnie skompilowanej procedurze składowanej i wywołuje tę procedurę.
Wynik:
Aktualizacja:
Jeśli z jakiegoś powodu nie musisz odwiedzać każdego wiersza w tabeli, możesz wykonać odpowiednik wersji „przejdź do następnej daty”, która została zaimplementowana w rekurencyjnym CTE przez Paula White'a.
Typ danych nie wymaga kolumny identyfikatora i nie należy używać indeksu skrótu.
Procedura przechowywana używa
select top(1) ..
następnej wartości.źródło
T.TheDate >= dateadd(day, 91, @CurDate)
wszystkie, byłoby dobrze, prawda?TheDate
naTType
naDate
.Rozwiązanie wykorzystujące kursor.
(po pierwsze, niektóre potrzebne tabele i zmienne) :
Rzeczywisty kursor:
I uzyskiwanie wyników:
Testowane w SQLFiddle
źródło
INSERT @cd
tylko wtedy@Qualify=1
(a tym samym nie wstawiać 13M wierszy, jeśli nie potrzebujesz wszystkich z nich na wyjściu). Rozwiązanie zależy od znalezienia indeksuTheDate
. Jeśli nie ma, nie będzie wydajne.Wynik
Zobacz także, jak obliczyć całkowitą liczbę uruchomień w programie SQL Server
aktualizacja: patrz poniżej wyniki testów wydajności.
Z powodu odmiennej logiki znalezienia „90 dni przerwy” ypercube i moje rozwiązania, jeśli pozostawione nietknięte, mogą zwrócić różne wyniki do rozwiązania Paula White'a. Wynika to z użycia odpowiednio funkcji DATEDIFF i DATEADD .
Na przykład:
zwraca „2014-04-01 00: 00: 00.000”, co oznacza, że „2014-04-01 01: 00: 00.000” przekracza 90 dni przerwy
ale
Zwraca „90”, co oznacza, że nadal znajduje się w luce.
Rozważ przykład sprzedawcy. W takim przypadku sprzedaż łatwo psującego się produktu, który sprzedał się przed datą „2014-01-01” o godzinie „2014-01-01 23: 59: 59: 999”, jest w porządku. Zatem wartość DATEDIFF (DZIEŃ, ...) w tym przypadku jest OK.
Innym przykładem jest pacjent czekający na wizytę. Dla kogoś, kto przychodzi o godzinie „2014-01-01 00: 00: 00: 000” i odchodzi o godzinie „2014-01-01 23: 59: 59: 999”, jeśli DATEDIFF jest używany, to 0 (zero) dni faktyczne oczekiwanie trwało prawie 24 godziny. Znowu pacjent, który przychodzi o godzinie „01.01.2014 23:59:59” i odchodzi o godzinie „2014-01-02 00:00:01” czekał na dzień, jeśli zostanie użyty DATEDIFF.
Ale dygresję.
Zostawiłem rozwiązania DATEDIFF, a nawet przetestowałem je pod kątem wydajności, ale naprawdę powinny być w swojej lidze.
Zauważono również, że w przypadku dużych zestawów danych nie można uniknąć wartości tego samego dnia. Więc jeśli powiedzmy, że 13 milionów rekordów obejmuje 2 lata danych, w końcu będziemy mieć więcej niż jeden rekord przez kilka dni. Te rekordy są filtrowane przy najbliższej okazji w moich rozwiązaniach DATEDIFF mojego i ypercube. Mam nadzieję, że Ypercube nie ma nic przeciwko temu.
Rozwiązania przetestowano w poniższej tabeli
z dwoma różnymi indeksami klastrowymi (w tym przypadku mydate):
Tabela została wypełniona w następujący sposób
W przypadku wielomilionowego przypadku wiersza zmieniono INSERT w taki sposób, że losowo dodawano wpisy 0-20 minut.
Wszystkie rozwiązania zostały starannie zapakowane w następujący kod
Rzeczywiste kody testowane (bez określonej kolejności):
Rozwiązanie DATEDIFF firmy Ypercube ( YPC, DATEDIFF )
Rozwiązanie DATEADD firmy Ypercube ( YPC, DATEADD )
Rozwiązanie Paula White'a ( PW )
Moje rozwiązanie DATEADD ( PN, DATEADD )
Moje rozwiązanie DATEDIFF ( PN, DATEDIFF )
Korzystam z programu SQL Server 2012, więc przepraszam Mikaela Erikssona, ale jego kod nie będzie tutaj testowany. Nadal oczekiwałbym, że jego rozwiązania z DATADIFF i DATEADD zwrócą różne wartości w niektórych zestawach danych.
Rzeczywiste wyniki to:
źródło
Ok, czy coś przeoczyłem lub dlaczego po prostu nie pominąłeś rekurencji i nie wróciłeś do siebie? Jeśli data jest kluczem podstawowym, musi być unikalna i chronologicznie, jeśli planujesz obliczać przesunięcie do następnego wiersza
Wydajność
Chyba że całkowicie przegapiłem coś ważnego ....
źródło
WHERE [TheDate] > [T1].[TheDate]
aby uwzględnić 90-dniowy próg różnicy. Ale nadal twoja produkcja nie jest pożądana.