Set IDENTITY_INSERT OFF for all tables - sql

I have a script which creates an entire database and inserts all records to a few dozen tables.
It works great, unless there is some issue during the processing, and a table gets left with IDENTITY_INSERT ON, when the script fails during insertion and before it can be set to OFF again.
When this happens, the script automatically fails when attempting to run it again, with the error "IDENTITY_INSERT is already ON for table xx" as we go into the insertion for the first table.
As a failsafe I would like to make sure that IDENTITY_INSERT is set to OFF for all tables, before running the rest of the processing in the setup script.
As an alternative, we could perhaps close the MS SQL connection and open it again, which, as I understand it, would clear all IDENTITY_INSERT values for the connection session.
What's the best way to do this, and prevent the "already on" errors?

Dynamic SQL:
select 'set identity_insert ['+s.name+'].['+o.name+'] off'
from sys.objects o
inner join sys.schemas s on s.schema_id=o.schema_id
where o.[type]='U'
and exists(select 1 from sys.columns where object_id=o.object_id and is_identity=1)
Then copy & paste the resulting SQL into another query window and run

EXEC sp_MSforeachtable #command1="SET IDENTITY_INSERT ? OFF"

EXEC sp_MSforeachtable #command1="PRINT '?'; SET IDENTITY_INSERT ? OFF",
#whereand = ' AND EXISTS (SELECT 1 FROM sys.columns WHERE object_id = o.id AND is_identity = 1)'
Building on Lynn's answer, in case you're too lazy to perform this in more than one step - this should run on all tables where there is an identity column.
Caveat is only tested in 2012 and sp_MSforeachtable is of course entirely unsupported...

Building on #KevD's answer - It was working fine for disabling but here is more for enabling as well.
To disable all identity inserts where they need to be disabled, use -
EXEC sp_MSforeachtable #command1="PRINT '?'; SET IDENTITY_INSERT ? OFF",
#whereand = ' AND EXISTS (SELECT 1 FROM sys.columns WHERE object_id = o.id
AND is_identity = 1) and o.type = ''U'''
To enable all identity inserts where they need to be enabled, use -
EXEC sp_MSforeachtable #command1="PRINT '?'; SET IDENTITY_INSERT ? ON",
#whereand = ' AND EXISTS (SELECT 1 FROM sys.columns WHERE object_id = o.id
AND is_identity = 1) and o.type = ''U'''
Tested on Sql server 2014 and 2016

I had a similar issue but I'd rather not use undocumented stored procedures in production. To automate this I built on to #John Dewey's answer and put it into a cursor. This iterates over 699 tables in 407 ms.
DECLARE #sql NVARCHAR(500) -- SQL command to execute
DECLARE sql_cursor CURSOR LOCAL FAST_FORWARD FOR
SELECT 'SET identity_insert ['+s.name+'].['+o.name+'] OFF'
FROM sys.objects o
INNER JOIN sys.schemas s on s.schema_id=o.schema_id
WHERE o.[type]='U'
AND EXISTS(SELECT 1 FROM sys.columns WHERE object_id=o.object_id AND is_identity=1)
OPEN sql_cursor
FETCH NEXT FROM sql_cursor INTO #sql
WHILE ##FETCH_STATUS = 0
BEGIN
EXECUTE sp_executesql #sql --> Comment this out to test
-- PRINT #sql --> Uncomment to test or if logging is desired
FETCH NEXT FROM sql_cursor INTO #sql
END
CLOSE sql_cursor
DEALLOCATE sql_cursor
If you are against cursors, it could also be easily transformed into a while loop.

Related

Indexing same table in multiple databases

