Should I delete Hypothetical Indexes? - tsql

I have noticed that Hypothetical indexes exist in a certain database. I have searched around and it appeared that this type of indexes are created by Tuning Advisor and are not always deleted.
There are several topics including official documentation of how to clear/delete these indexed, but I was not able to find if these indexes have any impact to the server themselves.
What I have check using the script below is that there is no size information about them:
SELECT OBJECT_NAME(I.[object_id]) AS TableName
,I.[name] AS IndexName
,I.[index_id] AS IndexID
,8 * SUM(A.[used_pages]) AS 'Indexsize(KB)'
FROM [sys].[indexes] AS I
INNER JOIN [sys].[partitions] AS P
ON P.[object_id] = I.[object_id]
AND P.[index_id] = I.[index_id]
INNER JOIN [sys].[allocation_units] AS A
ON A.[container_id] = P.[partition_id]
WHERE I.[is_hypothetical] = 1
GROUP BY I.[object_id]
,I.[index_id]
,I.[name]
ORDER BY 8 * SUM(A.[used_pages]) DESC
and having them, I have decided to check if there are some usage information about them in order to leave these who are often used, but again nothing was return. (I have use the "Existing Indexes Usage Statistics" from this article).
Could anyone tell why keeping these indexes is wrong and if I can define which of them should be kept?

Just USE the database you want to clean and run this:
DECLARE #sql VARCHAR(MAX) = ''
SELECT
#sql = #sql + 'DROP INDEX [' + i.name + '] ON [dbo].[' + t.name + ']' + CHAR(13) + CHAR(10)
FROM
sys.indexes i
INNER JOIN sys.tables t
ON i.object_id = t.object_id
WHERE
i.is_hypothetical = 1
EXECUTE sp_sqlexec #sql

Just delete them, they aren't actually taking up any space or causing any performance hit/benefit at all, but if you're looking at which indexes are defined on a table and forget to exclude hypothetical indexes, it might cause some confusion, also in the unlikely event that you try to create an index with the same name as one of these indexes, it will fail as it already exists.

If you use custom schemas and checked analyzing indexing views, you need some further improvements to the above scripts:
DECLARE #sql VARCHAR(MAX) = ''
SELECT #sql = #sql
+ 'DROP INDEX [' + i.name + ']'
+ 'ON [' + OBJECT_SCHEMA_NAME(t.[object_id]) + '].[' + t.name + ']'
+ CHAR(13) + CHAR(10)
FROM sys.indexes i
INNER JOIN sys.[all_objects] t
ON i.object_id = t.object_id
WHERE i.is_hypothetical = 1
PRINT #sql
EXECUTE sp_sqlexec #sql

Related

How to create PK in thousands of datases where not exist

