T-SQL to modify a field in any database where it exists - sql

So I have about 150 SQL Server 2005 databases all mostly copies of each other. Each one has a table called bvc_User that has a field called PasswordChanged. For now, PasswordChanged is a nullable bit field.
I want to change all instances of it to a non-nullable bit field with a default value of 0 and set any NULL values currently in the table data to 1 (yes, I don't want them set to the default of 0).
I can probably do the update of the NULL setting and the corresponding update of the already existing database values separately, but my primary question is, how can I loop through all the databases on a server, look for the bvc_User table (not all DB's on the server have it) and if it exists, alter the PasswordChanged field?

You can use sp_msforeachdb to run a command on all database. Notice that I check for the existence of the table prior to actually using it.
sp_msforeachdb '
If Exists(Select 1
From [?].Information_Schema.Tables
Where Table_Name = ''bvc_User'')
Update [?].dbo.bvc_User
Set PasswordChanged = 1
Where PasswordChanged Is NULL'

Three pieces: get the db names, loop through them, execute dynamic sql:
DECLARE #dbNames TABLE (Id int IDENTITY, name nvarchar(50))
INSERT INTO #dbNames
SELECT name FROM sysdatabases
DECLARE #statement nvarchar(540)
DECLARE #dbName nvarchar(50)
DECLARE #maxId int
SELECT #maxId = MAX(Id) FROM #dbNames
DECLARE #i int
SET #i = 1
WHILE #i < #maxId
BEGIN
SEt #dbName = 'dbName'
SET #statement = 'USE ' + #dbName + CHAR(13) + '--execute table specific alter table logic here'
EXEC sp_ExecuteSql #statement
SET #i = #i + 1
END
HTH.

Related

How do I insert values in an idenity column over a linkedServer [duplicate]

I want to use a stored procedure to copy a table from my test database to a linked server with the same ID's / Identity but I can't get it to work..
I've set the IDENTITY_INSERT to ON but it still complains about the ID column.
Here's my procedure:
CREATE PROCEDURE [dbo].[TEST2PROD_CopyUIDataSServer]
AS Begin
declare #sql nvarchar(max)
-- First truncate target table
set #sql = 'EXEC [LINKEDSERVER].tempdb.sys.sp_sqlexec' + char(39)+ 'TRUNCATE Table [ProductManager].dbo.[UIData]' + char(39)+ ';'
---- SET IDENTITY_INSERT ON
set #sql = #sql + 'EXEC [LINKEDSERVER].tempdb.sys.sp_sqlexec' + char(39)+ 'SET IDENTITY_INSERT [ProductManager].[dbo].[UIData] ON' + char(39)+ ';'
---- INSERT UIDATA records from DB1 into linked server DB2
set #sql = #sql + 'WITH TestData as (SELECT * from ProductManager.dbo.UIData UID)' + NCHAR(13)+ 'INSERT INTO [LINKEDSERVER].[ProductManager].[dbo].[UIData]' + NCHAR(13) + 'select * from TestData;'
print #sql
exec (#sql)
end
But when I execute the SP it gives me the following error:
The OLE DB provider "SQLNCLI10" for linked server .... could not INSERT INTO table "[LINKEDSERVER].[ProductManager].[dbo].[UIData]" because of column "Id". The user did not have permission to write to the column.
Linked server properties RPC and RPC out are set to true. I hope someboy can help me out here?
UPDATE: I decided to pull things apart, first I copy the data from the local server to the linked server in a TEMP_TABLE where I don't have to deal with IDENTITY issues.
Then I wrote a stored procedure on the linked / remote server, since I'm not using SELECT * but specify the column list. Chances are this will work from the local server in an SP too but I don't have the time or interest to check it out yet..
USE [ProductManager]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[TEST2PROD_CopyBaseTables]
AS BEGIN
DECLARE #DestTable VARCHAR(50)
DECLARE #DestPath VARCHAR(50)
DECLARE #SrceTable VARCHAR(255)
declare #sql nvarchar(max)
DECLARE #columnList varchar(max)
DECLARE #err int
Begin TRY
declare #comma_delimited_list varchar(4000)
--- FIRST TRY WITH ONE TABLE, EXTENDABLE...
set #comma_delimited_list = 'UIData'
declare #cursor cursor
set #cursor = cursor static for
select * from dbo.Split(#comma_delimited_list,',') a
declare #naam varchar(50)
open #cursor
while 1=1 begin
fetch next from #cursor into #DestTable
if ##fetch_status <> 0 break
--Create tablenames
SET #SrceTable = '[ProductManager].[dbo].TEMP_' + #DestTable
SET #DestPath = '[ProductManager].[dbo].'+ #DestTable
print #srceTable;
print #DestTable;
--Truncate target table
set #sql ='TRUNCATE TABLE '+ #DestPath + ';'
--Insert statement needs column names
set #columnList =''
SELECT #columnList = coalesce(#columnList + '[' + name + '],','') FROM sys.columns Where OBJECT_NAME(OBJECT_ID) = #DestTable
if RIGHT(RTRIM(#columnList),1) = ','
begin
SET #columnList = LEFT(#columnList, LEN(#columnList) - 1)
end
--Transfer data from source table 2 destination
set #sql = #sql + ' SET IDENTITY_INSERT ' + #DestPath + ' ON;' + ' INSERT INTO ' + #DestPath + '(' + #columnList + ') SELECT ' + #columnList + ' FROM ' + #SrceTable
print #sql;
exec (#sql)
end
-- not strictly necessary w/ cursor variables since the will go out of scope like a normal var
close #cursor
deallocate #cursor
End Try
Begin Catch
declare #ErrorMsg nvarchar(MAX);
select #ErrorMsg = ERROR_MESSAGE();
SELECT #err = ##error IF #err <> 0 Return #err
end Catch
END
IDENTITY_INSERT doesn't work with linked servers AFAIK, unless you execute dynamic SQL that includes the SET IDENTITY_INSERT in the batch or have some code (Stored Proc for instance) on the remote server which does that for you.
The IDENTITY_INSERT is per-session (see MSDN) and when you use the remote server this will probably be in a different session from your statement executed via [LINKEDSERVER].tempdb.sys.sp_sqlexec, which causes it to fail as you see it happening.
You can insert an identity value into a table with an identity column on a linked server with the "SWITCH TO" trick.
If you haven't used the "SWITCH TO" trick to add and remove identity on a column, it's very quick, even on large tables!
Conceptually you simply create a new SCHEMA exactly like the table you are wanting to INSERT to without the identity defined. Then switch the table to that SCHEMA and do your INSERT. Then switch back to the SCHEMA with the identity defined.
The sample below has been tested on a linked server in AZURE.
All the caveats of using "SWITCH TO" apply (indexes must be the same, drop and recreate foreign keys, etc)
To test, you can run the full script below on an Linked Azure SQL Server database. You'll need to do a find/replace with [LINKED_SERVER_NAME] and [DATABASE_NAME], replacing with your values. On a non-Azure DB you may need to add "ON PRIMARY" to the table creations.
--Let's setup the example by creating a table with an IDENTITY column on the Linked Server
EXEC('
CREATE TABLE [DATABASE_NAME].[dbo].[Example_Table](
[ID] [int] IDENTITY(1,1) NOT NULL,
[Name] [nchar](10) NULL
)
'
) AT [LINKED_SERVER_NAME]
--INSERT some data into the table
INSERT INTO [LINKED_SERVER_NAME].[DATABASE_NAME].[dbo].[Example_Table] ([Name]) VALUES ('Travis')
INSERT INTO [LINKED_SERVER_NAME].[DATABASE_NAME].[dbo].[Example_Table] ([Name]) VALUES ('Mike')
-- Looks good
SELECT * FROM [LINKED_SERVER_NAME].[DATABASE_NAME].[dbo].[Example_Table]
GO
-- Create a TABLE with an identical schema, without the identity defined
EXEC('
CREATE TABLE [DATABASE_NAME].[dbo].[Example_Table_temp](
[ID] [int] NOT NULL,
[Name] [nchar](10) NULL
)
'
) AT [LINKED_SERVER_NAME]
--Now Use the "SWITCH TO" to move the data to the new table
EXEC('
ALTER TABLE [DATABASE_NAME].[dbo].[Example_Table] SWITCH TO [DATABASE_NAME].[dbo].[Example_Table_temp]
'
) AT [LINKED_SERVER_NAME]
--Drop the old table (It should now be empty, but you may want to verify that if you are unsure here)
EXEC('
DROP TABLE [DATABASE_NAME].[dbo].[Example_Table]
'
) AT [LINKED_SERVER_NAME]
--Rename the new table back to the old table name
-- NOTE the lack of database and owner identifiers in the new name
-- NOTE the use of double single qoutes (ESCAPED single quotes)
EXEC('USE [DATABASE_NAME];
EXEC sp_rename ''[DATABASE_NAME].[dbo].Example_Table_temp'',''Example_Table''
'
) AT [LINKED_SERVER_NAME]
-- Now do your IDENTITY INSERTs !!!!
INSERT INTO [LINKED_SERVER_NAME].[DATABASE_NAME].[dbo].[Example_Table] (ID,[Name]) VALUES (888,'Travis')
INSERT INTO [LINKED_SERVER_NAME].[DATABASE_NAME].[dbo].[Example_Table] (ID,[Name]) VALUES (999,'Mike')
--Verify they got put in
SELECT * FROM [LINKED_SERVER_NAME].[DATABASE_NAME].[dbo].[Example_Table]
--Now let's switch it back to our SCHEMA with an IDENTITY
EXEC('
CREATE TABLE [DATABASE_NAME].[dbo].[Example_Table_temp](
[ID] [int] IDENTITY(1,1) NOT NULL,
[Name] [nchar](10) NULL
)
ALTER TABLE [DATABASE_NAME].[dbo].[Example_Table] SWITCH TO [DATABASE_NAME].[dbo].[Example_Table_temp]
DROP TABLE [DATABASE_NAME].[dbo].[Example_Table]
EXEC sp_rename ''[DATABASE_NAME].[dbo].Example_Table_temp'',''Example_Table''
'
) AT [LINKED_SERVER_NAME]
--Data is still there
SELECT * FROM [LINKED_SERVER_NAME].[DATABASE_NAME].[dbo].[Example_Table]
GO
-- And note you can no longer INSERT the IDENTITY
INSERT INTO [LINKED_SERVER_NAME].[DATABASE_NAME].[dbo].[Example_Table] (ID,[Name]) VALUES (45,'Travis')
GO
You need to execute dynamic query Example :
exec [LINKSERVERNAME].[DATABASENAME].[SCHEMANAME].sp_executesql N'Your Query'
If any column set identity the you need to set SET IDENTITY_INSERT TargetTable ON and need to specified the column name. Example:
SET IDENTITY_INSERT TargetTable ON;
INSERT INTO TargetTable(Col1, Col2, Col3)
SELECT Col1, Col2, Col3 FROM SourceTable;
SET IDENTITY_INSERT TargetTable OFF;

SQL Server - Select columns that meet certain conditions?

My COLUMNS can contain only three values or var chars - economy, basic, luxury. I want to select a ROW and display only those COLUMNS which contain luxury. The problem is that there are many such columns - about 50. I don't want to type the names of all those columns in my select query. Is there a shorter and simpler alternative to this ? Which query should I use ?
I am thinking of something like this (this is a FAKE query) -
#declare Column_Name varchar(30)
select Column_Name where Column_Value = 'luxury'
from ATable
where rowId = 'row 5';
Table structure -
rowId | Column1 | Column2 | Column3.....
I've created a stored procedure for you.
This procedure examines the MSSQL meta to build a dynamic SQL string that returns a result containing column names N and their values V, and the corresponding row key K from which that value was retrieved, for a specified table.
When this is executed, the results stored in a global temporary table called ##ColumnsByValue, which can then be queried directly.
Create the GetColumnsByValue stored procedure, by executing this script:
-- =============================================
-- Author: Ben Roberts (sepster#internode.on.net)
-- Create date: 22 Mar 2013
-- Description: Returns the names of columns that contain the specified value, for a given row
-- =============================================
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
IF OBJECT_ID ( 'dbo.GetColumnsByValue', 'P' ) IS NOT NULL
DROP PROCEDURE dbo.GetColumnsByValue;
GO
CREATE PROCEDURE dbo.GetColumnsByValue
-- Add the parameters for the stored procedure here
#idColumn sysname,
#valueToFind nvarchar(255),
#dbName sysname,
#tableName sysname,
#schemaName sysname,
#debugMode int = 0
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from interfering with SELECT statements.
SET NOCOUNT ON;
DECLARE #SQL nvarchar(max);
DECLARE #SQLUnion nvarchar(max);
DECLARE #colName sysname;
DECLARE #dbContext nvarchar(256);
DECLARE #Union nvarchar(10);
SELECT #dbContext = #dbName + '.' + #schemaName + '.sp_executeSQL';
SELECT #SQLUnion = '';
SELECT #Union = '';
IF OBJECT_ID ( 'tempdb..##GetColumnsByValueIgnoreList') IS NULL -- no columns to ingore have been specified, need to create an empty list.
BEGIN
CREATE TABLE ##GetColumnsByValueIgnoreList (column_name nvarchar(255));
END
DECLARE DBcursor CURSOR FOR
SELECT
COLUMN_NAME
FROM
INFORMATION_SCHEMA.COLUMNS
WHERE
TABLE_NAME = #tableName
AND
TABLE_SCHEMA = #schemaName;
OPEN DBcursor;
FETCH DBcursor INTO #colName;
WHILE (##FETCH_STATUS = 0)
BEGIN
IF (
#colName != #idColumn
AND
#colName NOT IN (SELECT column_name FROM ##GetColumnsByValueIgnoreList)
)
BEGIN
SELECT #SQL = 'SELECT '+#idColumn+' as K, '''+#colName+''' as N, ' +#colName+ ' as V FROM ' + #dbName + '.' + #schemaName + '.' + #tableName;
--PRINT #SQL;
SELECT #SQLUnion = #SQL + #Union + #SQLUnion;
SELECT #Union = ' UNION ';
END
FETCH DBcursor INTO #colName;
END; -- while
CLOSE DBcursor; DEALLOCATE DBcursor;
IF (#debugMode != 0)
BEGIN
PRINT #SQLUnion;
PRINT #dbContext;
END
ELSE
BEGIN
-- Delete the temp table if it has already been created.
IF OBJECT_ID ('tempdb..##ColumnsByValue') IS NOT NULL
BEGIN
DROP TABLE ##ColumnsByValue
END
-- Create a new temp table
CREATE TABLE ##ColumnsByValue (
K nvarchar(255), -- Key
N nvarchar(255), -- Column Name
V nvarchar(255) -- Column Value
)
-- Populate it with the results from our dynamically generated SQL.
INSERT INTO ##ColumnsByValue EXEC #dbContext #SQLUnion;
END
END
GO
The SP takes several inputs as parameters, these are explained in the following code.
Note also I've provided a mechanism to add an "ignore list" as an input:
This allows you to list any column names that should not be included
in the results.
You do NOT need to add the columnn that you're using as your key, ie the row_id from your example structure.
You MUST include other columns that are not varchar as
these will cause an error (as the SP just does a varchar comparison
on all columns it looks at).
This is done via a temp table that you must create/populate
Your example table structure suggests
the table contains only columns of interest, so this may not apply to
you.
I've included example code for how to do this (but only do this if you need to):
IF OBJECT_ID ( 'tempdb..##GetColumnsByValueIgnoreList') IS NOT NULL
BEGIN
DROP TABLE ##GetColumnsByValueIgnoreList;
END
CREATE TABLE ##GetColumnsByValueIgnoreList (column_name nvarchar(255));
INSERT INTO ##GetColumnsByValueIgnoreList VALUES ('a_column');
INSERT INTO ##GetColumnsByValueIgnoreList VALUES ('another_column');
INSERT INTO ##GetColumnsByValueIgnoreList VALUES ('yet_another_column');
Now, to fire off the procedure that build your temp table of results, use the following code (and modify as appropriate, of course).
-- Build the ##ColumnsByValue table
EXEC dbo.GetColumnsByValue
#idColumn = 'row_id', -- The name of the column that contains your row ID (eg probably your PK column)
#dbName = 'your_db_name',
#tableName = 'your_table_name',
#schemaName = 'dbo',
#debugMode = 0 -- Set this to 1 if you just want a print out of the SQL used to build the temp table, to 0 if you want the temp table populated
This leaves you with ##ColumnsByValue, on which you can perform whatever search you need, eg:
select * from ##ColumnsByValue WHERE v = 'luxury' and k = 5 --some_row_id
You'd need to re-execute the stored procedure (and if relevant, create/modify the ignore list table prior to it) for each table you want to examine.
A concern with this approach is the nvarchar length might get exceeded in your case. You'd prob. need to use different datatype, reduce the column name lengths etc. Or break it up into sub-steps and union the results together to get the resultset you're after.
Another concern I have is that this is complete overkill for your particular scenario, where a one-off script-to-query-window will give you the basis of what you need, then some clever text editing in eg Notepad++ will get you all the way there... and hence this problem will likely (and quite reasonably) put you off doing it this way! But it is a good general-case question, and so deserves an answer for anyone interested in future ;-)

How can I spot in what database is a stored procedure with name 'myStoredProcedure'?

There are bunch of databases to the SQL server I am connected.
How should I query the sysobjects in order to spot in what database a stored procedure with name 'myStoredProcedure' is located ?
The query should return the database name.
Thanks
I know you are not asking for this, but I'd really download RedGate's Sql Search add-in for SSMS and use that. It allows you to find any object (proc, table, view, column, etc) on any database easily.
And it's free!
I'd give this a try:
CREATE TABLE ##DatabaseList
(
DatabaseName varchar(50)
)
EXECUTE SP_MSForEachDB 'USE [?]; INSERT INTO ##DatabaseList SELECT DB_NAME() FROM [sys].[objects] WHERE name = "MyStoredProcedure" AND type_desc = "SQL_STORED_PROCEDURE"'
SELECT * FROM ##DatabaseList
DROP TABLE ##DatabaseList
That's using the undocumented/ unsupported system stored procedure SP_MSForEachDb and writing any hits to a global temp table, then outputting the contents to the Results window before dropping the table. If you just need to know which database (or databases - there may of course be more than one) has an appropriately named SP, this should do it. If you want to use the output elsewhere as a parameter, it may take a little more work.
By the way, I'm only learning this stuff myself over the last few months so if anyone can critique the above and suggest a better way to go at it I'm happy to receive feedback. Equally, I can answer any further questions posted here to the best of my ability.
Cheers
So out of curiosity I decided to try write this myself, especially since ADG mentioned his solution was using an unsupported, undocumented procedure. This could also be expanded to take a 2nd parameter so where it checks the type = P (stored Proc) you could probably change it to look for other things like views / tables etc.
My solution is a bit long but here goes:
CREATE PROCEDURE spFindProceduresInDatabases
(
#ProcedureName NVARCHAR(99)
)
AS
BEGIN
-- Get all the database names and put them into a table
DECLARE #Db TABLE (DatabaseName Varchar(99))
INSERT INTO #Db SELECT name FROM Sys.databases
-- Declare a table to hold our results
DECLARE #results TABLE (DatabaseName VARCHAR(99))
-- Make a Loop
-- Declare a variable to be incremented
DECLARE #count INT
SET #count = 0
-- Declare the end condition
DECLARE #endCount INT
SELECT #endCount = COUNT(*) FROM #Db
-- Loop through the databases
WHILE (#count < #endCount )
BEGIN
-- Get the database we are going to look into
DECLARE #dbWeAreChecking VARCHAR(99)
SELECT TOP 1 #dbWeAreChecking = DatabaseName FROM #Db
DELETE FROM #Db WHERE DatabaseName = #dbWeAreChecking
-- Create and execute our query
DECLARE #Query NVARCHAR(3000)
SET #Query = N'SELECT #outParam = COUNT(*) FROM '+#dbWeAreChecking+'.sys.sysobjects WHERE type = ''P'' and name = #ProcedureName'
Declare #outParam INT
print (#Query)
DECLARE #ParmDefinition NVARCHAR(500)
DECLARE #IntVariable INT
SET #ParmDefinition = N'#ProcedureName VARCHAR(99),#outParam INT OUTPUT'
SET #IntVariable = 35
EXECUTE sp_executesql
#Query ,
#ParmDefinition,
#ProcedureName,
#outParam = #outParam OUTPUT
-- If we have a result insert it into the results table
If (#outParam > 0)
BEGIN
INSERT INTO #results(DatabaseName) VALUES(#dbWeAreChecking)
END
-- Increment the counter
SET #count = (#count + 1)
END
-- SELECT ALL OF THE THINGS!!!
SELECT * FROM #results
END

SQL Server Multi-Step Stored Procedure

I've got a software suite that is based off of multiple libraries where:
1 library = 1 SQL Database.
Different users can have different access to different libraries.
In addition, the databases are named in a specific manner to help identify which are "mine" and which aren't.
I'd like to create a stored procedure that takes a variable called #UserName and returns the databases that have a name starting with MYDB, where #UserName is found in a table USERS.
I'm figuring that I'll start with EXEC sp_databases, but I'm unsure how to continue.
What I need to know is:
How do I iterate the results of sp_databases to pull out just the databases that have a name matching my pattern?
How do I then check for #UserName in the [USER NAME] column of the USERS table of each database returned from #1?
I'm guessing it has something to do with temp tables and cursors, but I'm not really sure where to start.
Any help?
Thanks!
Here is some proof of concept code to show you an approach. sys.databases contains a more accessible list of databases. You'll pretty much have to use dynamic sql at some point though.
CREATE PROCEDURE MyDBs #userName VARCHAR(255)
AS
BEGIN
DECLARE #max INT
DECLARE #i INT
DECLARE #sql VARCHAR(500)
CREATE TABLE #SQL
(
rid int identity primary key clustered,
query varchar(500)
)
INSERT INTO #SQL(query)
SELECT 'SELECT * FROM ['+ name '+].USERS WHERE username = #UserName'
FROM master.sys.databases
WHERE NAME LIKE '%yourpattern%'
SELECT #max = ##rowcount, #i = 1
WHILE #i <= #max
BEGIN
SELECT #sql = query FROM #sql WHERE rid = #i
EXEC #sql
SET #i = #i + 1
END
DROP TABLE #SQL
For 1, just look at the sp_databases code, copy it and modify it to your needs. For Example (see last 2 conditions of where clause. This is the actual code of the sp_databases stored proc. You can look at it on the master db):
declare #UserName varchar(50)='someuser'
select
DATABASE_NAME = db_name(s_mf.database_id),
DATABASE_SIZE = convert(int,
case -- more than 2TB(maxint) worth of pages (by 8K each) can not fit an int...
when convert(bigint, sum(s_mf.size)) >= 268435456
then null
else sum(s_mf.size)*8 -- Convert from 8192 byte pages to Kb
end),
REMARKS = convert(varchar(254),null)
from
sys.master_files s_mf
where
s_mf.state = 0 and -- ONLINE
has_dbaccess(db_name(s_mf.database_id)) = 1 and
--db_name(s_mf.database_id) like '%'+#UserName+'%' and exists -- you may or may not want to leave this condition here. You'll figure out what condition to use
(select 1 from databasename.dbo.Users where [UserName]=#UserName)
group by s_mf.database_id
order by 1

Is there a way to select a database from a variable?

Is there a way to select a database from a variable?
Declare #bob as varchar(50);
Set #bob = 'SweetDB';
GO
USE #bob
Unfortunately, no.
Unless you can execute the rest of your batch as dynamic SQL.
Using execute to dynamically execute SQL will change the context for the scope of the execute statement, but will not leave a lasting effect on the scope you execute the execute statement from.
In other words, this:
DECLARE #db VARCHAR(100)
SET #db = 'SweetDB'
EXECUTE('use ' + #db)
Will not set the current database permanently, but if you altered the above code like this:
DECLARE #db VARCHAR(100)
SET #db = 'SweetDB'
EXECUTE('use ' + #db + ';select * from sysobjects')
select * from sysobjects
Then the result of those two queries will be different (assuming you're not in SweetDB already), since the first select, executed inside execute is executing in SweetDB, but the second isn't.
declare #NewDB varchar(50)
set #NewDB = 'NewDB'
execute('use ' + #NewDB)
#TempTables will presist across GOs
you can create the table in the first batch, insert/select data as necessary in that or any following batch.
here is some sample syntax:
CREATE TABLE #YourTableName
(
col1 int not null primary key identity(1,1)
,col2 varchar(10)
)