当日期分别存储为年,月和日时,查找范围内的日期

时间:2018-01-31 17:53:45

标签: sql sql-server date tsql datetime

我有一个表,其中包含列Year,Month,Day,它们是INT,还有其他一些列。桌子的大小非常大。

现在,我想检索日期范围内的数据,比如在@StartDate和@EndDate之间。

我使用的查询是:

SELECT * FROM DataTable
WHERE 10000*M.[YEAR]+100*M.[Month]+M.[Day] >= 10000*YEAR(@StartDate)+100*MONTH(@StartDate)+DAY(@StartDate) 
    AND 10000*M.[YEAR]+100*M.[Month]+M.[Day] < 10000*YEAR(@EndDate)+100*MONTH(@EndDate)+DAY(@EndDate)

这使得SQL Server扫描索引,而不是搜索索引。

有没有办法让它成为Index Seek?我的意思是如何在这里改变逻辑?

我使用的是SQL Server,但我想它也适用于其他数据库。

7 个答案:

答案 0 :(得分:2)

您可能对使用DATEFROMPARTS ( year, month, day )

感兴趣
SELECT * FROM DataTable M
WHERE DATEFROMPARTS(M.[Year], M.[Month], M.[Day]) between @StartDate and @EndDate

修改 根据评论,我建议创建一个日期持久计算字段,该字段将结合年,月和日

希望这会对你有所帮助

答案 1 :(得分:0)

也许这样的事情会这样做:

DECLARE @Start date = '2016-01-23'
DECLARE @End date = '2017-04-22';



SELECT * FROM DataTable DT
JOIN (
    SELECT YEAR(@Start) SY
        , MONTH(@Start) SM
        , DAY(@Start) SD
        , YEAR(@End) EY
        , MONTH(@End) EM
        , DAY(@End) ED
        ) MMM
    ON ( 
        MMM.SY < DT.YEAR
        OR ( MMM.SY = DT.YEAR AND 
             ( MMM.SM < DT.MONTH OR
                ( MMM.SM = DT.MONTH AND MMM.SD <= DT.DAY )
             )
            )
        )
        AND 
        ( 
        MMM.EY > DT.YEAR
        OR ( MMM.EY = DT.YEAR AND 
             ( MMM.EM > DT.MONTH OR
                ( MMM.EM = DT.MONTH AND MMM.ED >= DT.DAY )
             )
            )
        )

    ;

答案 2 :(得分:0)

你能告诉我如何使用虚拟数据填充表格吗?

不要使用*,而是提及所有需要列及其数据类型 所以它给出了创造索引的合理想法

如果你回答我的上述问题,那么我会相应地更新我的答案。

试试这个,

    DECLARE @Start date = '2000-01-23'
    DECLARE @End date = '2017-04-22';

   CREATE table MainTable(yr int not null,mnth int not null,d int not null)
create nonclustered index #MainTable_ymd on MainTable(yr ,mnth ,d )
----create nonclustered index #MainTable_y on #MainTable(yr)include(mnth ,d ) 
----here include(other column) so that it is covering index
----truncate table MainTable

insert into MainTable WITH(tablock)
select year(dt),month(dt),day(dt)
from
(
select top (10000000) dateadd(day,ROW_NUMBER()over(order by a.number)/5,'1900-01-01')dt from master..spt_values a 
,master..spt_values  b
)t4

I populate maintable with dummy data with 63,25,225 rows

   --Solution 1

SELECT *
FROM (
    SELECT yr
        ,mnth
        ,d
        ,cast(cast(yr AS VARCHAR(4)) + right('00' + cast(mnth AS VARCHAR), 2) + right('00' + cast(d AS VARCHAR), 2) AS DATE) dt
    FROM MainTable
    WHERE yr >= year(@Start)
        AND yr <= year(@End)
    ) t4
WHERE dt >= @Start
    AND dt <= @End


--RETURN
-- Solution 2
-- here put the result in temp table

CREATE table #tmp1(yr int,mnth int,d int,dt date not null)

INSERT INTO #tmp1
SELECT yr
    ,mnth
    ,d
    ,cast(cast(yr AS VARCHAR(4)) + right('00' + cast(mnth AS VARCHAR), 2) + right('00' + cast(d AS VARCHAR), 2) AS DATE) dt
