A query to return only 80 distinct values can finish almost instantaneously with the right index and the right physical implementation strategy. The run time of the query is determined by the number of distinct values as opposed to the size of the table.
I'll start by throwing 10 million rows into a table:
DROP TABLE IF EXISTS dbo.[Ports];
CREATE TABLE dbo.[Ports] (
PortName VARCHAR(100),
Filler VARCHAR(200)
);
INSERT INTO dbo.[Ports] WITH (TABLOCK)
SELECT TOP (10000000)
'Port ' + CAST(ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) % 80 AS VARCHAR(2))
, REPLICATE('Z', 200)
FROM master..spt_values t1
CROSS JOIN master..spt_values t2
CROSS JOIN master..spt_values t3
OPTION (MAXDOP 1);
CREATE INDEX IX_PortName ON [Ports] (PortName);
If I write the query like this:
SELECT DISTINCT (PortName)
FROM [Ports]
ORDER BY PortName ASC;
It takes 1329 ms of CPU to finish on my machine with the following plan:
That query plan isn't necessarily bad, but SQL Server is scanning all ten million rows just to return 80 distinct ones. There's a more efficient algorithm available but it takes some effort to get. It's very fast to get the minimum value from an index. It's also very fast to get the next value from an index. Both are just a handful of logical reads. Instead of reading the entire index what if we could effectively read just the distinct values and skip ahead to the next ones?
That can be done with recursive SQL. Paul White describes the approach here. Below is T-SQL written against your table:
WITH RecursiveCTE
AS
(
-- Anchor
SELECT TOP (1)
PortName
FROM dbo.[Ports] AS T
ORDER BY
T.PortName
UNION ALL
-- Recursive
SELECT R.PortName
FROM
(
-- Number the rows
SELECT
T.PortName,
rn = ROW_NUMBER() OVER (
ORDER BY T.PortName)
FROM dbo.[Ports] AS T
JOIN RecursiveCTE AS R
ON R.PortName < T.PortName
) AS R
WHERE
-- Only the row that sorts lowest
R.rn = 1
)
SELECT *
FROM RecursiveCTE
OPTION (MAXRECURSION 0);
The query takes 0 ms of CPU time on my machine. IO is very low as well:
Table 'Ports'. Scan count 81, logical reads 246, physical reads 0,
read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob
read-ahead reads 0.
If I increase the number of rows in the table to 100 million, the original query now takes 17203 ms of CPU time. The recursive query still takes 0 ms of CPU time. You can solve your query performance problem just by creating an index and writing some fancy code. In this case, there's no real need to implement caching in some other layer.
DISTINCT <column>
in a regular production query is almost always a sign that you are not normalized properly. If this is an OLAP database (i.e., not OLTP), then that can be OK, and you just need an index onPortName
.