You could also write the query as
SELECT c_code CourseCode
FROM courselist
WHERE c_desc < 'BASIC EDUCATION'
UNION ALL
SELECT c_code CourseCode
FROM courselist
WHERE c_desc > 'BASIC EDUCATION' AND c_desc < 'PRE SCHOOL'
UNION ALL
SELECT c_code CourseCode
FROM courselist
WHERE c_desc > 'PRE SCHOOL' AND c_desc < 'SCIENCE HIGH SCHOOL'
UNION ALL
SELECT c_code CourseCode
FROM courselist
WHERE c_desc > 'SCIENCE HIGH SCHOOL'
To get four range seeks.
Unless any of the NOT IN
values are extremely common I would expect this to be worse rather than better however.
If the values are common then last time I looked at this it seemed the best SQL Server would do is skip the first indexed value and scan the rest.
A completely rigged example looking at the case where this might be better...
CREATE TABLE [dbo].[courselist](
[c_desc] VARCHAR(20),
[c_code] CHAR(880))
CREATE NONCLUSTERED INDEX [idx_dmcasarms_CourseList_cDesc]
ON [dbo].[courselist]([c_desc] ASC,[c_code] ASC);
WITH T AS (SELECT TOP (10000) v1.number FROM master..spt_values v1, master..spt_values v2)
INSERT INTO [dbo].[courselist]
SELECT X, number
FROM T
CROSS JOIN (VALUES ('PRE SCHOOL'),
('SCIENCE HIGH SCHOOL')) V(X)
UNION ALL
SELECT REPLICATE('A',20), 1
On my machine with a warm cache the NOT IN
has a full scan and 3,132 logical reads with an elapsed time of 20ms. Whereas the multiple seeks shows 28 reads and an elapsed time of 0ms.