Can you create an index on a table variable in SQL Server 2000?
i.e.
DECLARE #TEMPTABLE TABLE (
[ID] [int] NOT NULL PRIMARY KEY
,[Name] [nvarchar] (255) COLLATE DATABASE_DEFAULT NULL
)
Can I create an index on Name?
The question is tagged SQL Server 2000 but for the benefit of people developing on the latest version I'll address that first.
SQL Server 2014
In addition to the methods of adding constraint based indexes discussed below SQL Server 2014 also allows non unique indexes to be specified directly with inline syntax on table variable declarations.
Example syntax for that is below.
/*SQL Server 2014+ compatible inline index syntax*/
DECLARE #T TABLE (
C1 INT INDEX IX1 CLUSTERED, /*Single column indexes can be declared next to the column*/
C2 INT INDEX IX2 NONCLUSTERED,
INDEX IX3 NONCLUSTERED(C1,C2) /*Example composite index*/
);
Filtered indexes and indexes with included columns can not currently be declared with this syntax however SQL Server 2016 relaxes this a bit further. From CTP 3.1 it is now possible to declare filtered indexes for table variables. By RTM it may be the case that included columns are also allowed but the current position is that they "will likely not make it into SQL16 due to resource constraints"
/*SQL Server 2016 allows filtered indexes*/
DECLARE #T TABLE
(
c1 INT NULL INDEX ix UNIQUE WHERE c1 IS NOT NULL /*Unique ignoring nulls*/
)
SQL Server 2000 - 2012
Can I create a index on Name?
Short answer: Yes.
DECLARE #TEMPTABLE TABLE (
[ID] [INT] NOT NULL PRIMARY KEY,
[Name] [NVARCHAR] (255) COLLATE DATABASE_DEFAULT NULL,
UNIQUE NONCLUSTERED ([Name], [ID])
)
A more detailed answer is below.
Traditional tables in SQL Server can either have a clustered index or are structured as heaps.
Clustered indexes can either be declared as unique to disallow duplicate key values or default to non unique. If not unique then SQL Server silently adds a uniqueifier to any duplicate keys to make them unique.
Non clustered indexes can also be explicitly declared as unique. Otherwise for the non unique case SQL Server adds the row locator (clustered index key or RID for a heap) to all index keys (not just duplicates) this again ensures they are unique.
In SQL Server 2000 - 2012 indexes on table variables can only be created implicitly by creating a UNIQUE or PRIMARY KEY constraint. The difference between these constraint types are that the primary key must be on non nullable column(s). The columns participating in a unique constraint may be nullable. (though SQL Server's implementation of unique constraints in the presence of NULLs is not per that specified in the SQL Standard). Also a table can only have one primary key but multiple unique constraints.
Both of these logical constraints are physically implemented with a unique index. If not explicitly specified otherwise the PRIMARY KEY will become the clustered index and unique constraints non clustered but this behavior can be overridden by specifying CLUSTERED or NONCLUSTERED explicitly with the constraint declaration (Example syntax)
DECLARE #T TABLE
(
A INT NULL UNIQUE CLUSTERED,
B INT NOT NULL PRIMARY KEY NONCLUSTERED
)
As a result of the above the following indexes can be implicitly created on table variables in SQL Server 2000 - 2012.
+-------------------------------------+-------------------------------------+
| Index Type | Can be created on a table variable? |
+-------------------------------------+-------------------------------------+
| Unique Clustered Index | Yes |
| Nonunique Clustered Index | |
| Unique NCI on a heap | Yes |
| Non Unique NCI on a heap | |
| Unique NCI on a clustered index | Yes |
| Non Unique NCI on a clustered index | Yes |
+-------------------------------------+-------------------------------------+
The last one requires a bit of explanation. In the table variable definition at the beginning of this answer the non unique non clustered index on Name is simulated by a unique index on Name,Id (recall that SQL Server would silently add the clustered index key to the non unique NCI key anyway).
A non unique clustered index can also be achieved by manually adding an IDENTITY column to act as a uniqueifier.
DECLARE #T TABLE
(
A INT NULL,
B INT NULL,
C INT NULL,
Uniqueifier INT NOT NULL IDENTITY(1,1),
UNIQUE CLUSTERED (A,Uniqueifier)
)
But this is not an accurate simulation of how a non unique clustered index would normally actually be implemented in SQL Server as this adds the "Uniqueifier" to all rows. Not just those that require it.
It should be understood that from a performance standpoint there are no differences between #temp tables and #temp tables that favor variables. They reside in the same place (tempdb) and are implemented the same way. All the differences appear in additional features. See this amazingly complete writeup: https://dba.stackexchange.com/questions/16385/whats-the-difference-between-a-temp-table-and-table-variable-in-sql-server/16386#16386
Although there are cases where a temp table can't be used such as in table or scalar functions, for most other cases prior to v2016 (where even filtered indexes can be added to a table variable) you can simply use a #temp table.
The drawback to using named indexes (or constraints) in tempdb is that the names can then clash. Not just theoretically with other procedures but often quite easily with other instances of the procedure itself which would try to put the same index on its copy of the #temp table.
To avoid name clashes, something like this usually works:
declare #cmd varchar(500)='CREATE NONCLUSTERED INDEX [ix_temp'+cast(newid() as varchar(40))+'] ON #temp (NonUniqueIndexNeeded);';
exec (#cmd);
This insures the name is always unique even between simultaneous executions of the same procedure.
If Table variable has large data, then instead of table variable(#table) create temp table (#table).table variable doesn't allow to create index after insert.
CREATE TABLE #Table(C1 int,
C2 NVarchar(100) , C3 varchar(100)
UNIQUE CLUSTERED (c1)
);
Create table with unique clustered index
Insert data into Temp "#Table" table
Create non clustered indexes.
CREATE NONCLUSTERED INDEX IX1 ON #Table (C2,C3);
Related
I need to create a table with a nonclustered primary key (to set foreign keys on other tables to it) and a clustered index to store the data in the intended order.
However, the resulting stored data is sorted in the primary key's order as opposed to the index's.
Is there a way to prevent this from occurring? Here is an example (SQL Server 14.0 RTM):
create table dbo.a (
x nvarchar(50) not null
,y nvarchar(100) not null
,index ix_a clustered (y)
,constraint pk_a primary key nonclustered (x)
)
insert dbo.a
values
('d','p')
,('c','q');
select * from dbo.a
the result should be sorted with p first, then q. Howerver, q is in the first row and p is in the second row.
In a similar case, this approach worked when the primary key was in 2 columns as opposed to only 1 column.
You are confused. This query:
select *
from dbo.a
Does not tell you anything about the "ordering" of a table. A SQL table with no ORDER BY returns rows in an indeterminate order. I also freely admit that with a handful of rows in the table, this would be highly correlated with the actual ordering of the data, but I strongly discourage you from thinking along those lines.
If you want to know the actual ordering, you need to peak at the data pages. Or you can perhaps use an execution plan to see if an index is being used instead of a sort.
I think that what you are seeing is that SQL Server is choosing to return rows from the query using the primary key index. With two rows in the table, the actual execution plan doesn't really matter.
Can you create an index on a table variable in SQL Server 2000?
i.e.
DECLARE #TEMPTABLE TABLE (
[ID] [int] NOT NULL PRIMARY KEY
,[Name] [nvarchar] (255) COLLATE DATABASE_DEFAULT NULL
)
Can I create an index on Name?
The question is tagged SQL Server 2000 but for the benefit of people developing on the latest version I'll address that first.
SQL Server 2014
In addition to the methods of adding constraint based indexes discussed below SQL Server 2014 also allows non unique indexes to be specified directly with inline syntax on table variable declarations.
Example syntax for that is below.
/*SQL Server 2014+ compatible inline index syntax*/
DECLARE #T TABLE (
C1 INT INDEX IX1 CLUSTERED, /*Single column indexes can be declared next to the column*/
C2 INT INDEX IX2 NONCLUSTERED,
INDEX IX3 NONCLUSTERED(C1,C2) /*Example composite index*/
);
Filtered indexes and indexes with included columns can not currently be declared with this syntax however SQL Server 2016 relaxes this a bit further. From CTP 3.1 it is now possible to declare filtered indexes for table variables. By RTM it may be the case that included columns are also allowed but the current position is that they "will likely not make it into SQL16 due to resource constraints"
/*SQL Server 2016 allows filtered indexes*/
DECLARE #T TABLE
(
c1 INT NULL INDEX ix UNIQUE WHERE c1 IS NOT NULL /*Unique ignoring nulls*/
)
SQL Server 2000 - 2012
Can I create a index on Name?
Short answer: Yes.
DECLARE #TEMPTABLE TABLE (
[ID] [INT] NOT NULL PRIMARY KEY,
[Name] [NVARCHAR] (255) COLLATE DATABASE_DEFAULT NULL,
UNIQUE NONCLUSTERED ([Name], [ID])
)
A more detailed answer is below.
Traditional tables in SQL Server can either have a clustered index or are structured as heaps.
Clustered indexes can either be declared as unique to disallow duplicate key values or default to non unique. If not unique then SQL Server silently adds a uniqueifier to any duplicate keys to make them unique.
Non clustered indexes can also be explicitly declared as unique. Otherwise for the non unique case SQL Server adds the row locator (clustered index key or RID for a heap) to all index keys (not just duplicates) this again ensures they are unique.
In SQL Server 2000 - 2012 indexes on table variables can only be created implicitly by creating a UNIQUE or PRIMARY KEY constraint. The difference between these constraint types are that the primary key must be on non nullable column(s). The columns participating in a unique constraint may be nullable. (though SQL Server's implementation of unique constraints in the presence of NULLs is not per that specified in the SQL Standard). Also a table can only have one primary key but multiple unique constraints.
Both of these logical constraints are physically implemented with a unique index. If not explicitly specified otherwise the PRIMARY KEY will become the clustered index and unique constraints non clustered but this behavior can be overridden by specifying CLUSTERED or NONCLUSTERED explicitly with the constraint declaration (Example syntax)
DECLARE #T TABLE
(
A INT NULL UNIQUE CLUSTERED,
B INT NOT NULL PRIMARY KEY NONCLUSTERED
)
As a result of the above the following indexes can be implicitly created on table variables in SQL Server 2000 - 2012.
+-------------------------------------+-------------------------------------+
| Index Type | Can be created on a table variable? |
+-------------------------------------+-------------------------------------+
| Unique Clustered Index | Yes |
| Nonunique Clustered Index | |
| Unique NCI on a heap | Yes |
| Non Unique NCI on a heap | |
| Unique NCI on a clustered index | Yes |
| Non Unique NCI on a clustered index | Yes |
+-------------------------------------+-------------------------------------+
The last one requires a bit of explanation. In the table variable definition at the beginning of this answer the non unique non clustered index on Name is simulated by a unique index on Name,Id (recall that SQL Server would silently add the clustered index key to the non unique NCI key anyway).
A non unique clustered index can also be achieved by manually adding an IDENTITY column to act as a uniqueifier.
DECLARE #T TABLE
(
A INT NULL,
B INT NULL,
C INT NULL,
Uniqueifier INT NOT NULL IDENTITY(1,1),
UNIQUE CLUSTERED (A,Uniqueifier)
)
But this is not an accurate simulation of how a non unique clustered index would normally actually be implemented in SQL Server as this adds the "Uniqueifier" to all rows. Not just those that require it.
It should be understood that from a performance standpoint there are no differences between #temp tables and #temp tables that favor variables. They reside in the same place (tempdb) and are implemented the same way. All the differences appear in additional features. See this amazingly complete writeup: https://dba.stackexchange.com/questions/16385/whats-the-difference-between-a-temp-table-and-table-variable-in-sql-server/16386#16386
Although there are cases where a temp table can't be used such as in table or scalar functions, for most other cases prior to v2016 (where even filtered indexes can be added to a table variable) you can simply use a #temp table.
The drawback to using named indexes (or constraints) in tempdb is that the names can then clash. Not just theoretically with other procedures but often quite easily with other instances of the procedure itself which would try to put the same index on its copy of the #temp table.
To avoid name clashes, something like this usually works:
declare #cmd varchar(500)='CREATE NONCLUSTERED INDEX [ix_temp'+cast(newid() as varchar(40))+'] ON #temp (NonUniqueIndexNeeded);';
exec (#cmd);
This insures the name is always unique even between simultaneous executions of the same procedure.
If Table variable has large data, then instead of table variable(#table) create temp table (#table).table variable doesn't allow to create index after insert.
CREATE TABLE #Table(C1 int,
C2 NVarchar(100) , C3 varchar(100)
UNIQUE CLUSTERED (c1)
);
Create table with unique clustered index
Insert data into Temp "#Table" table
Create non clustered indexes.
CREATE NONCLUSTERED INDEX IX1 ON #Table (C2,C3);
Why would SQL Server 2005 find it more efficient to perform a table scan instead of using the available clustered index on the primary key (and only the primary key)?
DISCLAIMER:
There is also a non-clustered, non-unique index on the primary key with no included columns. This is baffling to me and we've had a good office chuckle already. If this index ends up being the problem, then we know who to shoot. Unfortunately, it's a production site and I can't just rip it out but will make plans to do so if necessary.
Maybe the problem is not the mentally deficient contrary index, however...
According to FogLight PASS the following statement has been causing a scan on a table with ~10 million rows about 600 times an hour when we delete a row by the primary key:
DELETE FROM SomeBigTable WHERE ID = #ID
The table DDL:
CREATE TABLE [SomeBigTable]
(
[ID] [int] NOT NULL,
[FullTextIndexTime] [timestamp] NOT NULL,
[FullTextSearchText] [varchar] (max) NOT NULL,
CONSTRAINT [PK_ID] PRIMARY KEY CLUSTERED
(
[ID] ASC
)
) -- ...
ON PRIMARY
The clustered index constraint in detail:
ADD CONSTRAINT [PK_ID] PRIMARY KEY CLUSTERED
(
[ID] ASC
) WITH PAD_INDEX = OFF
,STATISTICS_NORECOMPUTE = OFF
,SORT_IN_TEMPDB = OFF
,IGNORE_DUP_KEY = OFF
,ONLINE = OFF
,ALLOW_ROW_LOCKS = ON
,ALLOW_PAGE_LOCKS = ON
,FILLFACTOR = 75
ON PRIMARY
The non-unique, non-clustered index on the same table:
CREATE NONCLUSTERED INDEX [IX_SomeBigTable_ID] ON [SomeBigTable]
(
[ID] ASC
) WITH PAD_INDEX = OFF
,STATISTICS_NORECOMPUTE = OFF
,SORT_IN_TEMPDB = OFF
,IGNORE_DUP_KEY = OFF
,ONLINE = OFF
,ALLOW_ROW_LOCKS = ON
,ALLOW_PAGE_LOCKS = ON
,FILLFACTOR = 98
ON PRIMARY
There is also a foreign key constraint on the [ID] column pointing to an equally large table.
The 600 table scans are about ~4% of the total delete operations per hour on this table using the same statement. So, not all executions of this statement cause a table scan.
It goes without saying, but saying it anyway...this is a lot of nasty I/O that I'd like to send packing.
Have you tried recomputing statistics on the table and clearing your proc cache?
e.g. something like this:
USE myDatabase;
GO
UPDATE STATISTICS SomeBigTable;
GO
DBCC FREEPROCCACHE
DBCC DROPCLEANBUFFERS
It could be that sql server is just using the wrong index because it has a bad plan cached from when there was different data in the table.
Some things to try, some things to check:
Are you running a DELETE SomBigTable where ID = #Parameter statement? If so, is #Parameter of type int, or is it a different datatype than the column being deleted? (Probably not it, but I hit a situation once where a string was getting cast as unicode, and that caused an index to be ignored.)
Make a copy of the database and mess around with it:
Try to identify which deletes cause a scan, and which do not
Is it related to the presence or absense of data in the FK-related table?
Is the foreign key trusted (check via sys.foriegn_keys)
Drop the FK. Does it change anything?
Drop the second index. Does that change anything?
Might be none of these, but while messing around with them you might stumble across the real issue.
I wrote a script that creates some tables (it previously drops them if they exist) and then tries to create two indexes on each table.
The first index uses the Primary Key column to create a non-clustered index, and the second uses another column to create the clustered indexed. This is because the primary key column is a GUID instead of an int.
How can I drop the default index if I don't know it's name? or how can I specify a name for the primary key column index so I can drop it? Or better yet, how can I specify the 2 index i need right in the Create Table statement?
SELECT * FROM sys.indexes
However, I'm not understanding where in your process you actually have to drop an index.
You said you are creating some tables and then creating two indexes on each table.
If you are DROPping existing tables at the beginning, any indexes are automatically dropped.
There is no such thing as a default index.
Tables can either be heaps or clustered indexes. If you drop the clustered index, the table will be converted to a heap and any non-clustered indexes will have to be updated to point to the data in the unordered heap.
You can create like this all at once:
CREATE TABLE dbo.tbl
(
Id int NOT NULL IDENTITY (1, 1) CONSTRAINT UK_ID UNIQUE CLUSTERED,
SomeUUID UNIQUEIDENTIFIER NOT NULL CONSTRAINT PK_SomeUUID PRIMARY KEY NONCLUSTERED
)
Here's a SQLFiddle: http://sqlfiddle.com/#!6/d759e/12
You can define the two indices right after you create the table:
CREATE TABLE dbo.YourTable ( ...... )
GO
ALTER TABLE dbo.YourTable
ADD CONSTRAINT PK_YourTable PRIMARY KEY NONCLUSTERED (YourGuidColumn)
****************
this is crucial ! Otherwise, your PK will be clustered!
CREATE CLUSTERED INDEX IX01_YourTable ON dbo.YourTable(YourOtherColumn)
or even better:
CREATE UNIQUE CLUSTERED INDEX IX01_YourTable ON dbo.YourTable(YourOtherColumn)
That should create a non-clustered primary key and a separate (preferably unique) clustered index on a separate column.
I create the following table in H2:
CREATE TABLE TEST
(ID BIGINT NOT NULL PRIMARY KEY)
Then I look into INFORMATION_SCHEMA.TABLES table:
SELECT SQL
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME = 'TEST'
Result:
CREATE CACHED TABLE TEST(
ID BIGINT NOT NULL
)
Then I look into INFORMATION_SCHEMA.CONSTRAINTS table:
SELECT SQL
FROM INFORMATION_SCHEMA.CONSTRAINTS
WHERE TABLE_NAME = 'TEST'
Result:
ALTER TABLE TEST
ADD CONSTRAINT CONSTRAINT_4C
PRIMARY KEY(ID)
INDEX PRIMARY_KEY_4C
These statements are not the ones which I have stated, therefore, the question is:
Is the information in TABLES and CONSTRAINS reflects how real SQL which was executed in database?
In original CREATE TABLE statement
there was no CACHED word. (not a problem)
I have never executed ALTER TABLE .. ADD CONSTRAINT statement.
The actual reason why I am asking the question is that I am not sure which statement should I execute in order to guarantee that primary key is used in a clustered index.
If you look at my previous question H2 database: clustered index support then you may find in the answer of Thomas Mueller the following statement:
If a primary key is created after the table has been created then the primary key is stored in a new index b-tree.
Therefore, if the statements are executed as such they are shown in INFORMATION_SCHEMA, then primary key is created after the table is created and hence ID is not used in a clustered index (basically as a key in a data b-tree).
Is there a way how one can guarantee that primary key is used in a clustered index in H2?
Is the information in TABLES and CONSTRAINS reflects how real SQL which was executed in database?
Yes. Basically, those are the statements that are run when opening the database.
If you look at my previous question
The answer "If a primary key is created after the table has been created..." was incorrect, I fixed it now to "If a primary key is created after data has been inserted...".
Is there a way how one can guarantee that primary key is used as a clustered index in H2?
This is now better described in the H2 documentation at "How Data is Stored Internally": "If a single column primary key of type BIGINT, INT, SMALLINT, TINYINT is specified when creating the table (or just after creating the table, but before inserting any rows), then this column is used as the key of the data b-tree."