I have trouble working out how to create a pk on thousands of databases. I have tried using
sp_ineachdb by Aaron Bertrand, but it only works on the first database. I need that the script that finds the PK's to be created, runs against the current Db, which doesn't seem to be the case.
DECLARE #PKScript2 VARCHAR(max)='';
SELECT #PKScript2 += ' ALTER TABLE ' + QUOTENAME(SCHEMA_NAME(obj.SCHEMA_ID))+'.'+
QUOTENAME(obj.name) + ' ADD CONSTRAINT PK_'+ obj.name+
' PRIMARY KEY CLUSTERED (' + QUOTENAME(icol.name) + ')' + CHAR(13)
FROM sys.identity_columns icol INNER JOIN
sys.objects obj on icol.object_id= obj.object_id
WHERE NOT EXISTS (SELECT * FROM sys.key_constraints k
WHERE k.parent_object_id = obj.object_id AND k.type = 'PK')
AND obj.type = 'U'
Order by obj.name
PRINT (#PKScript2);
EXEC [master].[dbo].[sp_ineachdb] #command = #PKScript2, #database_list= '[vosk][vpb][vpbk][vsb][vsh][vst]'
For the sake of the example, I have only used 6 databases.

Rename multiple tables at once

I am needing some sql code that I can use regularly to rename multiple tables at once. The code below is what I would want if I could use the update statement but cannot. Simple as possible.
UPDATE sys.tables
SET name = SUBSTRING(name, CHARINDEX('PP_', name),LEN(name))
WHERE CHARINDEX('PP_', name) > 0
Use sp_rename to rename objects. Below is an example that generates and executes the needed script.
DECLARE #SQL nvarchar(MAX) =
(SELECT 'EXEC sp_rename ''' + QUOTENAME(s.name) + '.' + QUOTENAME(t.name) + ''', ''' + SUBSTRING(t.name, CHARINDEX('PP_', t.name),LEN(t.name)) + ''';'
FROM sys.tables AS t
JOIN sys.schemas AS s ON s.schema_id = t.schema_id
WHERE CHARINDEX('PP_', t.name) > 0
FOR XML PATH(''), TYPE).value('.', 'nvarchar(MAX)');
EXEC sp_executesql #SQL;
To answer the additional question asked in the comments, you can generate a single DROP TABLE statement for these tables using the script below. Note that this method might not work if the tables have foreign key relationships since it doesn't drop in dependency order.
DECLARE #SQL nvarchar(MAX) =
N'DROP TABLE ' + STUFF((SELECT ',' + QUOTENAME(s.name) + '.' + QUOTENAME(t.name)
FROM sys.tables AS t
JOIN sys.schemas AS s ON s.schema_id = t.schema_id
--WHERE CHARINDEX('PP_', t.name) > 0
FOR XML PATH(''), TYPE).value('.', 'nvarchar(MAX)'),1,1,'')
+ N';';

Codefluent SQL Server producer

We are using the SQL Server producer. We want an index for each foreign key column. SQL Server does not put indexes onto foreign key columns automatically. How can we create an index for each foreign key column automatically? Should we code an aspect for this?
CodeFluent Entities does not generate indices by default. However you can set index="true" on a property:
<cf:property name="Customer" index="true" />
And use the SQL Server Template Engine and the template provided by CodeFluent Entities "C:\Program Files (x86)\SoftFluent\CodeFluent\Modeler\Templates\SqlServer\[Template]CreateIndexes.sql" to create indices.
If you don't want to add index=true on each property, you can change the template to automatically include all properties, or you can write an aspect to add the attribute (this is more complex).
Another solution is to use a SQL script:
DECLARE #SQL NVARCHAR(max)
SET #SQL = ''
SELECT #SQL = #SQL +
'IF NOT EXISTS (SELECT * FROM sys.indexes WHERE object_id = OBJECT_ID(N''[dbo].[' + tab.name + ']'') AND name = N''IX_' + cols.name + ''')' + CHAR(13)+CHAR(10) +
'CREATE NONCLUSTERED INDEX [IX_' + cols.name + '] ON [dbo].[' + tab.name + ']( [' + cols.name + '] ASC ) ON [PRIMARY];' + CHAR(13)+CHAR(10)
FROM sys.foreign_keys keys
INNER JOIN sys.foreign_key_columns keyCols ON keys.object_id = keyCols.constraint_object_id
INNER JOIN sys.columns cols ON keyCols.parent_object_id = cols.object_id AND keyCols.parent_column_id = cols.column_id
INNER JOIN sys.tables tab ON keyCols.parent_object_id = tab.object_id
ORDER BY tab.name, cols.name
EXEC(#SQL)

SQL 2008R2: What's the fastest way to do a 'INSERT INTO target <all columns except two> SELECT <all columns> FROM source'

I'm doing a SQL-to-SQL conversion, and have 50+ tables to convert from old (source) to new (target) database. I think the answer is 'there's no really fast way to do this', but I'll ask the question anyways.
Each 'group' has..
Two Source tables: Anywhere from 10 to 700 columns.
These two tables have the same schema, although some columns have different collations.
Target tables: Number of columns = Columns in source tables + 2, as I added start_dt and end_dt.
I can't do a 'INSERT INTO Target SELECT * FROM Source' because of the two extra columns.
Question: What's the fastest way to do a 'INSERT INTO target SELECT FROM source'
Using a view in the designer I don't see a way to select all and have it show all columns, and then just remove the two I don't need. * displays as * instead of all column names.
I'll entertain third party apps on this one.
Thanks.
Jim
In SQL Server Management studio, expand your table. You will see a couple nodes appear below the table name. Columns, Keys, Constraints, etc... Drag the "Column" node in to a query window and all of the columns will be added to the query window. Tack on your 2 extra columns and execute it.
This is still somewhat manual, but it will save you a ton of typing.
Always try to stay away from SELECT *. In SSMS right click on the table/view -> script as -> select. (The wording may not be exact. I am working from memory.) Then you don't have to type out all the fields.
If you mean the speed of getting the data over.
If you can on the destination:
turn off all triggers
drop indexes
change the recovery of the DB to
bulk logged
Do the inserts in batches
What about using SSIS?
How many records are you talking about?
Question was answered here.
DECLARE #source_table sysname
DECLARE #target_table sysname
DECLARE #col_list varchar(max)
DECLARE #sql varchar(max)
--naturally replaced by cursor table loop in final code
SET #source_table = 'dbs'
SET #target_table = 'dbs'
SELECT #col_list = STUFF((
SELECT ', ' + src.name
FROM sys.columns src
INNER JOIN sys.columns trg ON
trg.name = src.name
WHERE
src.object_id = OBJECT_ID(#source_table) AND
trg.object_id = OBJECT_ID(#target_table)
ORDER BY src.column_id
FOR XML PATH('')
), 1, 1, '')
SET #sql = 'INSERT INTO ' + #target_table + ' ( ' + #col_list + ' ) SELECT ' + #col_list + ' FROM ' + #source_table + ' '
EXEC (#sql)
SET #sql = 'INSERT INTO ' + #target_table + ' ( ' + #col_list + ' ) SELECT ' + #col_list + ' FROM ' + #source_table + '_History '
EXEC (#sql)

TSQL: Rowcounts of All Tables In A Server

I am trying to obtain the row count of all tables in a server (NOT a particular database, but all the databases on a server, excluding the msdb, model, master, etc). I don't need any other details to be returned other than the database name, the table name, and row count.
My approach to this problem is to get all the databases in a server and place an id on them, which will be referred to in a while loop (beginning with id one until the maximum id). Then, within the while loop, I obtain the tables and row counts in the matching database ID. My problem is that the USE DatabaseName doesn't seem to allow me to make it dynamic, meaning that I can't store a database name in a variable and use it as the referred to database when performing the table with row count query.
Is there another approach to this that I'm missing (I've looked at many other examples - often using cursors, which seem to be much longer in code and appear to use more resources - this is a relatively fast query even if I use the largest database by tables, except that it doesn't hit the next database and so on), or am I missing something obvious in the code to make this dynamic?
DECLARE #ServerTable TABLE(
DatabaseID INT IDENTITY(1,1),
DatabaseName VARCHAR(50)
)
DECLARE #count INT
DECLARE #start INT = 1
SELECT #count = COUNT(*) FROM sys.databases WHERE name NOT IN ('master','tempdb','model','msdb')
INSERT INTO #ServerTable (DatabaseName)
SELECT name
FROM sys.databases
WHERE name NOT IN ('master','tempdb','model','msdb')
WHILE #start < #count
BEGIN
DECLARE #db VARCHAR(50)
SELECT #db = DatabaseName FROM #ServerTable WHERE DatabaseID = #start
-- This is the problem, as the USE doesn't seem to allow it to be dynamic.
USE #db
GO
SELECT #db
,o.name [Name]
,ddps.row_count [Row Count]
FROM sys.indexes AS i
INNER JOIN sys.objects AS o ON i.OBJECT_ID = o.OBJECT_ID
INNER JOIN sys.dm_db_partition_stats AS ddps ON i.OBJECT_ID = ddps.OBJECT_ID AND i.index_id = ddps.index_id
WHERE i.index_id < 2 AND o.is_ms_shipped = 0
ORDER BY o.NAME
SET #start = #start + 1
END
Note: I tried checking in the sys.objects and sys.indexes to see if I could filter with a database name, but I had no luck.
Update: I tried turning the SELECT into something dynamic with no success (note the below code only shows the change SELECT):
SET #sql = '
SELECT ' + #db + ' [Database]
,o.name [Name]
,ddps.row_count [Row Count]
FROM ' + #db + '.sys.objects
INNER JOIN ' + #db + ' sys.objects AS o ON i.OBJECT_ID = o.OBJECT_ID
INNER JOIN ' + #db + ' sys.dm_db_partition_stats AS ddps ON i.OBJECT_ID = ddps.OBJECT_ID AND i.index_id = ddps.index_id
WHERE i.index_id < 2 AND o.is_ms_shipped = 0
ORDER BY o.NAME'
No, that is essentially the way you do it.
I'm not sure why you think a while loop is faster than a cursor (though this is a common misconception). They are essentially the same thing. I don't always use cursors, but when I do, I use LOCAL FAST_FORWARD - make sure that you do too. See this article for more info:
What impact can different cursor options have?
To reduce the code required for individual tasks like this, you might be interested in the sp_MSforeachdb replacement I wrote (sp_MSforeachdb is a built-in, undocumented and unsupported stored procedure that will repeat a command for every database, but it is not possible to, say, filter out system databases, and it also has a severe bug where it will sometimes halt execution):
Making a more reliable and flexible sp_MSforeachdb
Execute a Command in the Context of Each Database in SQL Server
Another way would be dynamic SQL.
DECLARE #sql NVARCHAR(MAX) = N'';
SELECT #sql += '
SELECT db = N''' + name + '''
,o.name [Name]
,ddps.row_count [Row Count]
FROM ' + QUOTENAME(name) + '.sys.indexes AS i
INNER JOIN ' + QUOTENAME(name) + '.sys.objects AS o
ON i.OBJECT_ID = o.OBJECT_ID
INNER JOIN ' + QUOTENAME(name) + '.sys.dm_db_partition_stats AS ddps
ON i.OBJECT_ID = ddps.OBJECT_ID AND i.index_id = ddps.index_id
WHERE i.index_id < 2 AND o.is_ms_shipped = 0
ORDER BY o.NAME;'
FROM sys.databases
WHERE database_id > 4;
PRINT #sql;
--EXEC sp_executesql #sql;
(The print is there so that you can inspect the command before executing. It may be truncated at 8K if you have a large number of databases, but don't be alarmed - that is just a display issue in SSMS, the command is complete.)
You could also build a #temp table first, and insert into that, so that you have a single resultset to work with, e.g.
CREATE TABLE #x(db SYSNAME, o SYSNAME, rc SYSNAME);
DECLARE #sql NVARCHAR(MAX) = N'';
SELECT #sql += 'INSERT #x(db,o,rc)
SELECT db = N''' + name + '''
,o.name [Name]
,ddps.row_count [Row Count]
FROM ' + QUOTENAME(name) + '.sys.indexes AS i
INNER JOIN ' + QUOTENAME(name) + '.sys.objects AS o
ON i.OBJECT_ID = o.OBJECT_ID
INNER JOIN ' + QUOTENAME(name) + '.sys.dm_db_partition_stats AS ddps
ON i.OBJECT_ID = ddps.OBJECT_ID AND i.index_id = ddps.index_id
WHERE i.index_id < 2 AND o.is_ms_shipped = 0
ORDER BY o.NAME;'
FROM sys.databases
WHERE database_id > 4;
EXEC sp_executesql #sql;
SELECT db, o, rc FROM #x ORDER BY db, o;
Now, don't be fooled into believing this isn't also using a cursor or loop - it is. But it is building the command in a loop as opposed to executing it in a loop.
In terms of you making a dynamic query, rather than use a using, you could do a fully qualified name for your table names, using your selected #db variable.
So it would be 'FROM ' + #db+'.sys.objects' etc.
You would have to check that your DB name is valid (for instance, if you had a name that needed brackets for some reason).