Is heap a table without indexes at all or without a clustered index?

You need to be very careful what you read on the interwebs ;-) (of course, that also goes for this answer or pretty much anything anywhere, but still). Just as there is a lot of good information out there, there is also a lot of misinformation (and sadly, this is not confined to technical info). And people copy and paste / repost / share both. So, it is good to ask the question :-).

Eventhough BOL does have some mistakes, in this case (and most cases) it is correct: a Heap is specifically a table without a Clustered index. It has nothing to do with non-Clustered indexes. Of course, this could be a simple case of misunderstanding what is being said in these "online posts" since "a table without any kind of indexes" is a Heap, but only because having no indexes means that there is no Clustered index. However, if these posts claim that a table with only non-Clustered indexes is not a Heap, then they would definitely be incorrect.

If you look at sys.indexes you will see that index_id of 1 is a Clustered Index and 0 is a Heap. A Table will have one or the other. It is not possible to have both. Non-clustered indexes start at index_id of 2 and go up from there. All tables will have an index_id of either 0 or 1 and optionally one or more of index_id >= 2.

You can even test this by using the following query:

SELECT COUNT(*)
FROM   sys.indexes si
WHERE  si.index_id IN (0, 1)
GROUP BY si.[object_id]
HAVING   COUNT(*) > 1;

It should never return a row.

Here is a second test which is even more obvious and does not allow for someone to speculate that the condition can exist but just doesn't, which I suppose is possible with the test above:

-- DROP TABLE #tmp;
CREATE TABLE #tmp (Col1 INT, Col2 INT);
SELECT * FROM tempdb.sys.indexes si WHERE si.[object_id] = OBJECT_ID(N'tempdb.dbo.#tmp')
-- 1 row; index_id = 0 and type_desc = HEAP

CREATE NONCLUSTERED INDEX [IX_#tmp] ON #tmp (Col2 ASC);
SELECT * FROM tempdb.sys.indexes si WHERE si.[object_id] = OBJECT_ID(N'tempdb.dbo.#tmp')
-- 2 rows; index_id = 0 / HEAP and index_id = 2 / NONCLUSTERED

CREATE CLUSTERED INDEX [CIX_#tmp] ON #tmp (Col1 ASC);
SELECT * FROM tempdb.sys.indexes si WHERE si.[object_id] = OBJECT_ID(N'tempdb.dbo.#tmp')
-- still 2 rows (not 3!!); index_id = 1 / CLUSTERED and index_id = 2 / NONCLUSTERED

The point of this second test is that the authority on what is and is not true about SQL Server is always going to be SQL Server itself. So it is important to test to essentially ask SQL Server the question, and not a human. Even the experts are sometimes wrong, but SQL Server is always correct (with regards to questions about SQL Server, of course).


Also, while this is not "proof" in any definitive way, the title of the following MSDN page is rather telling:

Heaps (Tables without Clustered Indexes)