Všechny existující (pracovní) odpovědi mají jeden ze dvou problémů:
- Budou ignorovat indexy v prohledávaném sloupci
- Ten (potenciálně) vybere data, která nejsou zamýšlena, a tiše poškodí vaše výsledky.
1. Ignorované indexy:
Z velké části platí, že když má prohledávaný sloupec na sobě zavolanou funkci (včetně implicitně, jako pro CAST
), musí optimalizátor ignorovat indexy ve sloupci a prohledávat každý záznam. Zde je rychlý příklad:
Máme co do činění s časovými razítky a většina RDBMS má tendenci ukládat tyto informace jako rostoucí hodnotu nějakého druhu, obvykle long
nebo BIGINTEGER
počet mili-/nanosekund. Aktuální čas tedy vypadá/je uložen takto:
1402401635000000 -- 2014-06-10 12:00:35.000000 GMT
Nevidíte hodnotu 'Rok' ('2014'
) tam, ano? Ve skutečnosti je tu poměrně složitá matematika k překládání tam a zpět. Pokud tedy zavoláte některou z funkcí extrakce/datové části na hledaný sloupec, server musí provést veškerou matematiku, aby zjistil, zda ji můžete zahrnout do výsledků. Na malých stolech to není problém, ale jak se procento vybraných řádků snižuje, dochází k většímu a většímu odlivu. Pak v tomto případě to děláte podruhé, protože se ptáte na MONTH
... no, chápete.
2. Nezamýšlená data:
V závislosti na konkrétní verzi SQL Serveru a datových typech sloupců pomocí BETWEEN
(nebo podobné rozsahy včetně horních hranic:<=
) může mít za následek výběr nesprávných dat. V podstatě můžete skončit zahrnutím dat z půlnoci „následujícího“ dne nebo vyloučením určité části záznamů „aktuálního“ dne.
Co byste měli dělat:
Potřebujeme tedy způsob, který je bezpečný pro naše data a bude používat indexy (pokud je to možné). Správný způsob je pak ve tvaru:
WHERE date_created >= @startOfPreviousMonth AND date_created < @startOfCurrentMonth
Vzhledem k tomu, že existuje pouze jeden měsíc, @startOfPreviousMonth
lze snadno nahradit/odvodit:
DATEADD(month, -1, @startOCurrentfMonth)
Pokud potřebujete odvodit začátek aktuálního měsíce na serveru, můžete to udělat takto:
DATEADD(month, DATEDIFF(month, 0, CURRENT_TIMESTAMP), 0)
Zde je rychlé vysvětlení. Počáteční DATEDIFF(...)
získá rozdíl mezi začátkem aktuální éry (0001-01-01
- AD, CE, cokoliv), v podstatě vrací velké celé číslo. Toto je počet měsíců do začátku aktuálního Měsíc. Toto číslo pak přičteme k začátku éry, který je na začátku daného měsíce.
Váš úplný skript by tedy mohl/měl by vypadat podobně jako následující:
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
Všechny operace s datem jsou tak provedeny pouze jednou, na jedné hodnotě; optimalizátor může volně používat indexy a nebudou zahrnuta žádná nesprávná data.