Rebuild or Reorganize indexes - sql

I want to reorganize or rebuild indexes. I know the sql to perform this action on all indexes in a table is
alter index all on table_name reorganize;
But I only want to rebuild or reorganize if fragmentation percentage on each index is between a certain range. Is this possible to do?

Here is a procedure what I wrote
CREATE PROCEDURE dbo.ixRebuild #fillfactor int = 100, #Force bit = 0, #Schema varchar(255) = NULL, #Table varchar(255) = NULL, #PkOnly bit = 0
AS
/*
* ixRebuild
* Rebuild all indices in a database.
* Indices with >30% fragmentation are rebuilt.
* Indices with >6% fragmentation are just reorganised.
*
* The default fill factor is 100%.
*
* Required permissions are:
* GRANT VIEW DATABASE STATE TO <user>
* GRANT ALTER TO <user>
* GRANT EXEC ON ixRebuild TO <user>
*
* Created 17/9/08 by rwb.
*/
BEGIN
DECLARE #db int
DECLARE #tab varchar(256)
DECLARE #ix int
DECLARE #ixName varchar(256)
DECLARE #frag float
DECLARE #cmd varchar(1024)
DECLARE #type int
DECLARE c CURSOR FAST_FORWARD FOR
SELECT DISTINCT s.database_id, s.index_id, i.name,
Convert(float, s.avg_fragmentation_in_percent),
ss.name + '.' + t.name AS tableName,
i.type
FROM sys.dm_db_index_physical_stats(Db_Id(), NULL, NULL, NULL, NULL) s
INNER JOIN sys.indexes i ON s.object_id = i.object_id AND s.index_id = i.index_id
INNER JOIN sys.tables t ON i.object_id = t.object_id
INNER JOIN sys.schemas ss ON t.schema_id = ss.schema_id
WHERE (#Schema IS NULL OR ss.name = #Schema)
AND (#Table IS NULL OR t.name = #Table)
AND (#PkOnly = 0 OR i.is_primary_key = 1)
AND (
#Force = 1
OR (
avg_fragmentation_in_percent > 6
AND page_count > 100 -- rebuilding small indices does nothing
AND i.name IS NOT NULL -- for tables with no PK
)
)
-- DISTINCT because ys.dm_db_index_physical_stats
-- contains a row for each part of a partitioned index on a partitioned table.
OPEN c
FETCH NEXT FROM c INTO #db, #ix, #ixName, #frag, #tab, #type
WHILE ##Fetch_Status = 0
BEGIN
PRINT Db_Name( #db ) + ' / ' + #tab + ' / ' + #ixName + ' ' + Cast(#frag as varchar(16))
SET #cmd = ''
IF #frag < 10.0 AND #Force = 0
BEGIN
SET #cmd = 'ALTER INDEX ' + #ixName + ' ON ' + #tab + ' REORGANIZE'
END
ELSE
BEGIN
SET #cmd = 'ALTER INDEX ' + #ixName + ' ON ' + #tab +
CASE
WHEN #type IN (1, 2) THEN ' REBUILD WITH (FILLFACTOR = ' + Cast(#fillfactor AS varchar(4)) + ')'
ELSE ''
END
END
RAISERROR(#cmd, 0, 1) WITH NOWAIT;
EXEC (#cmd)
FETCH NEXT FROM c INTO #db, #ix, #ixName, #frag, #tab, #type
END
CLOSE c
DEALLOCATE c
END

Generally rebuilding all indexes of a table is a bad idea, because some can have no fragmentation, while some other can be horribly bloated !
Rebuiliding index is a heavy process and is blocking the access of the table the time to do so, except in ONLINE mode available only with the Enterprise version.
So you need to rebuild only under certains circumstances... Authors (which I think due to my old age in RDBMS, I am a part of them...) agree to say that small tables don't care and a percentage of fragmentation under 30 does not matter.
I alway add some complexity over these ideas, with another point : "big" rows of table or indexes (1600 bytes is a max for an index) will appear always to be fragmented, but are not... This because evaluating a frag percent take in account a percent of free space on a page and with a 8 Kb page, a natural no-recoverable space will stay inside the page (20% max for an index and 49 % max for a table).
So a good practice is to mix all those considerations to build a proper code to rebuild or defrag all objects includes in your database.
As an example, a short code to do so, can be :
DECLARE #SQL NVARCHAR(max) = N'';
SELECT #SQL = #SQL + CASE WHEN i.name IS NULL
THEN N'ALTER TABLE [' + s.name + '].[' + o.name + '] REBUILD;'
WHEN avg_fragmentation_in_percent > 30
THEN N'ALTER INDEX [' + i.name + '] ON [' + s.name + '].[' + o.name + '] REBUILD;'
ELSE N'ALTER INDEX [' + i.name + '] ON [' + s.name + '].[' + o.name + '] REORGANIZE;'
END
FROM sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL, NULL, NULL) AS ips
JOIN sys.objects AS o ON ips.object_id = o.object_id
JOIN sys.schemas AS s ON o.schema_id = s.schema_id
JOIN sys.indexes AS i ON ips.object_id = i.object_id AND ips.index_id = i.index_id
WHERE ips.page_count > 64 AND avg_fragmentation_in_percent > 10;
EXEC (#SQL);

Related

SQL Server rebuilding indexes - script

I'm using a script from #Namphibian, but I have some problems there.
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
CREATE TABLE #FragmentedIndexes
(
DatabaseName SYSNAME,
SchemaName SYSNAME,
TableName SYSNAME,
IndexName SYSNAME,
[Fragmentation%] FLOAT
)
INSERT INTO #FragmentedIndexes
SELECT
DB_NAME(DB_ID()) AS DatabaseName,
ss.name AS SchemaName,
OBJECT_NAME (s.object_id) AS TableName,
i.name AS IndexName,
s.avg_fragmentation_in_percent AS [Fragmentation%]
FROM
sys.dm_db_index_physical_stats(db_id(),NULL, NULL, NULL, 'SAMPLED') s
INNER JOIN
sys.indexes i ON s.[object_id] = i.[object_id]
AND s.index_id = i.index_id
INNER JOIN
sys.objects o ON s.object_id = o.object_id
INNER JOIN
sys.schemas ss ON ss.[schema_id] = o.[schema_id]
WHERE
s.database_id = DB_ID()
AND i.index_id != 0
AND s.record_count > 0
AND o.is_ms_shipped = 0
DECLARE #RebuildIndexesSQL NVARCHAR(MAX)
SET #RebuildIndexesSQL = ''
SELECT
#RebuildIndexesSQL = #RebuildIndexesSQL +
CASE
WHEN [Fragmentation%] > 30
THEN CHAR(10) + 'ALTER INDEX ' + QUOTENAME(IndexName) + ' ON '
+ QUOTENAME(SchemaName) + '.'
+ QUOTENAME(TableName) + ' REBUILD;'
WHEN [Fragmentation%] > 10
THEN CHAR(10) + 'ALTER INDEX ' + QUOTENAME(IndexName) + ' ON '
+ QUOTENAME(SchemaName) + '.'
+ QUOTENAME(TableName) + ' REORGANIZE;'
END
FROM #FragmentedIndexes
WHERE [Fragmentation%] > 10
DECLARE #StartOffset INT
DECLARE #Length INT
SET #StartOffset = 0
SET #Length = 4000
WHILE (#StartOffset < LEN(#RebuildIndexesSQL))
BEGIN
PRINT SUBSTRING(#RebuildIndexesSQL, #StartOffset, #Length)
SET #StartOffset = #StartOffset + #Length
END
PRINT SUBSTRING(#RebuildIndexesSQL, #StartOffset, #Length)
EXECUTE sp_executesql #RebuildIndexesSQL
DROP TABLE #FragmentedIndexes
But instead of 'SAMPLED' I'm using 'DETAILED' but still some indexes are not rebuilt. I found a few indexes with the same value of fragmentation over 30% which still wasn't rebuilded or reorganized yet. That script is running every night last 4 days. My problem is I can't use Maintentance plans for this task.
Any ideas please?
According to this answer:
https://dba.stackexchange.com/questions/18372/why-index-rebuild-does-not-reduce-index-fragmentatation
You need to consider the number of page of your index to know if you do a rebuild
I will recommend to change your INSERT INTO SELECT to this
SELECT
DB_NAME(DB_ID()) AS DatabaseName,
ss.name AS SchemaName,
OBJECT_NAME (s.object_id) AS TableName,
i.name AS IndexName,
s.avg_fragmentation_in_percent AS [Fragmentation%],
page_count
FROM
sys.dm_db_index_physical_stats(db_id(),NULL, NULL, NULL, 'DETAILED') s
INNER JOIN
sys.indexes i ON s.[object_id] = i.[object_id]
AND s.index_id = i.index_id
INNER JOIN
sys.objects o ON s.object_id = o.object_id
INNER JOIN
sys.schemas ss ON ss.[schema_id] = o.[schema_id]
WHERE
s.database_id = DB_ID()
AND i.index_id != 0
AND s.record_count > 0
AND o.is_ms_shipped = 0
AND s.avg_fragmentation_in_percent > 0
AND page_count > 1000

Get all UNIQUEIDENTIFIER values based on variables in query

Goal is to get all UNIQUEIDENTIFIER values from all columns in database.
Code which is supposed to load all those values:
DECLARE #TableNames TABLE
(
ID INT NOT NULL IDENTITY(0, 1),
TableName NVARCHAR(50) NOT NULL,
ColName NVARCHAR(50) NOT NULL
);
DECLARE #Guids TABLE
(
ID INT NOT NULL IDENTITY(0, 1),
FoundGuid UNIQUEIDENTIFIER NOT NULL
);
DECLARE #Local NVARCHAR(50);
WHILE #Counter < 500
BEGIN
SELECT #Local = TableName FROM #TableNames WHERE Id = #Counter;
INSERT INTO #Guids EXEC('SELECT Id FROM [' + #Local + ']');
SET #Counter = #Counter + 1;
END;
Is this safe thing to do so? Eventually, what is the way to get those values?
I would use the system views to generate dynamic sql. This is 100% accurate and not limited to only those columns named Id. It won't matter what schema or column name is used. This will get you all those values with no looping at all.
declare #SQL nvarchar(max) = ''
select #SQL = #SQL + 'select ' + QUOTENAME(c.name) + ' = ' + QUOTENAME(c.name)
+ ' FROM ' + QUOTENAME(s.name) + '.' + QUOTENAME(t.name)
+ ' UNION ALL '
from sys.tables t
join sys.columns c on c.object_id = t.object_id
join sys.types ty on ty.user_type_id = c.user_type_id
join sys.schemas s on s.schema_id = t.schema_id
where ty.name = 'uniqueidentifier'
--removes the last UNION ALL
select #SQL = left(#SQL, len(#SQL) - 10)
select #SQL
--uncomment below to execute the dynamic sql when you are comfortable it is correct
--exec sp_executesql #SQL

How to update all datetime records to UTC time in a SQL Server database?

I have a SQL Server database with ~1000 tables. Most of the tables have datetime columns with different name. Currently the value stored there is with offset because the code inserted it with the current date from the server.
The goal is to have all datetime records in UTC time.
Now I want to do two things:
Using some of these 3 functions (SYSDATETIME, SYSDATETIMEOFFSET, SYSUTCDATETIME) I can get the offset. Next step is to find all datetime records and update them. Can you help me with this?
Some of the datetime columns have default value GETTIME. I want to update that with SYSUTCDATETIME. Any idea how?
PS: I do not want to change the columns' type to datetimeoffset because the type is not supported in MSSQL Server 2005.
EDIT: Because there are so many comments about datetime and the topic of the question was shifted in different direction I suggest you not to think about datetime column type but for int. The problem is still the same. Updating many tables and changing the default value of columns.
Best Regards
mynkow
Dynamic SQL to update all user databases on your server.
The first query outputs to the Results tab.
The second query outputs to the Messages tab.
SET NOCOUNT ON;
GO
---------------------------------------------
-- #1 Dynamic SQL to update DATETIME values with UTC offset
DECLARE #t TABLE(TABLE_CATALOG VARCHAR(128), TABLE_SCHEMA VARCHAR(128), TABLE_NAME VARCHAR(128), COLUMN_NAME VARCHAR(128));
INSERT #t
EXEC sp_msforeachdb 'select db = "?"
, s.name
, t.name
, c.name
FROM [?].sys.tables t
JOIN [?].sys.columns c ON c.object_id = t.object_id
JOIN [?].sys.types y ON y.user_type_id = c.user_type_id
JOIN [?].sys.schemas s ON s.schema_id = t.schema_id
WHERE t.[type] = ''U''
AND y.name = ''DATETIME''
AND "?" NOT IN (''master'', ''tempdb'', ''model'', ''msdb'')';
DECLARE #Offset INT;
SET #Offset = - DATEPART(TZOFFSET, SYSDATETIMEOFFSET());
SELECT [SQL] = 'UPDATE ['+ C.TABLE_CATALOG +'].[' + C.TABLE_SCHEMA + '].[' + C.TABLE_NAME + '] SET [' + C.COLUMN_NAME + '] = DATEADD(MINUTE,' + CAST(#Offset AS VARCHAR(5)) + ',[' + C.COLUMN_NAME + ']);'
FROM #t C;
GO
---------------------------------------------
-- #2 Dynamic SQL to change DATETIME column defaults to SYSUTCDATETIME
DECLARE #t TABLE([SQL] VARCHAR(MAX));
DECLARE #SQL VARCHAR(MAX);
INSERT #t
EXEC sp_msforeachdb 'SELECT [SQL] = ''---------------------------------------------'' + CHAR(13) + CHAR(10)
+ ''-- [?].[''+s.name+''].[''+t.name+''].['' + c.name + '']'' + CHAR(13) + CHAR(10)
+ ''ALTER TABLE [?].[''+s.name+''].[''+t.name+'']'' + CHAR(13) + CHAR(10)
+ ''DROP CONSTRAINT [''+d.name + '']'' + CHAR(13) + CHAR(10)
+ ''GO'' + CHAR(13) + CHAR(10)
+ ''ALTER TABLE [?].[''+s.name+''].[''+t.name+''] ADD CONSTRAINT'' + CHAR(13) + CHAR(10)
+ ''[''+d.name+''] DEFAULT (SYSUTCDATETIME()) FOR [''+c.name + '']'' + CHAR(13) + CHAR(10)
+ ''GO'' + CHAR(13) + CHAR(10) + CHAR(13) + CHAR(10)
FROM [?].sys.default_constraints d
JOIN [?].sys.columns c ON c.default_object_id = d.object_id
JOIN [?].sys.types y ON y.user_type_id = c.user_type_id
JOIN [?].sys.tables t ON t.object_id = d.parent_object_id AND t.[type] = ''U''
JOIN [?].sys.schemas s ON s.schema_id = t.schema_id
WHERE y.name = ''datetime''
AND "?" NOT IN (''master'', ''tempdb'', ''model'', ''msdb'')';
DECLARE C CURSOR FOR
SELECT * FROM #t
OPEN C
FETCH NEXT FROM C INTO #SQL;
WHILE ##FETCH_STATUS = 0 BEGIN
PRINT #SQL;
FETCH NEXT FROM C INTO #SQL;
END
CLOSE C;
DEALLOCATE C;
GO

Deadlock when querying INFORMATION_SCHEMA

I have a process which dynamically alters my SQL2K5 table structure according to changes in a published meta-data layer.
For example, if a new column needs to be added and the table has NO dependancies - the steps would be:
1. Create scripts using T-SQL for any indexes & primary keys that already exist on the table [these scripts are included below]
2. Drop the table
3. Re-create the table from the meta-layer that has the new column
4. Execute the scripts created in step#1
5. Populate the table using BulkCopy
The above is initiated via a .NET assembly and runs in 3 concurrent streams on a daily basis.
I am receiving a deadlock error in step #1 - when I access the INFORMATION_SCHEMA tables to script out the indexes/keys. I have used the hint WITH(NOLOCK) in these scripts thinking this should prevent any locking when 3 streams of these actions are running concurrently. A table can only be processed (the create or scripting) in 1 stream.
Is there something more I need to do???
Any comments greatly appreciated.
[Scripts]
ALTER Procedure [dbo].[s$spScriptPrimaryKeyForTable]
#Tablename varchar(100)
AS
-- Get all existing primary keys
DECLARE cPK CURSOR FOR
SELECT TABLE_NAME, CONSTRAINT_NAME
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WITH(NOLOCK)
WHERE upper(TABLE_NAME)=upper(#Tablename)
ORDER BY TABLE_NAME
DECLARE #PkTable SYSNAME
DECLARE #PkName SYSNAME
-- Loop through all the primary keys
OPEN cPK
FETCH NEXT FROM cPK INTO #PkTable, #PkName
WHILE (##FETCH_STATUS = 0)
BEGIN
DECLARE #PKSQL NVARCHAR(4000) SET #PKSQL = ''
SET #PKSQL = 'ALTER TABLE ' + #PkTable + ' ADD CONSTRAINT ' + #PkName + ' PRIMARY KEY CLUSTERED ('
-- Get all columns for the current primary key
DECLARE cPKColumn CURSOR FOR
SELECT COLUMN_NAME
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WITH(NOLOCK)
WHERE TABLE_NAME = #PkTable AND CONSTRAINT_NAME = #PkName
ORDER BY ORDINAL_POSITION
OPEN cPKColumn
DECLARE #PkColumn SYSNAME
DECLARE #PkFirstColumn BIT SET #PkFirstColumn = 1
-- Loop through all columns and append the sql statement
FETCH NEXT FROM cPKColumn INTO #PkColumn
WHILE (##FETCH_STATUS = 0)
BEGIN
IF (#PkFirstColumn = 1)
SET #PkFirstColumn = 0
ELSE
SET #PKSQL = #PKSQL + ', '
SET #PKSQL = #PKSQL + #PkColumn
FETCH NEXT FROM cPKColumn INTO #PkColumn
END
CLOSE cPKColumn
DEALLOCATE cPKColumn
SET #PKSQL = #PKSQL + ')'
-- Print the primary key statement
-- PRINT #PKSQL
FETCH NEXT FROM cPK INTO #PkTable, #PkName
END
CLOSE cPK
DEALLOCATE cPK
SELECT ISNULL(#PKSQL,' ')
================
ALTER Procedure [dbo].[s$spScriptIndexesForTable]
#Tablename varchar(100)
AS
DECLARE #RetVal varchar(4000)
SET #RetVal = ''
-- Get all existing indexes, but NOT the primary keys
DECLARE cIX CURSOR FOR
SELECT OBJECT_NAME(SI.Object_ID), SI.Object_ID, SI.Name, SI.Index_ID
FROM Sys.Indexes SI WITH(NOLOCK)
LEFT JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS TC WITH(NOLOCK) ON SI.Name = TC.CONSTRAINT_NAME AND OBJECT_NAME(SI.Object_ID) = TC.TABLE_NAME
WHERE TC.CONSTRAINT_NAME IS NULL
AND OBJECTPROPERTY(SI.Object_ID, 'IsUserTable') = 1
AND upper(OBJECT_NAME(SI.Object_ID))=upper(#Tablename)
ORDER BY OBJECT_NAME(SI.Object_ID), SI.Index_ID
DECLARE #IxTable SYSNAME
DECLARE #IxTableID INT
DECLARE #IxName SYSNAME
DECLARE #IxID INT
-- Loop through all indexes
OPEN cIX
FETCH NEXT FROM cIX INTO #IxTable, #IxTableID, #IxName, #IxID
WHILE (##FETCH_STATUS = 0)
BEGIN
DECLARE #IXSQL NVARCHAR(4000)
--SET #PKSQL = ''
SET #IXSQL = 'CREATE '
-- Check if the index is unique
IF (INDEXPROPERTY(#IxTableID, #IxName, 'IsUnique') = 1)
SET #IXSQL = #IXSQL + 'UNIQUE '
-- Check if the index is clustered
IF (INDEXPROPERTY(#IxTableID, #IxName, 'IsClustered') = 1)
SET #IXSQL = #IXSQL + 'CLUSTERED '
SET #IXSQL = #IXSQL + 'INDEX ' + #IxName + ' ON [' + #IxTable + '] ('
-- Get all columns of the index
DECLARE cIxColumn CURSOR FOR
SELECT SC.Name,IC.[is_included_column],IC.is_descending_key
FROM Sys.Index_Columns IC WITH(NOLOCK)
JOIN Sys.Columns SC WITH(NOLOCK) ON IC.Object_ID = SC.Object_ID AND IC.Column_ID = SC.Column_ID
WHERE IC.Object_ID = #IxTableID AND Index_ID = #IxID
ORDER BY IC.Index_Column_ID,IC.is_included_column
DECLARE #IxColumn SYSNAME
DECLARE #IxIncl bit
DECLARE #Desc bit
DECLARE #IxIsIncl bit set #IxIsIncl = 0
DECLARE #IxFirstColumn BIT SET #IxFirstColumn = 1
-- Loop throug all columns of the index and append them to the CREATE statement
OPEN cIxColumn
FETCH NEXT FROM cIxColumn INTO #IxColumn, #IxIncl, #Desc
WHILE (##FETCH_STATUS = 0)
BEGIN
IF (#IxFirstColumn = 1)
BEGIN
SET #IxFirstColumn = 0
END
ELSE
BEGIN
--check to see if it's an included column
IF ((#IxIsIncl = 0) AND (#IxIncl = 1))
BEGIN
SET #IxIsIncl = 1
SET #IXSQL = #IXSQL + ') INCLUDE ('
END
ELSE
BEGIN
SET #IXSQL = #IXSQL + ', '
END
END
SET #IXSQL = #IXSQL + '[' + #IxColumn + ']'
--check to see if it's DESC
IF #Desc = 1
SET #IXSQL = #IXSQL + ' DESC'
FETCH NEXT FROM cIxColumn INTO #IxColumn, #IxIncl, #Desc
END
CLOSE cIxColumn
DEALLOCATE cIxColumn
SET #IXSQL = #IXSQL + ')'
-- Print out the CREATE statement for the index
--SELECT 'IXSQL: ' + #IXSQL
IF #RetVal IS NULL
SET #RetVal = ''
--SELECT 'Retval: ' + #RetVal
SET #RetVal = #RetVal + #IXSQL + ' '
FETCH NEXT FROM cIX INTO #IxTable, #IxTableID, #IxName, #IxID
END
CLOSE cIX
DEALLOCATE cIX
SELECT ISNULL(#RetVal,' ')
INFORMATION_SCHEMA views are just that - views. You can't update them so they are unlikely to cause any deadlocks. If you want to determine the real source (which I assume has something to do with your alters, or other code within the cursor that you didn't show, or other code you're calling in combination with calling these procedures - since selects against views and then selecting variables can't be the cause), I suggest reading Gail Shaw's blog post on interpreting deadlocks.
In spite of (1) I still suggest using more modern catalog views than INFORMATION_SCHEMA. The same information can be derived from, for example, sys.key_constraints.
You're using the default cursor options; and you're nesting cursors. If you end up still using cursors, you should get in the habit of using a less resource intensive cursor (e.g. LOCAL STATIC FORWARD_ONLY READ_ONLY).
You don't actually need a cursor to do this. Here is how I would re-write the PK table script:
CREATE PROCEDURE dbo.ScriptPKForTable
#TableName SYSNAME
AS
BEGIN
SET NOCOUNT ON;
DECLARE
#pkName SYSNAME,
#clustered BIT,
#object_id INT,
#sql NVARCHAR(MAX);
SELECT
#object_id = OBJECT_ID(UPPER(#TableName));
SELECT
#pkName = kc.name,
#clustered = CASE i.[type]
WHEN 1 THEN 1 ELSE 0 END
FROM
sys.key_constraints AS kc
INNER JOIN
sys.indexes AS i
ON kc.parent_object_id = i.[object_id]
AND kc.unique_index_id = i.index_id
WHERE
kc.parent_object_id = #object_id
AND kc.[type] = 'pk';
SET #sql = N'ALTER TABLE ' + QUOTENAME(#TableName)
+ ' ADD CONSTRAINT ' + #pkName
+ ' PRIMARY KEY ' + CASE #clustered
WHEN 1 THEN 'CLUSTERED' ELSE '' END + ' (';
SELECT
#sql = #sql + c.name + ','
FROM
sys.index_columns AS ic
INNER JOIN
sys.indexes AS i
ON ic.index_id = i.index_id
AND ic.[object_id] = i.[object_id]
INNER JOIN
sys.key_constraints AS kc
ON i.[object_id] = kc.[parent_object_id]
AND kc.unique_index_id = i.index_id
INNER JOIN
sys.columns AS c
ON i.[object_id] = c.[object_id]
AND ic.column_id = c.column_id
WHERE
kc.[type] = 'PK'
AND kc.parent_object_id = #object_id
ORDER BY key_ordinal;
SET #sql = LEFT(#sql, LEN(#sql) - 1) + ');';
SELECT COALESCE(#sql, ' ');
END
GO
As for the index creation script, I think there is a better way to do this (again without explicit cursors, not that avoiding the cursor is the goal, but the code is going to be a LOT cleaner). First you need a function to build either key or include columns from the index:
CREATE FUNCTION dbo.BuildIndexColumns
(
#object_id INT,
#index_id INT,
#included_columns BIT
)
RETURNS NVARCHAR(MAX)
AS
BEGIN
DECLARE #s NVARCHAR(MAX);
SELECT #s = N'';
SELECT #s = #s + c.name + CASE ic.is_descending_key
WHEN 1 THEN ' DESC' ELSE '' END + ','
FROM sys.index_columns AS ic
INNER JOIN sys.columns AS c
ON ic.[object_id] = c.[object_id]
AND ic.column_id = c.column_id
WHERE c.[object_id] = #object_id
AND ic.[object_id] = #object_id
AND ic.index_id = #index_id
AND ic.is_included_column = #included_columns
ORDER BY ic.key_ordinal;
IF #s > N''
SET #s = LEFT(#s, LEN(#s)-1);
RETURN (NULLIF(#s, N''));
END
GO
With that function in place, a ScriptIndexes procedure is pretty easy:
CREATE PROCEDURE dbo.ScriptIndexesForTable
#TableName SYSNAME
AS
BEGIN
SET NOCOUNT ON;
DECLARE
#sql NVARCHAR(MAX),
#object_id INT;
SELECT #sql = N'', #object_id = OBJECT_ID(UPPER(#TableName));
SELECT #sql = #sql + 'CREATE '
+ CASE i.is_unique WHEN 1 THEN 'UNIQUE ' ELSE '' END
+ CASE i.[type] WHEN 1 THEN 'CLUSTERED ' ELSE '' END
+ ' INDEX ' + i.name + ' ON ' + QUOTENAME(#TableName) + ' ('
+ dbo.BuildIndexColumns(#object_id, i.index_id, 0)
+ ')' + COALESCE(' INCLUDE('
+ dbo.BuildIndexColumns(#object_id, i.index_id, 1)
+ ')', '') + ';' + CHAR(13) + CHAR(10)
FROM
sys.indexes AS i
WHERE
i.[object_id] = #object_id
-- since this will be covered by ScriptPKForTable:
AND i.is_primary_key = 0
ORDER BY i.index_id;
SELECT COALESCE(#sql, ' ');
END
GO
Note that my solution does not assume the PK is clustered (your PK script hard-codes CLUSTERED but then your index script assumes that any of the indexes could be clustered). I also ignore additional properties such as filegroup, partitioning, or filtered indexes (not supported in 2005 anyway).

How can I drop all indexes in a SQL database with one command?

So, how can I drop all indexes in a SQL database with one command? I have this command that will get me all the 20 or so drop statements, but how can I run all of those drop statements from this "result set"?
select * from vw_drop_idnex;
Another variation that gives me the same list is:
SELECT 'DROP INDEX ' + ix.Name + ' ON ' + OBJECT_NAME(ID) AS QUERYLIST
FROM sysindexes ix
WHERE ix.Name IS NOT null and ix.Name like '%pre_%'
I tried to do "exec(select cmd from vw_drop_idnex)" and it didn't work. I am looking for something that works like a for loop and runs the queries one by one.
-----------------------
With Rob Farleys help, final draft of the script is:
declare #ltr nvarchar(1024);
SELECT #ltr = ( select 'alter table '+o.name+' drop constraint '+i.name+';'
from sys.indexes i join sys.objects o on i.object_id=o.object_id
where o.type<>'S' and is_primary_key=1
FOR xml path('') );
exec sp_executesql #ltr;
declare #qry nvarchar(1024);
select #qry = (select 'drop index '+o.name+'.'+i.name+';'
from sys.indexes i join sys.objects o on i.object_id=o.object_id
where o.type<>'S' and is_primary_key<>1 and index_id>0
for xml path(''));
exec sp_executesql #qry
You're very close.
declare #qry nvarchar(max);
select #qry =
(SELECT 'DROP INDEX ' + quotename(ix.name) + ' ON ' + quotename(object_schema_name(object_id)) + '.' + quotename(OBJECT_NAME(object_id)) + '; '
FROM sys.indexes ix
WHERE ix.Name IS NOT null and ix.Name like '%prefix_%'
for xml path(''));
exec sp_executesql #qry
this worked for me
we skip sys indexes and for constraints
declare #qry nvarchar(max);
select #qry = (
select 'IF EXISTS(SELECT * FROM sys.indexes WHERE name='''+ i.name +''' AND object_id = OBJECT_ID(''['+s.name+'].['+o.name+']'')) drop index ['+i.name+'] ON ['+s.name+'].['+o.name+']; '
from sys.indexes i
inner join sys.objects o on i.object_id=o.object_id
inner join sys.schemas s on o.schema_id = s.schema_id
where o.type<>'S' and is_primary_key<>1 and index_id>0
and s.name!='sys' and s.name!='sys' and is_unique_constraint=0
for xml path(''));
exec sp_executesql #qry
From: Stephen Hill's Bloggie
DECLARE #indexName VARCHAR(128)
DECLARE #tableName VARCHAR(128)
DECLARE [indexes] CURSOR FOR
SELECT [sysindexes].[name] AS [Index],
[sysobjects].[name] AS [Table]
FROM [sysindexes]
INNER JOIN [sysobjects]
ON [sysindexes].[id] = [sysobjects].[id]
WHERE [sysindexes].[name] IS NOT NULL
AND [sysobjects].[type] = 'U'
--AND [sysindexes].[indid] > 1
OPEN [indexes]
FETCH NEXT FROM [indexes] INTO #indexName, #tableName
WHILE ##FETCH_STATUS = 0
BEGIN
--PRINT 'DROP INDEX [' + #indexName + '] ON [' + #tableName + ']'
Exec ('DROP INDEX [' + #indexName + '] ON [' + #tableName + ']')
FETCH NEXT FROM [indexes] INTO #indexName, #tableName
END
CLOSE [indexes]
DEALLOCATE [indexes]
GO
None of the answers quite suited my needs.
I needed one that will also drop indexes that backup unique or primary constraints (except if these can't be dropped as they back up a foreign key)
DECLARE #SqlScript NVARCHAR(MAX);
SELECT #SqlScript =
(
SELECT
'
BEGIN TRY
'+ CASE WHEN 1 IN (i.is_primary_key, i.is_unique_constraint) THEN
'
ALTER TABLE ' + QUOTENAME(OBJECT_SCHEMA_NAME(i.object_id)) + '.' + QUOTENAME(t.name) + ' DROP CONSTRAINT ' + QUOTENAME(i.name) + ';'
else
'
DROP INDEX ' + QUOTENAME(i.name) + ' ON ' + QUOTENAME(OBJECT_SCHEMA_NAME(i.object_id)) + '.' + QUOTENAME(t.name)
END+'
END TRY
BEGIN CATCH
RAISERROR(''Could not drop %s on table %s'', 0,1, ' + QUOTENAME(i.name, '''') + ', ' + QUOTENAME(t.name, '''') + ')
END CATCH
'
FROM sys.indexes i
JOIN sys.tables t ON i.object_id = t.object_id
WHERE i.type_desc IN ('CLUSTERED', 'NONCLUSTERED' )
ORDER BY t.object_id, i.index_id DESC
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)');
--Return script that will be run
SELECT #SqlScript AS [processing-instruction(x)]
FOR XML PATH('');
EXEC (#SqlScript);
Minor improvements to the accepted answer that I had to make in my own case, mostly to account for schemas:
declare #qry nvarchar(4000);
select #qry = (select 'drop index ['+s.name+'].['+o.name+'].['+i.name+'];'
from sys.indexes i join sys.objects o on i.object_id=o.object_id join sys.schemas s on o.schema_id=s.schema_id
where o.type<>'S' and is_primary_key<>1 and index_id>0 and s.name<>'sys'
for xml path(''));
exec sp_executesql #qry
Also: In my case it couldn't run in one go because the script becomes longer than 4000 characters. The only way I could think of to deal with that was to put a "top 20" on the inner select and execute it multiple times.
The "Final Draft" that OP posts as part of his question errors out if there are already zero indexes on any table in the DB. I needed to fix that.
Also, I wanted more control over the process than dropping all indexes on all tables, so I wrote the following stored proc to do it one table at a time:
CREATE PROCEDURE [dbo].[sp_DropAllIndexesOnTable]
#TableName varchar(1000)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #DropCommand1 nvarchar(4000)
DECLARE #DropCommand2 nvarchar(4000)
--Create Temp Table to hold the names and table names of all Clustered Indexes
SELECT o.name AS TableName, i.name AS IndexName
INTO #AllClustered
FROM sys.indexes i
INNER JOIN sys.objects o ON i.object_id=o.object_id
WHERE o.type <> 'S'
AND is_primary_key = 1
--Create Temp Table to hold the names and table names of all NonClustered Indexes
SELECT o.name AS TableName, i.name AS IndexName
INTO #AllNonClustered
FROM sys.indexes i
INNER JOIN sys.objects o ON i.object_id=o.object_id
WHERE o.type <> 'S'
AND is_primary_key <> 1
AND index_id > 0
--Create DROP CONSTRAINT command for the Primary Key Constraint if there is one
SELECT #DropCommand1 = ( SELECT 'ALTER TABLE dbo.['+ TableName +'] DROP CONSTRAINT ['+ IndexName +'];'
FROM #AllClustered
WHERE TableName = #TableName
FOR xml path('') );
--Create DROP INDEX command for the indexes on the table if there are any
SELECT #DropCommand2 = ( SELECT 'DROP INDEX [' + IndexName + '] ON dbo.['+ TableName +'];'
FROM #AllNonClustered
WHERE TableName = #TableName
FOR xml path('') );
IF (#DropCommand1 IS NULL AND #DropCommand2 IS NULL) PRINT 'No action taken, zero indexes found on table ' + #TableName
IF #DropCommand1 IS NOT NULL EXEC sp_executesql #DropCommand1
IF #DropCommand2 IS NOT NULL EXEC sp_executesql #DropCommand2
DROP TABLE IF EXISTS #AllClustered
DROP TABLE IF EXISTS #AllNonClustered
END
GO
I cycle through the specific tables in my DB which I want to drop indexes on using a loop, and I drop the indexes by calling this proc with the table name, and recreate better ones right after. This way, only one table at a time has no indexes.
The reason I do this and the number of tables I do it on would make your head spin, but I definitely needed a proc like this!
SELECT 'DROP INDEX [' + IX.NAME + '] ON ' + OBJECT_NAME(IX.OBJECT_ID) + '; '
FROM SYS.INDEXES IX
JOIN SYS.OBJECTS O ON IX.OBJECT_ID = O.OBJECT_ID
INNER JOIN SYS.SCHEMAS S ON O.SCHEMA_ID = S.SCHEMA_ID
WHERE
IX.NAME IS NOT NULL
AND O.TYPE <> 'S'
AND IS_PRIMARY_KEY <> 1
AND INDEX_ID > 0
AND S.NAME != 'SYS' AND S.NAME!= 'SYS' AND IS_UNIQUE_CONSTRAINT = 0
Modify conditions according to your needs
If u want to delete PK constraints, you will get this message if you try to drop index:
An explicit DROP INDEX is not allowed on index... It is being used for PRIMARY KEY constraint enforcement.
Then, use this...
SELECT 'ALTER TABLE [' + O.NAME + '] DROP CONSTRAINT ' + IX.NAME + '; '
FROM SYS.INDEXES IX
JOIN SYS.OBJECTS O ON IX.OBJECT_ID = O.OBJECT_ID
INNER JOIN SYS.SCHEMAS S ON O.SCHEMA_ID = S.SCHEMA_ID
WHERE
IX.NAME IS NOT NULL
AND O.TYPE <> 'S'
AND INDEX_ID > 0
AND S.NAME != 'SYS' AND S.NAME!= 'SYS'