Simulate real data. Create a large table (the size of the final table should be the same as in real life) with the distribution of products and dates, as you expected in real life. Start by adding three separate independent indexes for the products, start date, end date. Try running a query. Review the implementation plan. Try other index combinations. Compare plans and performance. If nothing gives acceptable performance, come back here with a script that generates sample data and your request.
In my test, the optimizer was the inner join of the results of three independent index queries.
Create table
plus three independent indexes for each column:
CREATE TABLE [dbo].[Test]( [ID] [int] IDENTITY(1,1) NOT NULL, [ProductID] [int] NOT NULL, [StartDate] [date] NOT NULL, [EndDate] [date] NOT NULL, CONSTRAINT [PK_Test] PRIMARY KEY CLUSTERED ( [ID] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] CREATE NONCLUSTERED INDEX [IX_EndDate] ON [dbo].[Test] ( [EndDate] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] CREATE NONCLUSTERED INDEX [IX_ProductID] ON [dbo].[Test] ( [ProductID] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] CREATE NONCLUSTERED INDEX [IX_StartDate] ON [dbo].[Test] ( [StartDate] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
Generate test data
- Only 1M lines.
- Up to 100 different product identifiers with uniform distribution.
- Start dates within 10,000 days from 2000-01-01 (~ 27 years)
- End dates are within 1000 days from the start date (duration up to ~ 3 years).
inquiry:
INSERT INTO Test(ProductID, StartDate, EndDate) SELECT TOP(1000000) CA.ProductID ,DATEADD(day, StartOffset, '2000-01-01') AS StartDate ,DATEADD(day, StartOffset+DurationDays, '2000-01-01') AS EndDate FROM sys.all_objects AS o1 cross join sys.all_objects AS o2 cross apply ( SELECT cast((cast(CRYPT_GEN_RANDOM(4) as int) / 4294967295.0 + 0.5) * 100 + 1 as int) AS ProductID ,cast((cast(CRYPT_GEN_RANDOM(4) as int) / 4294967295.0 + 0.5) * 10000 as int) AS StartOffset ,cast((cast(CRYPT_GEN_RANDOM(4) as int) / 4294967295.0 + 0.5) * 1000 as int) AS DurationDays ) AS CA
Request for optimization:
DECLARE @VarDate date = '2004-01-01'; SELECT * FROM Test WHERE ProductID = 1 AND @VarDate >= StartDate AND @VarDate <= EndDate ;
It returns ~ 500 rows.
Execution plan

The server suggested the following index:
CREATE NONCLUSTERED INDEX [<Name of Missing Index, sysname,>] ON [dbo].[Test] ([ProductID],[StartDate],[EndDate]) INCLUDE ([ID])
but having such an indicator is stupid, IMHO.
If you have only 1M lines and 100K different product identifiers, not 100; in other words, if a search by a specific product identifier excludes the vast majority of rows, then the best option probably has one index for ProductID and includes other columns:
CREATE NONCLUSTERED INDEX IX_Product ON [dbo].[Test] ([ProductID]) INCLUDE ([StartDate],[EndDate])
OR
CREATE NONCLUSTERED INDEX IX_Product ON [dbo].[Test] ([ProductID], [StartDate]) INCLUDE ([EndDate])
OR
CREATE NONCLUSTERED INDEX IX_Product ON [dbo].[Test] ([ProductID],[EndDate]) INCLUDE ([StartDate])
If one of the dates gives good selectivity, then instead of it comes an index, not ProductID.
If none of the columns has good selectivity, then this is difficult.
Edit
It is foolish to blindly make an index, as suggested by the optimizer, because you know that you will search for a specific ProductID, but then for the StartDates row and then the EndDates range. So, the third column of EndDate will never be used for the search itself. In this case, it is better to INCLUDE this column in the index, rather than make it part of the index, as I showed above.
If the query was for a specific ProductID and for a specific StartDate (and not a range), and then for a range of EndDate (or a specific EndDate), then using EndDate as part of the index would help.