At finde på en måde at bruge BETWEEN på med bordet, som det er, vil fungere, men vil i alle tilfælde give dårligere ydeevne:
- Det vil i bedste fald forbruge mere CPU at lave en form for beregning på rækkerne i stedet for at arbejde med dem som datoer.
- Det vil i værste fald fremtvinge en tabelscanning på hver række i tabellen, men hvis dine kolonner har indekser, så er en søgning mulig med den rigtige forespørgsel. Dette kan være en KÆMPE ydelsesforskel, fordi at tvinge begrænsningerne ind i en BETWEEN-sætning vil deaktivere brugen af indekset.
Jeg foreslår i stedet følgende, hvis du har et indeks på dine datokolonner og overhovedet bekymrer dig om ydeevne:
DECLARE
@FromDate date = '20111101',
@ToDate date = '20120201';
SELECT *
FROM dbo.YourTable T
WHERE
(
T.[Year] > Year(@FromDate)
OR (
T.[Year] = Year(@FromDate)
AND T.[Month] >= Month(@FromDate)
)
) AND (
T.[Year] < Year(@ToDate)
OR (
T.[Year] = Year(@ToDate)
AND T.[Month] <= Month(@ToDate)
)
);
Det er dog forståeligt, at du ikke ønsker at bruge sådan en konstruktion, da det er meget akavet. Så her er en kompromisforespørgsel, som i det mindste bruger numerisk beregning og vil bruge mindre CPU end beregning af dato-til-streng-konvertering (dog ikke nok mindre til at kompensere for den tvungne scanning, som er det egentlige ydeevneproblem).
SELECT *
FROM dbo.YourTable T
WHERE
T.[Year] * 100 + T.[Month] BETWEEN 201111 AND 201202;
Hvis du har et indeks på Year
, kan du få et stort løft ved at indsende forespørgslen som følger, som har mulighed for at søge:
SELECT *
FROM dbo.YourTable T
WHERE
T.[Year] * 100 + T.[Month] BETWEEN 201111 AND 201202
AND T.[Year] BETWEEN 2011 AND 2012; -- allows use of an index on [Year]
Selvom dette bryder dit krav om at bruge en enkelt BETWEEN
udtryk, er det ikke for meget mere smertefuldt og vil fungere meget godt med Year
indeks.
Du kan også ændre dit bord. Helt ærligt er det ikke godt at bruge separate tal til dine datodele i stedet for en enkelt kolonne med en datodatatype. Grunden til, at det ikke er godt, er på grund af det præcise problem, du står over for lige nu – det er meget svært at forespørge på.
I nogle data warehousing scenarier, hvor lagring af bytes betyder meget, kunne jeg forestille mig situationer, hvor du kan gemme datoen som et tal (såsom 201111
), men det anbefales ikke. Det bedste løsningen er at ændre din tabel til at bruge datoer i stedet for at opdele den numeriske værdi af måneden og året. Du skal blot gemme den første dag i måneden, og erkende, at den står i hele måneden.
Hvis det ikke er en mulighed at ændre den måde, du bruger disse kolonner på, men du stadig kan ændre din tabel, kan du tilføje en vedvarende beregnet kolonne:
ALTER Table dbo.YourTable
ADD ActualDate AS (DateAdd(year, [Year] - 1900, DateAdd(month, [Month], '18991201')))
PERSISTED;
Med dette kan du bare gøre:
SELECT *
FROM dbo.YourTable
WHERE
ActualDate BETWEEN '20111101' AND '20120201';
PERSISTED
søgeord betyder, at mens du stadig får en scanning, behøver den ikke at foretage nogen beregning på hver række, da udtrykket beregnes på hver INSERT eller UPDATE og gemmes i rækken. Men du kan få en søgning, hvis du tilføjer et indeks på denne kolonne, hvilket vil få den til at fungere meget godt (selvom alt i alt er dette stadig ikke så ideelt som at skifte til at bruge en faktisk datokolonne, fordi det vil tage mere plads og vil påvirke INSERTs og OPDATERINGER):
CREATE NONCLUSTERED INDEX IX_YourTable_ActualDate ON dbo.YourTable (ActualDate);
Resumé:Hvis du virkelig ikke kan ændre tabellen på nogen måde, så bliver du nødt til at indgå et kompromis på en eller anden måde. Det vil ikke være muligt at få den simple syntaks, du ønsker, som også vil fungere godt, når dine datoer er gemt opdelt i separate kolonner.