Wszystkie istniejące (działające) odpowiedzi mają jeden z dwóch problemów:
- Zignorują indeksy w przeszukiwanej kolumnie
- Będzie (potencjalnie) wybiera dane, które nie są zamierzone, po cichu psując wyniki.
1. Ignorowane indeksy:
W większości przypadków, gdy przeszukiwana kolumna ma wywołaną funkcję (w tym niejawnie, jak dla CAST
), optymalizator musi zignorować indeksy w kolumnie i przeszukać każdy rekord. Oto krótki przykład:
Mamy do czynienia ze znacznikami czasu, a większość RDBMS przechowuje te informacje jako pewnego rodzaju rosnącą wartość, zwykle long
lub BIGINTEGER
liczba mili-/nanosekund. Aktualny czas wygląda więc/jest przechowywany w następujący sposób:
1402401635000000 -- 2014-06-10 12:00:35.000000 GMT
Nie widzisz wartości „Rok” ('2014'
) tam, prawda? W rzeczywistości jest sporo skomplikowanej matematyki do przetłumaczenia tam iz powrotem. Więc jeśli wywołasz jakąkolwiek funkcję wyodrębniania/części daty w przeszukiwanej kolumnie, serwer musi wykonać całą tę matematykę tylko po to, aby dowiedzieć się, czy możesz uwzględnić ją w wynikach. Na małych stołach nie stanowi to problemu, ale wraz ze spadkiem odsetka wybranych rzędów staje się to coraz większym drenażem. W tym przypadku robisz to po raz drugi, pytając o MONTH
... cóż, masz obraz.
2. Niezamierzone dane:
W zależności od konkretnej wersji SQL Server i typów danych kolumn, używając BETWEEN
(lub podobne włącznie górne zakresy:<=
) może spowodować wybranie niewłaściwych danych. Zasadniczo potencjalnie możesz włączyć dane z północy „następnego” dnia lub wykluczyć część rekordów „bieżącego” dnia.
Co powinnaś robić:
Potrzebujemy więc sposobu, który będzie bezpieczny dla naszych danych i będzie korzystał z indeksów (jeśli jest to wykonalne). Prawidłowy sposób ma więc postać:
WHERE date_created >= @startOfPreviousMonth AND date_created < @startOfCurrentMonth
Biorąc pod uwagę, że jest tylko jeden miesiąc, @startOfPreviousMonth
można łatwo zastąpić/wyprowadzić przez:
DATEADD(month, -1, @startOCurrentfMonth)
Jeśli chcesz uzyskać na serwerze początek bieżącego miesiąca, możesz to zrobić w następujący sposób:
DATEADD(month, DATEDIFF(month, 0, CURRENT_TIMESTAMP), 0)
Tutaj krótkie wyjaśnienie. Początkowy DATEDIFF(...)
otrzyma różnicę między początkiem bieżącej ery (0001-01-01
- AD, CE, cokolwiek), zasadniczo zwraca dużą liczbę całkowitą. Jest to liczba miesięcy do rozpoczęcia bieżącego miesiąc. Następnie dodajemy tę liczbę do początku ery, czyli na początku danego miesiąca.
Twój pełny skrypt mógłby/powinien wyglądać podobnie do następującego:
DECLARE @startOfCurrentMonth DATETIME
SET @startOfCurrentMonth = DATEADD(month, DATEDIFF(month, 0, CURRENT_TIMESTAMP), 0)
SELECT *
FROM Member
WHERE date_created >= DATEADD(month, -1, @startOfCurrentMonth) -- this was originally misspelled
AND date_created < @startOfCurrentMonth
Wszystkie operacje na datach są zatem wykonywane tylko raz, na jednej wartości; optymalizator może swobodnie używać indeksów i żadne nieprawidłowe dane nie zostaną uwzględnione.