Im writing a script to perform a comparison between a base table, and all databases in my server.
My approach for this case is using table variables.
Performig the query harcoded, returns the expected results, but the issue comes when trying to perform a dynamic query for each database.
DECLARE #myTableVariableBase TABLE (Version varchar(10),count int, dbname Varchar(50))
DECLARE #dbname VARCHAR(200)
DECLARE db_cursor CURSOR FOR
SELECT name FROM sys.databases
WHERE name NOT IN ('databases_not_used')
INSERT INTO #myTableVariableBase SELECT Version, count(Version),'Base_Database' FROM [Base_Database].[dbo].[table] GROUP BY Version ORDER BY Version desc
OPEN db_cursor
FETCH NEXT FROM db_cursor INTO #dbname
WHILE ##FETCH_STATUS = 0
BEGIN
DECLARE #myTableVariableTarget TABLE (Version varchar(10),count int, dbname Varchar(50))
EXEC('INSERT INTO #myTableVariableTarget SELECT Version, count(Version), #dbname FROM ' + #dbname + '.[dbo].[Table]')
PRINT #dbname
SELECT * FROM #myTableVariableBase
EXCEPT
SELECT * FROM #myTableVariableTarget
FETCH NEXT FROM db_cursor INTO #dbname
END
CLOSE db_cursor
DEALLOCATE db_cursor
This query returns the message
Msg 1087, Level 15, State 2, Line 1
Must declare the table variable "#myTableVariableTarget".
Msg 137, Level 15, State 2, Line 1
Must declare the scalar variable "#dbname".
Do you have any suggestions?
Thanks in advance!
Table variables have current batch scope. Use a Temporary Table instead, which is visible from nested scopes, like your dynamic query.
Instead of
DECLARE #myTableVariableTarget TABLE (Version varchar(10),count int, dbname Varchar(50))
do
create table #myTableVariableTarget (Version varchar(10),count int, dbname Varchar(50))
As mentioned, you cannot use table variables from a different scope.
But you don't need to. You can just creat one big script that does the whole thing, then execute it.
DECLARE #sql nvarchar(max);
SELECT #sql = STRING_AGG(CAST('
SELECT Version, count(Version), ''Base_Database''
FROM [Base_Database].[dbo].[table]
GROUP BY Version
EXCEPT
SELECT Version, count(Version), ' + QUOTENAME(d.name, '''') + '
FROM ' + QUOTENAME(d.name) + '.[dbo].[Table];
'
AS nvarchar(max)), '
' )
FROM sys.databases d
WHERE d.name NOT IN ('databases_not_used');
EXEC sp_executesql #sql;
Related
Somebody create database with special characters name like 'PRİNCE' long time ago.So we can't change database name anymore.
Because of ' İ ' we can't call database from sys.databases.
select name
from dbo.sysdatabases
where name=cast('PRİNCE' as varchar(100)) COLLATE SQL_Latin1_General_CP1253_CI_AI
it works well
But there is a procedure which we using gives error
'Database 'PRİNCE' does not exist. Make sure that the name is entered correctly.'
DECLARE #dbname VARCHAR(50) -- database name
Declare #strSQL nvarchar(max)
DECLARE c CURSOR FOR
select name from MASTER.dbo.sysdatabases where (name not in('master','model','msdb','tempdb'))
OPEN c;
FETCH NEXT FROM c INTO #dbname;
WHILE ##FETCH_STATUS = 0
begin
select #strSQL='use '+cast(#dbname as varchar(100))COLLATE SQL_Latin1_General_CP1253_CI_AI+' Select Db_name(DB_ID()) as Dbname from sys.database_files ;'
exec sp_executesql #strSQL
fetch next from c into #dbname
end
close c
deallocate c
The problem is your use of varchar throughout and not nvarchar. The specific problem statement is this one:
select #strSQL='use '+cast(#dbname as varchar(100))COLLATE SQL_Latin1_General_CP1253_CI_AI+' Select Db_name(DB_ID()) as Dbname from sys.database_files ;'
I am guessing your database (master I assume) is not in the collation SQL_Latin1_General_CP1253_CI_AI so as soon as the assignment happens, the value is lost.
See, for example, the below:
DECLARE #Database sysname = N'USE ' + 'İ' COLLATE SQL_Latin1_General_CP1253_CI_AI + ';';
SELECT #Database;
This returns USE I; not USE İ;. As object names are a sysname (a synonym of nvarchar(128) NOT NULL) then make sure you use that data type through your code:
USE master;
GO
DECLARE #dbname sysname; -- database name
DECLARE #strSQL nvarchar(max);
DECLARE c CURSOR FOR
SELECT [name]
FROM sys.databases --Don't use the old objects!
WHERE ([name] NOT IN(N'master',N'model',N'msdb',N'tempdb'));
OPEN c;
FETCH NEXT FROM c INTO #dbname;
WHILE ##FETCH_STATUS = 0 BEGIN
SET #strSQL = N'USE ' + QUOTENAME(#dbname) + N'; SELECT DB_NAME(DB_ID()) AS DBName from sys.database_files;'; --Note QUOTENAME as well for proper quoting.
EXEC sys.sp_executesql #strSQL;
FETCH NEXT FROM c INTO #dbname;
END;
CLOSE c;
DEALLOCATE c;
Ideally, I'd get rid of the CURSOR as well, but that's a completely different question.
I wanted to monitor sizes of any databases in an SQL Server instance so I came up with this query that builds a temporary table which delivers what I want
/*DROP TEMP TABLE IF STILL EXISTENT*/
IF OBJECT_ID('tempdb..#sizes') IS NOT NULL
DROP TABLE #sizes;
/*DECLARE CURSOR curs1 to query the names of all relevant databases*/
DECLARE curs1 INSENSITIVE CURSOR FOR
select CAST(name AS varchar(MAX)) from sys.databases WHERE state_desc ='ONLINE' AND name NOT IN ('tempdb', 'master', 'model', 'msdb', 'sysdb', 'tempdb2')
/*Open Cursor*/
OPEN curs1
/*Declare Variable to hold the value of current db*/
declare #dbname varchar(MAX) = 'dummy'
/*Create the temporary table for results*/
CREATE TABLE #sizes (DBname varchar(MAX), physicalname varchar(MAX), TotalSizeMB INT, AvailableSpaceMB INT)
/*Query the datafile statistics for every database dynamically*/
WHILE(1=1)
BEGIN
FETCH NEXT FROM curs1 INTO #dbname
IF ##FETCH_STATUS != 0
BREAK;
EXECUTE('USE ' + #dbname + ';INSERT INTO #sizes
SELECT DB_NAME(), f.physical_name,
CAST((f.size/128.0) AS DECIMAL(15,2)),
CAST(f.size/128.0 - CAST(FILEPROPERTY(f.name, ''SpaceUsed'') AS int)/128.0 AS DECIMAL(15,2))
FROM sys.database_files f;')
END
Select * from #sizes
/*Cleanup*/
DROP TABLE #sizes
CLOSE curs1
DEALLOCATE curs1
When the cursor fetches a string containing a "-", it splits the string at that point, leaving an incomplete database name, which can not be found. I isolated the EXECUTE function to be the problem. I assume this has to do with the change of of context when changing the database with the use command. But I also tried the datatypes nvarchar, text and ntext (which are not valid for local variables), char and nchar as well as sysname, which is the fieldtype of the sys.databases' "name"-column.
Any suggestions on how to stop this behaviour?
Try escaping the database name:
'USE [' + #dbname + ']; ...
Here is a quick example of how you can do this without using a cursor. It still requires dynamic sql but no cursor. :) I realize you are using FILEPROPERTY which will be NULL here but I tossed this example together quickly. And since I suspect you will probably continue to use the cursor version you already have I didn't refactor that piece.
declare #SQL nvarchar(max) = ''
select #SQL = #SQL +
'SELECT DatabaseName = ''' + name + ''', f.physical_name collate SQL_Latin1_General_CP1_CI_AS,
CAST((f.size/128.0) AS DECIMAL(15,2)),
CAST(f.size/128.0 - CAST(FILEPROPERTY(f.name, ''SpaceUsed'') AS int)/128.0 AS DECIMAL(15,2))
FROM [' + name + '].sys.database_files f union all '
from sys.databases
WHERE state_desc ='ONLINE'
AND name NOT IN ('tempdb', 'master', 'model', 'msdb', 'sysdb', 'tempdb2')
select #SQL = left(#SQL, len(#SQL) - 10)
select #SQL
--Uncomment the next line when you are comfortable the dynamic sql will work
--exec sp_executesql #SQL
I'm trying to iterate over some tables and clear all records.
My code is the following :
DECLARE #table varchar(100)
DECLARE db_cursor CURSOR FOR select name from sys.tables where name like '%cfe_%'
OPEN db_cursor
FETCH NEXT FROM db_cursor INTO #table
WHILE ##FETCH_STATUS = 0
BEGIN
print #table
delete from #table
FETCH NEXT FROM db_cursor INTO #table
END
CLOSE db_cursor
DEALLOCATE db_cursor
But I receive "Must declare the table variable "#table" at the line "delete..."
I can't see the error.
Thank you
You shoud use dynamic query,
DECLARE #table varchar(100)
,#v_str nvarchar(200)
DECLARE db_cursor CURSOR FOR select name from sys.tables where name like '%cfe_%'
OPEN db_cursor
FETCH NEXT FROM db_cursor INTO #table
WHILE ##FETCH_STATUS = 0
BEGIN
print #table
set #v_str = 'delete from '+#table
exec(#v_str)
FETCH NEXT FROM db_cursor INTO #table
END
CLOSE db_cursor
DEALLOCATE db_cursor
You need dynamic delete statement... Try this :
DECLARE #cmd VARCHAR(4000)
DECLARE #table varchar(100)
DECLARE db_cursor CURSOR FOR select name from sys.tables where name like '%cfe_%'
OPEN db_cursor
FETCH NEXT FROM db_cursor INTO #table
WHILE ##FETCH_STATUS = 0
BEGIN
SET #cmd = 'DELETE FROM '+#table
EXEC (#cmd)
FETCH NEXT FROM db_cursor INTO #table
END
CLOSE db_cursor
DEALLOCATE db_cursor
Even better would be to not use a cursor here. Looping in sql is a last resort. Also, your query is not going to do exactly what you think it will because you are using like and wanting to find an underscore. The underscore in a LIKE predicate requires it to be escaped with square brackets. As posted your query will return any table with cfe in the name not cfe_.
Once you are comfortable that the dynamic sql string is what you want you can uncomment it to execute it.
declare #SQL nvarchar(max) = ''
select #SQL = #SQL + 'delete from ' + name + ';'
from sys.tables
where name like '%cfe[_]%'
select #SQL
--exec sp_executesql #SQL
We can also use while loop for this process
DECLARE #Min Int,#max Int
IF Object_id('Tempdb..#DeleteList')IS NOT NULL
DROP TABLE #DeleteList
CREATE TABLE #DeleteList (Id Int Identity,Name varchar(100))
INSERT INTO #DeleteList(Name)
SELECT name FROM sys.tables WHERE CHARINDEX('cfe_',name)>0
SELECT #Min=Min(Id) FROm #DeleteList
SELECT #max=MAX(Id) FROm #DeleteList
While(#Min<=#max)
Begin
Declare #TableName Varchar(50),
#Sql Nvarchar(max)
SELECT #TableName=Name From #DeleteList Where id=#Min
SET #Sql='DELETE From '+#TableName
Exec (#Sql)
SET #Min=#Min+1
END
But if the deleting tables have foreign key refrences it will throw error so that first you need delete records from child and then go to Parent table
You will need to do something like;
EXEC sp_executesql #statement = N'DELETE FROM ' + #table
because currently you are trying to delete from a String variable, not the table named the same as the variable
We suffered some kind of invasion in our SQL Server.
I'm trying to find in every database, in every table, every column the word abortion and cheat.
I can do this with this query, but in a single database.
-- Store results in a local temp table so that. I'm using a
-- local temp table so that I can access it in SP_EXECUTESQL.
create table #tmp
(
db varchar(max),
tbl nvarchar(max),
col nvarchar(max),
val nvarchar(max),
);
declare #db nvarchar(max);
declare #tbl nvarchar(max);
declare #col nvarchar(max);
declare #q nvarchar(max);
declare #search nvarchar(max) = 'abortion';
-- Create a cursor on all columns in the database
declare c cursor for
SELECT
DB_NAME(DB_ID()) as DBName, tbls.TABLE_NAME, cols.COLUMN_NAME
FROM INFORMATION_SCHEMA.TABLES AS tbls
JOIN INFORMATION_SCHEMA.COLUMNS AS cols ON tbls.TABLE_NAME = cols.TABLE_NAME
-- For each table and column pair, see if the search value exists.
open c
fetch next from c into #db, #tbl, #col
while ##FETCH_STATUS = 0
begin
-- Look for the search key in current table column and if found add it to the results.
SET #q = 'INSERT INTO #tmp SELECT ''' +#db+''',''' + #tbl + ''', ''' + #col + ''', ' + #col + ' FROM ' + #tbl + ' WHERE ' + #col + ' LIKE ''%' + #search + '%'''
EXEC SP_EXECUTESQL #q
fetch next from c into #db, #tbl, #col
end
close c
deallocate c
-- Get results
select distinct db,tbl,col from #tmp
-- Remove local temp table.
drop table #tmp
How can I find these strings? The result set should be:
DATABASE | TABLE | COLUMN
I don't need the result ( text field ), and I need to select distinct for tables and columns, because it will be a lot of abortion in the same table/column.
While the use of the undocumented sp_msforeachdb is generally not encouraged, my instinct would be to send your existing code to this procedure like this:
exec sp_MSforeachdb 'USE [?];
-- Store results in a local temp table so that. I'm using a
-- local temp table so that I can access it in SP_EXECUTESQL.
create table #tmp (
db varchar(max) ,
tbl nvarchar(max),
col nvarchar(max),
val nvarchar(max),
);
declare #db nvarchar(max);
declare #tbl nvarchar(max);
declare #col nvarchar(max);
declare #q nvarchar(max);
--------------------------------------------------------------------------------------------
declare #search nvarchar(max) = ''abortion'';
--------------------------------------------------------------------------------------------
-- Create a cursor on all columns in the database
declare c cursor for
SELECT DB_NAME(DB_ID()) as DBName,tbls.TABLE_NAME, cols.COLUMN_NAME FROM INFORMATION_SCHEMA.TABLES AS tbls
JOIN INFORMATION_SCHEMA.COLUMNS AS cols
ON tbls.TABLE_NAME = cols.TABLE_NAME
-- For each table and column pair, see if the search value exists.
open c
fetch next from c into #db, #tbl, #col
while ##FETCH_STATUS = 0
begin
-- Look for the search key in current table column and if found add it to the results.
SET #q = ''INSERT INTO #tmp SELECT '''''' +#db+'''''','''''' + #tbl + '''''', '''''' + #col + '''''', '' + #col + '' FROM '' + #tbl + '' WHERE '' + #col + '' LIKE ''''%'' + #search + ''%''''''
EXEC SP_EXECUTESQL #q
fetch next from c into #db, #tbl, #col
end
close c
deallocate c;'
The only added code here is the first line, for the rest of the code just make sure to replace ' with ''. The ? in USE [?] is a special character meaning the currently active database in the loop sp_MSforeachdb executes.
I m trying to search database values through the views.
I m stuck at the below error.
USE AdventureWorks
GO
--EXEC Customer.sp_FindInViews Stephen, Sales
ALTER PROCEDURE Customer.sp_FindInViews #stringToFind VARCHAR(100), #schema sysname
AS
SET NOCOUNT ON
DECLARE
#ViewName AS nVarChar(128)
, #TmpQuery AS nVarChar(500)
, #Out3 as int
, #sqlCommand VARCHAR(8000)
, #where VARCHAR(8000)
, #columnName sysname
, #cursor VARCHAR(8000)
DECLARE Outer_Cursor CURSOR FOR
SELECT schema_name(schema_id)+'.'+name as "View_Name",schema_id FROM [sys].[all_views]
where schema_id in (#schema)
OPEN Cur_Views
FETCH NEXT FROM Cur_Views INTO #ViewName
WHILE ##Fetch_Status = 0
BEGIN
SET #sqlCommand = 'SELECT * FROM ' + #ViewName + ' WHERE'
SET #where = ''
DECLARE col_cursor CURSOR FOR
SELECT syscolumns.name FROM sys.sysobjects "sysobjects"
INNER JOIN sys.syscolumns "syscolumns"
on syscolumns.id = sysobjects.id
WHERE (sysobjects.type = 'V' and SCHEMA_NAME(sysobjects.uid) + '.' +sysobjects.name = #ViewName)
OPEN col_cursor
FETCH NEXT FROM col_cursor INTO #columnName
WHILE ##FETCH_STATUS = 0
BEGIN
IF #where <> ''
SET #where = #where + ' OR'
---------------------------------------------------------------------------
SET #where = #where + ' ' + #columnName + ' LIKE ''' + #stringToFind + ''''
SET #sqlCommand = #sqlCommand + #where
CREATE TABLE #Data (var varchar)
SELECT #TmpQuery = #sqlCommand
INSERT #Data exec (#TmpQuery)
SELECT #Out3 = var from #Data
PRINT #Out3
DROP TABLE #Data
FETCH NEXT FROM col_cursor INTO #columnName
END
CLOSE col_cursor
DEALLOCATE col_cursor
CLOSE Outer_Cursor
DEALLOCATE Outer_Cursor
END
GO
The code compiles , but it does give the error when executed as below :
EXEC Customer.sp_FindInViews Stephen, Sales
Msg 16915, Level 16, State 1, Procedure sp_FindInViews, Line 19
A cursor with the name 'Outer_Cursor' already exists.
Msg 16905, Level 16, State 1, Procedure sp_FindInViews, Line 22
The cursor is already open.
Msg 16924, Level 16, State 1, Procedure sp_FindInViews, Line 23
Cursorfetch: The number of variables declared in the INTO list must match that of selected columns.
I m not sure , why I m getting this error. I feel i m handling them. Any input on this , would be helpful.
Thanks.
Looks to me that you've changed cursor names. You start by declaring Outer_Cursor then open a cursor called Cur_Views.
Also when you fetch from the cursor you are only putting the cursor values in to 1 variable, in the cursor declaration you list 2 fields (View_Name and schema_id).
DECLARE Outer_Cursor CURSOR FOR
SELECT schema_name(schema_id)+'.'+name as "View_Name",schema_id FROM [sys].[all_views]
where schema_id in (#schema)
OPEN Cur_Views
FETCH NEXT FROM Cur_Views INTO #ViewName
The "cursor is already open" errors occur when you run the procedure a second time becuase the original cursors are still open (as the first attempt errored before being able to close them).
Not sure if it's the answer you've been looking for, but SQLSearch (http://www.red-gate.com/products/sql-development/sql-search/) is an excellent tool for searching databases (of course, you can set it to search views only) and it's free...