FROM MainTable
WHERE yr >= year(@Start)
    AND yr <= year(@End)


create clustered index #tmp1dt on #tmp1(dt)

select * from #tmp1
where dt>=@Start and dt<=@End

--drop table MainTable

drop table #tmp1

我在两个解决方案中都获得了索引。

使用JOhnRC查询我获得表扫描和并行性,使用urlreader我也可以使用相同的样本数据进行表扫描。

@Monah query-- TableScan

答案 3 :(得分:0)

可以我们以这种方式尝试:

select *
from dateCompare
where concat(Eyear, Emonth, Eday) between replace(@startDate, '-', '') 
                                      and replace(@endDate, '-', '')

答案 4 :(得分:0)

你可以试试这个。

select * from DataTable M
where 
(   M.[YEAR] > YEAR(@StartDate)
    OR (M.[YEAR] = YEAR(@StartDate)
        AND M.[Month] > Month(@StartDate) )
    OR (M.[YEAR] = YEAR(@StartDate)
        AND M.[Month] = Month(@StartDate)
        AND M.[Day]  >= Day(@StartDate) )
)
AND 
(   M.[YEAR] < YEAR(@EndDate)
    OR (M.[YEAR] = YEAR(@EndDate)
        AND M.[Month] < Month(@EndDate) )
    OR (M.[YEAR] = YEAR(@StartDate)
        AND M.[Month] = Month(@EndDate)
        AND M.[Day]  < Day(@EndDate)    )
)

答案 5 :(得分:0)

这是另一个肯定在DataTable上使用索引搜索的答案(假设在使用SQL SERVER 2014时索引是(YEAR,MONTH,DAY)。但是,我怀疑这可能比简单的方法花费更长的时间,除非选择的日期范围是非常狭窄。我很想知道它对真实数据的影响。

方法是创建一个包含开始日期和结束日期之间所有可能日期的表格,然后将此表格与YEAR,MONTH和DAY完全匹配的DataTable连接。

-- Paramters setting date range
DECLARE @Start date = '2016-01-23'
DECLARE @End date = '2017-04-22';

-- Create a table of all dates between start and end dates
CREATE TABLE #DATES 
(
    YY int
    , MM int
    , DD int
);

-- There are better ways to do this but for the sake of simplicity...
WHILE @Start <= @End
begin
 INSERT INTO #DATES VALUES ( YEAR(@Start), MONTH(@Start), DAY(@Start) );
 SET @Start = Dateadd(Day,1, @Start);
end ;

-- Finally, join the DataTable to the created dates with exact match
SELECT DT.* 
    FROM DataTable DT
        JOIN #DATES DD
        ON DD.YY = DT.YEAR
        AND DD.MM = DT.MONTH
        AND DD.DD = DT.DAY;

-- Finished with the dates table
DROP TABLE #DATES;

答案 6 :(得分:0)

可以创建一个持久计算列:

CREATE TABLE DataTable(
    year int NOT NULL,
    month int NOT NULL,
    day int NOT NULL,
    date AS (DATEFROMPARTS(year, month, day)) PERSISTED NOT NULL
)

...并将其编入索引:

CREATE NONCLUSTERED INDEX IX_CVR_ALL ON DataTable
(date) INCLUDE (...)

替代解决方案:

我创建了一个“日期表”,并填写了1990年到2050年之间的所有日期:

CREATE TABLE T_Temp_DateList(
    date date NOT NULL PRIMARY KEY,
    year int NOT NULL,
    month int NOT NULL,
    day int NOT NULL,
)
CREATE NONCLUSTERED INDEX KIX_SMALL
ON T_Temp_DateList(date)
INCLUDE (year, month, day)

“数据表”的索引如下:

CREATE NONCLUSTERED INDEX KIX_YMD
ON T_Temp_DataTable(year, month, day)

并使用了这个查询:

DECLARE @StartDate AS DATE = '2013-12-01'
DECLARE @EndDate   AS DATE = '2017-01-31'

SELECT dt.*
FROM T_Temp_DateList dl
INNER JOIN T_Temp_DataTable dt ON dl.year = dt.year AND dl.month = dt.month AND dl.day = dt.day
WHERE dl.date BETWEEN @StartDate AND @EndDate

结果如下,请注意两个索引都是搜索而不是扫描:

execution plan index usage