I am working with SQL Server 2019 and have a TVF that retrieves products based on a list of categories. However, I noticed that when I use SELECT *
, the query results in a table scan, whereas selecting specific indexed columns results in an index seek, leading to better performance.
I need to keep using SELECT *
because this function is used in multiple areas where different columns are needed. Additionally, forcing an index is not an option because the results might be sorted later outside the TVF, and forcing an index could degrade performance in such cases.
Reproducible example
I have a Products
table with an index on CategoryId
:
CREATE TABLE Products ( ProductId INT PRIMARY KEY, Name NVARCHAR(100), CategoryId CHAR(1), Price DECIMAL(10,2), CreatedAt DATETIME ); CREATE INDEX IX_Products_CategoryId ON Products (CategoryId);
I also have a table type to pass category filters:
CREATE TYPE Categories AS TABLE ( CategoryId CHAR(1) PRIMARY KEY );
And my TVF query is:
DECLARE @Categories Categories; INSERT INTO @Categories (CategoryId) VALUES ('B'), ('C'); SELECT TOP 10 * FROM Products WHERE CategoryId IN (SELECT CategoryId FROM @Categories);
Observations
- If I select specific columns covered by the index, I get an Index Seek (good performance):
SELECT TOP 10 ProductId, CategoryId FROM Products WHERE CategoryId IN (SELECT CategoryId FROM @Categories);
- If I do
SELECT *
(all columns), I get a table scan (bad performance):
SELECT TOP 10 * FROM Products WHERE CategoryId IN (SELECT CategoryId FROM @Categories);
Constraints & requirements
- I cannot avoid
SELECT *
because different usages of this TVF require different columns - I cannot force an index because it may negatively impact performance when sorting is applied outside the TVF.
- Sorting the data inside the TVF is not an option since sorting is handled by the caller
- The function must remain a TVF as it is used in multiple queries with additional filters applied
Questions
How can I modify the query or structure to ensure index seek behavior while keeping
SELECT *
?Are there any best practices for handling this inside a TVF without forcing an index?
Execution Plan
Attaching execution plan (generated with small amount of data):
SELECT *
:
SELECT CategoryId
:
Any insights or alternative approaches would be greatly appreciated!
TOP 10
without anyORDER BY
to define "what 10" is extremely questionablehow can i make my queries always take the best choices