I have almost 150 databases with all the same tables. I know its bad but I don't have control over it. I'm trying to improve performance with some indexes. I know what the indexes should be but I need to build them on the same tables in every database. Is there a way to do this bsides creating them all separately?
I had a similar situation a while back so I came up with this code. You can use dynamic SQL with sp_MSforeachdb to loop through your databases. I've excluded the system databases below but you can include/exclude databases as you like in that first IF.
This code will check each database for your specific table as well as checking to see if that index already exists on that table. If not, it creates it. I included a RAISERROR to show the progress through the databases in SSMS messages. Just change the table/index names below and update the CREATE INDEX statement as appropriate for you.
DECLARE #command varchar(1000)
SELECT #command = 'IF ''?'' NOT IN(''master'', ''model'', ''msdb'', ''tempdb'')
BEGIN USE ?
EXEC(''
DECLARE #DB VARCHAR(200)
SET #DB = DB_NAME()
RAISERROR (#DB, 10, 1) WITH NOWAIT
IF OBJECT_ID(''''dbo.TableName'''', ''''U'''') IS NOT NULL
BEGIN
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name=''''IX_TableName'''' AND object_id = OBJECT_ID(''''TableName''''))
BEGIN
CREATE INDEX [IX_TableName] ON TableName (indexColumn)
END
END
'') END'
EXEC sp_MSforeachdb #command

Is there a way add auto increment in all tables from a specific database at once?

I am trying to add auto increment in all existing tables in a specific database, and I can do that going through the table design, flagging the identity option, but in this case I have to do it table per table, and there is a lot of tables. Is there a way to do that automatically?
Copied from my comments per request:
I don't believe you're going to find an automated option for doing this for multiple tables. The change script that SSMS creates when you do this in table designer is already doing a ton of work you'd have to recreate for any other solution. Frankly, I wouldn't trust myself to do it as correctly as SSMS.
However, if it were a large enough number of tables, I would create a completely new database with the corrected schema. Ensure that everything in the new database is present and correct. Then, set identity insert to on all tables in the new db, copy the data over, set all the identity inserts off, and then move the new db to the old db with DETACH/ATTACH or BACKUP/RESTORE. In other words, I'd literally rebuild the database from the ground up because old schema had been completely trashed. It would take a lot for me to decide to do that in a production system, however.
I'd only do the DETACH/ATTACH or BACKUP/RESTORE if I absolutely needed to change the database file names or database names. I'd actually prefer to just use the new database as a new database for the application. That would also mean I could swap back to the old database pretty quickly if I ran into trouble.
It can be done by using a 'cursor', but you need to have all the columns that you need to add auto increment to in the same name as ID
Declare #Table nvarchar(50), #script nvarchar(100)
DECLARE cur CURSOR FORWARD_ONLY READ_ONLY LOCAL FOR
SELECT TABLE_SCHEMA + '.' + TABLE_NAME as 'Table' FROM INFORMATION_SCHEMA.TABLES where TABLE_NAME not in ('sysdiagrams') -- You can exclude any table from this process by adding it on the where statement
OPEN cur
FETCH NEXT FROM cur INTO #Table
WHILE ##FETCH_STATUS = 0 BEGIN
-- The sql command to alter a Table and add Identity to it, you can change ID by any column in your tables
set #script = 'Alter Table '+ #Table +' ADD ID INT IDENTITY'
EXEC sp_executesql #script
FETCH NEXT FROM cur INTO #Table
END
CLOSE cur
DEALLOCATE cur
Edit 1 : According to what you asked for in the comment
Declare #Table nvarchar(50), #script nvarchar(100), #primarykey_name nvarchar(20)
DECLARE cur CURSOR FORWARD_ONLY READ_ONLY LOCAL FOR
SELECT TABLE_SCHEMA + '.' + TABLE_NAME as 'Table' FROM INFORMATION_SCHEMA.TABLES where TABLE_NAME not in ('sysdiagrams') -- You can exclude any table from this process by adding it here
OPEN cur
FETCH NEXT FROM cur INTO #Table
WHILE ##FETCH_STATUS = 0 BEGIN
-- Find Primary key for the current Table and set it to #primarykey_name
Set #primarykey_name = (SELECT c.NAME FROM sys.key_constraints kc INNER JOIN sys.index_columns ic ON kc.parent_object_id = ic.object_id and kc.unique_index_id = ic.index_id
INNER JOIN sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id
WHERE kc.name='PK_'+ substring(#Table, 5, LEN(#Table)-4) and kc.type = 'PK')
-- The sql command to alter a Table and add Identity to the primarykey of each table
set #script = 'Alter Table '+ #Table +' ADD ' + #primarykey_name + ' INT IDENTITY'
print #script
--EXEC sp_executesql #script
FETCH NEXT FROM cur INTO #Table
END
CLOSE cur
DEALLOCATE cur

SQL: There is already an object named X in the database

This is my entire code so far
select Region
into #C
from ResearchDB.dbo.tC
where Tier=0
But I already get an error:
There is already an object named '#C' in the database.
Now if I comment the line " into #C" out, it works perfectly. Whats going on??
I use two options: 1 (quick but temporary) - Just hit the Change Connection button in SSMS and reconnect without changing any of the details. This will re-establish your connections dropping your temporary tables in the process. Don't forget to reselect your database as it will default to Master or whatever your default database has been set as.
2. I have a SP that drops all temp tables for your current session ONLY and I add this line
EXEC dbf_DropTempTables
at the top of each piece of code I use that creates temp tables that I made need to rerun multiple times whilst troubleshooting. Here is the SP for reference:
`CREATE PROCEDURE dbf_DropTempTables
AS
-- drop all #temp tables for current session
begin
DECLARE #sql VARCHAR(60),
#name VARCHAR(60)
DECLARE dtt CURSOR
FOR SELECT SUBSTRING(t.name, 1, CHARINDEX('___', t.name) - 1)
FROM tempdb.sys.tables AS t
WHERE t.name LIKE '#%[_][_][_]%'
AND t.[object_id] = OBJECT_ID('tempdb..' + SUBSTRING(t.name, 1, CHARINDEX('___', t.name) - 1))
open dtt
fetch next from dtt into #name
while ##fetch_status <> -1
BEGIN
SELECT #sql = 'DROP TABLE ' + #name
EXEC ( #sql )
fetch next from dtt into #name
END
CLOSE dtt
deallocate dtt
END`

Drop all objects in SQL Server database that belong to different schemas?

Is there a way to drop all objects in a db, with the objects belonging to two different schemas?
I had been previously working with one schema, so I query all objects using:
Select * From sysobjects Where type=...
then dropped everything I using
Drop Table ...
Now that I have introduced another schema, every time I try to drop it says something about I don't have permission or the object does not exist. BUT, if I prefix the object with the [schema.object] it works. I don't know how to automate this, cause I don't know what objects, or which of the two schemas the object will belong to. Anyone know how to drop all objects inside a db, regardless of which schema it belongs to?
(The user used is owner of both schemas, the objects in the DB were created by said user, as well as the user who is removing the objects - which works if the prefix I used IE. Drop Table Schema1.blah)
Use sys.objects in combination with OBJECT_SCHEMA_NAME to build your DROP TABLE statements, review, then copy/paste to execute:
SELECT 'DROP TABLE ' +
QUOTENAME(OBJECT_SCHEMA_NAME(object_id)) + '.' +
QUOTENAME(name) + ';'
FROM sys.objects
WHERE type_desc = 'USER_TABLE';
Or use sys.tables to avoid need of the type_desc filter:
SELECT 'DROP TABLE ' +
QUOTENAME(OBJECT_SCHEMA_NAME(object_id)) + '.' +
QUOTENAME(name) + ';'
FROM sys.tables;
SQL Fiddle
Neither of the other questions seem to have tried to address the all objects part of the question.
I'm amazed you have to roll your own with this - I expected there to be a drop schema blah cascade. Surely every single person who sets up a dev server will have to do this and having to do some meta-programming before being able to do normal programming is seriously horrible. Anyway... rant over!
I started looking at some of these articles as a way to do it by clearing out a schema: There's an old article about doing this, however the tables mentioned on there are now marked as deprecated. I've also looked at the documentation for the new tables to help understand what is going on here.
There's another answer and a great dynamic sql resource it links to.
After looking at all this stuff for a while it just all seemed a bit too messy.
I think the better option is to go for
ALTER DATABASE 'blah' SET SINGLE_USER WITH ROLLBACK IMMEDIATE
drop database 'blah'
create database 'blah'
instead. The extra incantation at the top is basically to force drop the database as mentioned here
It feels a bit wrong but the amount of complexity involved in writing the drop script is a good reason to avoid it I think.
If there seem to be problems with dropping the database I might revisit some of the links and post another answer
try this with sql2012 or above,
this script may help to delete all objects by selected schema
Note: below script for dbo schema for all objects but you may change in very first line #MySchemaName
DECLARE #MySchemaName VARCHAR(50)='dbo', #sql VARCHAR(MAX)='';
DECLARE #SchemaName VARCHAR(255), #ObjectName VARCHAR(255), #ObjectType VARCHAR(255), #ObjectDesc VARCHAR(255), #Category INT;
DECLARE cur CURSOR FOR
SELECT (s.name)SchemaName, (o.name)ObjectName, (o.type)ObjectType,(o.type_desc)ObjectDesc,(so.category)Category
FROM sys.objects o
INNER JOIN sys.schemas s ON o.schema_id = s.schema_id
INNER JOIN sysobjects so ON so.name=o.name
WHERE s.name = #MySchemaName
AND so.category=0
AND o.type IN ('P','PC','U','V','FN','IF','TF','FS','FT','PK','TT')
OPEN cur
FETCH NEXT FROM cur INTO #SchemaName,#ObjectName,#ObjectType,#ObjectDesc,#Category
SET #sql='';
WHILE ##FETCH_STATUS = 0 BEGIN
IF #ObjectType IN('FN', 'IF', 'TF', 'FS', 'FT') SET #sql=#sql+'Drop Function '+#MySchemaName+'.'+#ObjectName+CHAR(13)
IF #ObjectType IN('V') SET #sql=#sql+'Drop View '+#MySchemaName+'.'+#ObjectName+CHAR(13)
IF #ObjectType IN('P') SET #sql=#sql+'Drop Procedure '+#MySchemaName+'.'+#ObjectName+CHAR(13)
IF #ObjectType IN('U') SET #sql=#sql+'Drop Table '+#MySchemaName+'.'+#ObjectName+CHAR(13)
--PRINT #ObjectName + ' | ' + #ObjectType
FETCH NEXT FROM cur INTO #SchemaName,#ObjectName,#ObjectType,#ObjectDesc,#Category
END
CLOSE cur;
DEALLOCATE cur;
SET #sql=#sql+CASE WHEN LEN(#sql)>0 THEN 'Drop Schema '+#MySchemaName+CHAR(13) ELSE '' END
PRINT #sql
EXECUTE (#sql)
I do not know wich version of Sql Server are you using, but assuming that is 2008 or later, maybe the following command will be very useful (check that you can drop ALL TABLES in one simple line):
sp_MSforeachtable "USE DATABASE_NAME DROP TABLE ?"
This script will execute DROP TABLE .... for all tables from database DATABASE_NAME. Is very simple and works perfectly. This command can be used for execute other sql instructions, for example:
sp_MSforeachtable "USE DATABASE_NAME SELECT * FROM ?"

Programmatically replacing linked server references with local database references in SQL Server stored procs?

As a part of a scripted procedure I'm trying to programmatically update references to linked servers in stored procs. We have several references like this:-
SELECT foo, bar
FROM [Server].[Database].dbo.[Table]
Which I would like to translate to:-
SELECT foo, bar
FROM [Database].dbo.[Table]
I would like to do this entirely programmatically in a 'fire and forget' script across several databases.
The idea I have right now is to use metadata to find references to linked tables, read the text of each sp from metadata again, adjust each sp's text, then shove each block of updated text into an exec statement to rebuild 'em one-by-one.
I do wonder whether this will be a humongous pain however, so does anybody have any better ideas? I am open to using powershell if that could provide a better solution.
Thanks in advance!
Hopefully I am understanding the questions, but rather than removing or replacing [Server], I suggest one of two approaches:
Option 1: Don't change any of the
SPs. Instead, update the linked
server configuration to point a
different database, even the local
box.
Option 2: Don't change any of the
SPs. Instead, start using SQL Server
Aliases. SQL Server Aliases are
managed via the CliConfig utility and
are ultimately stored in the
registry. Thus, they can be applied
manually or via .reg script.
Basically, the SQL Server Alias
deciphers the server (along with
port) which is being referenced. If
you update the link server
configuration to reference the SQL
Server Alias rather than a specific
server, you can point your procedures
to different server (even the local server) whenever you
would like.
I hope it helps.
Your approach is the easiest, frankly. I had a similar issue earlier this year
Read sys.sql_modules
REPLACE the linked server text and CREATE -> ALTER
EXEC (#Result)
Here's a script to find all procs/functions/views that reference linked servers on a SQL 2005 instance - might be useful too:
USE master
GO
SET NOCOUNT ON;
--------------------------------------------------------------------
-- Test linked server connections
--------------------------------------------------------------------
BEGIN TRY DROP TABLE #Svrs; END TRY BEGIN CATCH END CATCH;
CREATE TABLE #Svrs
(
[Server] nvarchar(max),
[CanConnectAsDefault] bit
);
DECLARE #ServerName nvarchar(max), #RetVal int;
DECLARE Svrs CURSOR FAST_FORWARD READ_ONLY
FOR
SELECT ServerName = S.name
FROM sys.servers S;
OPEN Svrs;
FETCH NEXT FROM Svrs INTO #ServerName;
WHILE ##FETCH_STATUS = 0
BEGIN
BEGIN TRY
EXEC #RetVal = sys.sp_testlinkedserver #ServerName;
END TRY
BEGIN CATCH
SET #RetVal = sign(##error);
END CATCH;
INSERT INTO #Svrs
SELECT #ServerName
, CASE WHEN #RetVal = 0 THEN 1 ELSE 0 END;
FETCH NEXT FROM Svrs INTO #ServerName;
END;
CLOSE Svrs;
DEALLOCATE Svrs;
SELECT * FROM #Svrs
DROP TABLE #Svrs;
GO
--------------------------------------------------------------------
-- Report linked server references
--------------------------------------------------------------------
BEGIN TRY DROP TABLE #Refs; END TRY BEGIN CATCH END CATCH;
CREATE TABLE #Refs
(
[Server] nvarchar(max),
[Database] nvarchar(max),
[Schema] nvarchar(max),
[Object] nvarchar(max),
[Type] nvarchar(max)
);
DECLARE #DatabaseName nvarchar(max), #ServerName nvarchar(max), #SQL nvarchar(max);
DECLARE Refs CURSOR FAST_FORWARD READ_ONLY
FOR
SELECT DatabaseName = D.name
, ServerName = S.name
-- , ServerProvider = S.provider
-- , ServerSource = S.data_source
FROM sys.databases D
CROSS JOIN sys.servers S
WHERE D.name NOT IN ('master', 'tempdb', 'model', 'msdb', 'ReportServer', 'ReportServerTempDB');
OPEN Refs;
FETCH NEXT FROM Refs INTO #DatabaseName, #ServerName;
WHILE ##FETCH_STATUS = 0
BEGIN
SET #SQL = 'USE [' + #DatabaseName + '];
INSERT INTO #Refs
SELECT DISTINCT ''' + #ServerName + ''', ''' + #DatabaseName + ''', S.[name], O.[name], O.type_desc
FROM syscomments C
INNER JOIN sys.objects O ON C.id = O.[object_id]
LEFT JOIN sys.schemas S ON S.[schema_id] = O.[schema_id]
WHERE C.[TEXT] LIKE ''%[ ,~[( '''']' + #ServerName + '[ .,~])'''' ]%'' ESCAPE ''~'';'
PRINT 'Looking for ' + #ServerName + ' refs in ' + #DatabaseName -- + ': ' + #SQL;
EXEC sp_executesql #SQL;
FETCH NEXT FROM Refs INTO #DatabaseName, #ServerName;
END
CLOSE Refs;
DEALLOCATE Refs;
SELECT * FROM #Refs
DROP TABLE #Refs;
GO
--------------------------------------------------------------------
SET NOCOUNT OFF;
GO
This is not going to be a good idea for a production environment, but if you need a loopback linked server for dev purposes this worked for me:
EXEC sp_addlinkedserver #server = N'name_for_linked_server',
#srvproduct = N' ',
#provider = N'SQLNCLI',
#datasrc = N'name_of_my_sqlserver_instance',
#catalog = N'name_of_database'