Tables
- TEST_RUNS (ID int, Date_Time datetime, Row1 int, Row2 int, Row3 int, Row4 int)
- ADMIN_TIME_FILTER (ID int, Name varchar(20), Start_Date datetime, End_Date datetime)
ADMIN_TIME_FILTER holds a small list of shifting date ranges that get updated frequently via automation. We have common date ranges (ThisYear, ThisDay, etc) and some more esoteric ranges. This table exists strictly as a way to filter data out of TEST_RUNS in a join.
TEST_RUNS holds actual data we care about, and has millions of rows. We have a bunch of defined views joining these two tables together, so we can just update the ADMIN_TIME_FILTER table and have the changes propagate to the views. Aside from date, there are no rows that link these two tables together.
Queries
The problem is that the estimated row count is widly off, resulting in poor performing queries. Here’s a simple query the exhibits the bad estimate:
with test as (
SELECT a.* FROM TEST_RUNS a
INNER JOIN ADMIN_TIME_FILTER b ON b.ID = 5 -- ID for ThisYear range
AND a.date_time BETWEEN b.start_date AND b.end_date
)
select count(*) from test
Here’s the query plan:

Statistics aren’t the problem. I can run the following query and get an accurate row estimate
with test as (
select tr.* from test_runs tr
where tr.date_time between '2012-01-01 00:00:00' and '2012-12-31 00:00:00'
)
select count(*) from test
Help
Switching away from our join methodology isn’t a simple task, so I’m looking for an alternative to improve the row estimate.
Indexed views don’t appear to be a viable option due to the performance implications.
The problem isn’t the row count. Your query is set up as a non-equijoin. There is really no other way for SQL Server to handle the join other than by using nested loops.
If you have an id in the “a” table, the following might help:
By matching on the “id” between the tables, SQL Server can consider other join algorithms, such as merge joins or hash joins.
I suspect that there might be another way to fix this. What is the index on B using? I would suggest (id, start_date, end_date). The engine might be deciding to using an index on the dates to satisfy the query.