I have the following piece of script that loops through multiple identical databases (55 of them).
I am trying to get the database name as a column, but it keeps giving me the master db_name as this is where I execute from. When I change it to something else, then I get that db_name, but never the correct one based on the database in question.
DECLARE #sql NVARCHAR(MAX);
SET #sql = N'';
SELECT #sql = #sql + N'
UNION ALL
SELECT top 1
Account as Account,
DB_NAME() as DatabaseName,
(Select CompnyName from ' + QUOTENAME(name) + '.dbo.OADM)as Entity,
GL.TransId as TransactionId,
isnull(Debit,0) as Debit,
isnull(Credit,0) as Credit,
isnull(Debit,0)- isnull(Credit,0) as Balance,
year(GL.refdate)*10000+month(GL.refdate)*100+day(GL.refdate) as TimePeriod
FROM ' + QUOTENAME(name) + '.dbo.JDT1 GL'
FROM master.sys.databases WHERE
state = 0
and database_id > 8
and name not like '%template%'
and name not like '%staging%'
and name not like '%test%'
SET #sql = STUFF(#sql, 1, 11, '');
EXEC (#sql);
--Print #sql
The rest works great, it is just the db_name that I need added.
Please help.
Thanks,
Wynand
I tried using DB_ID() with and without quotes, but it keeps executing against the master database.
Related
I am trying to aggregate SQL Server Users through LinkdedServers, but am unable to select the database name in my query. I tried using db_name() as ''Database'' In my select statement, but it uses the current database context, and not the database that I am selecting from. I realize that this happens because I am using the "fully qualified" database name, so the database context never actually changes. I am also unable to pull in the cursor value as part of my select statement.
Does anyone have any ideas as to how I can get the database name of the database I am selecting from?
Here is my code:
DECLARE #DatabaseName VARCHAR(30)
DECLARE c1 CURSOR LOCAL FORWARD_ONLY STATIC READ_ONLY FOR
SELECT
Name
FROM
[LinkedServer].master.sys.databases
WHERE
Name NOT IN (
'master',
'model',
'tempdb',
'msdb',
'distribution',
)
OPEN c1
FETCH NEXT FROM c1
INTO #DatabaseName
WHILE ##FETCH_STATUS = 0
BEGIN
EXEC (
'
--INSERT INTO [Gather_Privileged_DBUsers_Sox]
SELECT distinct (dp.name) as ''DatabaseUser'',
s.name as ''ServerName'',
db_name() as ''Database'',
getdate() as ''date''
FROM [LinkedServer].[' + #DatabaseName + '].sys.database_role_members drm
JOIN [LinkedServer].[' + #DatabaseName + '].sys.database_principals dp
ON drm.member_principal_id = dp.principal_id
JOIN [LinkedServer].[' + #DatabaseName + '].sys.database_principals dp2
ON drm.role_principal_id = dp2.principal_id
JOIN [LinkedServer].[master].[sys].[servers] s on 1=1
WHERE
dp2.name in
(''db_owner'',''db_accessadmin'',''db_securityadmin'',''db_ddladmin'')
AND s.server_id = 0
AND dp.name not in (''dbo'')
AND dp.type != ''R''
')
FETCH NEXT FROM c1
INTO #DatabaseName
END
CLOSE c1;
DEALLOCATE c1;
GO
Attempt to use the variable value:
SELECT distinct (dp.name) as ''DatabaseUser'',
s.name as ''ServerName'',
' + #DatabaseName + ' as ''Database'',
getdate() as ''date''
When I do this, I get the following error: Msg 207, Level 16, State 1, Line 5
Invalid column name 'myTestDatabase'.
How can I turn the variable into a string in this situation?
You should get out of the habit of wrapping column aliases in single quotes. They are NOT string literals and it makes your code much harder to read. It also causes lots of anguish with dynamic sql.
Here is an example of how you would capture the value in your varaible and build this string without those extra single quotes.
declare #SQL nvarchar(max) =
'SELECT distinct (dp.name) as DatabaseUser,
s.name as ServerName,
''' + #DatabaseName + ''' as Database,
getdate() as [date]'
I have (for testing purposes) many dbs with the same schema (=same tables and columns basically) on a sql server 2008 r2 instance.
i would like a query like
SELECT COUNT(*) FROM CUSTOMERS
on all DBs on the instance. I would like to have as result 2 columns:
1 - the DB Name
2 - the value of COUNT(*)
Example:
DBName // COUNT (*)
TestDB1 // 4
MyDB // 5
etc...
Note: i assume that CUSTOMERS table exists in all dbs (except master).
Try this one -
SET NOCOUNT ON;
IF OBJECT_ID (N'tempdb.dbo.#temp') IS NOT NULL
DROP TABLE #temp
CREATE TABLE #temp
(
[COUNT] INT
, DB VARCHAR(50)
)
DECLARE #TableName NVARCHAR(50)
SELECT #TableName = '[dbo].[CUSTOMERS]'
DECLARE #SQL NVARCHAR(MAX)
SELECT #SQL = STUFF((
SELECT CHAR(13) + 'SELECT ' + QUOTENAME(name, '''') + ', COUNT(1) FROM ' + QUOTENAME(name) + '.' + QUOTENAME(#TableName)
FROM sys.databases
WHERE OBJECT_ID(QUOTENAME(name) + '.' + QUOTENAME(#TableName)) IS NOT NULL
FOR XML PATH(''), TYPE).value('text()[1]', 'NVARCHAR(MAX)'), 1, 1, '')
INSERT INTO #temp (DB, [COUNT])
EXEC sys.sp_executesql #SQL
SELECT *
FROM #temp t
Output (for example, in AdventureWorks) -
COUNT DB
----------- --------------------------------------------------
19972 AdventureWorks2008R2
19975 AdventureWorks2012
19472 AdventureWorks2008R2_Live
Straight forward query
EXECUTE sp_MSForEachDB
'USE ?; SELECT DB_NAME()AS DBName,
COUNT(1)AS [Count] FROM CUSTOMERS'
This query will show you what you want to see, but will also throw errors for each DB without a table called "CUSTOMERS". You will need to work out a logic to handle that.
Raj
How about something like this:
DECLARE c_db_names CURSOR FOR
SELECT name
FROM sys.databases
WHERE name NOT IN('master', 'tempdb') --might need to exclude more dbs
OPEN c_db_names
FETCH c_db_names INTO #db_name
WHILE ##Fetch_Status = 0
BEGIN
EXEC('
INSERT INTO #report
SELECT
''' + #db_name + '''
,COUNT(*)
FROM ' + #db_name + '..linkfile
')
FETCH c_db_names INTO #db_name
END
CLOSE c_db_names
DEALLOCATE c_db_names
SELECT * FROM #report
declare #userdb_list table (name varchar(4000) not null);
-- fill the db list with custom subset
insert into #userdb_list
select name from sys.databases --can add where condition to filter db names
declare
#curr_userdb varchar(300),
#db_placeholder varchar(300),
#final_db_exec_query varchar(max),
#query varchar(max);
set #query = '' -- <add ur query here>
set #db_placeholder = 'use {db}';
set #curr_userdb = (select min(name) from #userdb_list);
while #curr_userdb is not null
begin
set #final_db_exec_query = replace(#db_placeholder, '{db}', #curr_userdb + ' ' + #query);
exec (#final_db_exec_query);
--print #final_db_exec_query
set #curr_userdb = (select min(name) from #userdb_list where name > #curr_userdb);
end
GO
Solution without cursor - clean and simple
Because I know that a question was just referred to here that asked a slightly different question... if you only want to execute on certain databases, those databases could be stored in some table. Here I stored in a temporary table.
CREATE TABLE #Databases (
DbName varchar(255))
INSERT INTO #Databases (DbName)
Values ('GIS_NewJersey'), ('GIS_Pennsylvania')
DECLARE #command varchar(1000)
SELECT #command = 'Use [' + DbName + '];
Update sde.SAP_Load
SET FullAddress = CONCAT_WS('','', HouseNumber, Street, City, Postal, RegionName)
Update sde.PREMISE
SET FullAddress = CONCAT_WS('', '', HouseNumber, Street, City, Postal, RegionName)
Update sde.PREMISE_GEOCODE
SET FullAddress = CONCAT_WS('', '', HouseNumber, Street, City, Postal, RegionName)'
FROM #Databases
EXEC #command
Running Microsoft SQL Server 11.0.3128
on Windows Server 2012 R2 Essentials
I am attempting to return the name of a specific database based on a supplied variable (batch file that calls SQL script).
The process, in my head, should look something like this:
For each database in instance
Look in the current database
Return databasename if variable is found in column
The code I've been working with so far looks like this:
EXEC dbo.sp_MSForeachdb '
USE [?];
SELECT DB_NAME() AS DBName
UNION SELECT
ColumnName
FROM dbo.Items
WHERE ColumnName =''variable''
'
Problem is, this returns a lot more than I want it to since it returns "null" values for the databases that do not contain "variable" and creates messages for databases not containing "ColumnName".
But I can't seem to figure out how to get the specific info I want without the other stuff. First time poster, please let me know if I can improve the question.
Thanks!
EDIT: Oops, didn't realize at first you were working with mssql and not mysql. The principle below will still work; you'll just need to adjust the syntax a bit and use a user-function to replace group_concat since mssql doesn't have that.
Here's an approach without sp_MSForeachdb. Note that you will want to sanitize the parameters first.
delimiter $$
create procedure FindDatabases
(
in varName varchar(2000),
in tableName varchar(2000),
in columnName varchar(2000)
)
begin
declare selectQuery varchar(2000);
select group_concat(
concat('select ''',
table_schema,
''' as DatabaseName from ',
table_schema,
'.',
tableName,
' where ',
columnName,
' = ''',
varName,
'''')
separator ' union ') as DatabaseNames
from information_schema.tables
where table_name = tableName
into #selectQuery;
prepare preparedSql from #selectQuery;
execute preparedSql;
deallocate prepare preparedSql;
end $$
delimiter ;
Example usage:
call FindDatabases ( 'variable', 'Items', 'ColumnName' )
This procedure generates a sql query for each database with a table name matching the table name supplied, unions them together, and then executes them. Each query in the union returns its database name if the specified table in that database has a column matching the specified name that contains a value that matches the specified variable name. Only databases matching these requirements will be present in the query results, so you don't have to worry about null values in the results.
ADDITIONAL EDIT: As promised, here is a sqlserver version.
create procedure FindDatabases
(
#varName varchar(2000),
#tableName varchar(2000),
#columnName varchar(2000)
)
as
begin
declare #selectQuery nvarchar(2000)
-- first, get a list of database names that contain the specified table
IF OBJECT_ID('tempdb.dbo.#db_temp') IS NOT NULL
DROP TABLE #db_temp
CREATE TABLE #db_temp (DatabaseName SYSNAME)
SELECT #selectQuery = (
SELECT '
USE [' + d.name + '];
INSERT INTO #db_temp (DatabaseName)
SELECT DB_NAME() as DatabaseName
WHERE EXISTS(
SELECT 1
FROM sys.objects
WHERE [object_id] = OBJECT_ID(''' + #tableName + ''')
AND [type] = ''U''
)'
FROM sys.databases d
WHERE d.name NOT IN ('master', 'tempdb', 'model', 'msdb')
AND d.state_desc != 'OFFLINE'
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)')
EXEC sys.sp_executesql #selectQuery
-- use something like mysql's group_concat function to turn that list into a bunch of union all select statements
select
#selectQuery =
(
SELECT LEFT(database_names , LEN(database_names ) - 10) AS database_names
FROM #db_temp AS extern
CROSS APPLY
(
SELECT 'select ''' + DatabaseName + ''' as DatabaseName from ' + DatabaseName + '.dbo.' + #tableName +
' where ' + #columnName + ' = ''' + #varName + '''' + ' union all '
FROM #db_temp AS intern
FOR XML PATH('')
) pre_trimmed (database_names)
GROUP BY database_names
)
drop table #db_temp
-- run those select statements
exec sp_executesql #selectQuery
end
To run it:
exec FindDatabases 'someVar', 'Items', 'ColumnName'
I shamelessly pulled some snippets from here and here to work around the lack of a group_concat function and sqlserver's information_schema having only the local database's info and not sharing information across databases.
I need to find all databases on a linked server having the same value inside a certain table like the database on the source server which calls the query.
I've build a query which works fine as long as all databases on the linked server have this table. Otherwise it fails telling me:
The OLE DB provider "SQLNCLI11" for linked server "MyServer" does not
contain the table ""DbName"."dbo"."TableName"". The table either does
not exist or the current user does not have permissions on that table.
This is my code:
CREATE TABLE #x(DB SYSNAME, name varchar(255));
DECLARE #guid uniqueidentifier;
SET #guid = (SELECT guid FROM TableName);
DECLARE #sql NVARCHAR(MAX);
SET #sql = N'';
SELECT #sql = #sql + 'IF EXISTS (SELECT 1 FROM [LinkedServer].'
+ QUOTENAME(name) + '.sys.tables WHERE name = ''TableName'')
INSERT #x SELECT ''' + name + ''', name
FROM [LinkedServer].' + QUOTENAME(name) + '.dbo.TableName
WHERE guid = ''' + CONVERT(NVARCHAR(36), #guid) + ''';'
FROM [LinkedServer].master.sys.databases;
EXEC sp_executesql #sql;
SELECT * FROM #x;
DROP TABLE #x;
Is there a way to solve this?
since you don't know if the table exists in specific database at compile time, you have to use dynamic sql:
SELECT #sql += '
IF EXISTS (SELECT 1 FROM [LinkedServer].' + QUOTENAME(name) + '.sys.tables WHERE name = ''TableName'')
exec (''INSERT #x SELECT ''''' + name + ''''', name
FROM [LinkedServer].' + QUOTENAME(name) + '.dbo.TableName
WHERE guid = ''''' + CONVERT(NVARCHAR(36), #guid) + ''''''');'
FROM [LinkedServer].master.sys.databases;
where database_id > 4
so you have dynamic inside of dynamic.
I'm trying to shrink all databases (files and logs) in SQL Server 2008 R2.
I have finished the script, but the problem is that when I loop over all databases and execute the query to do shrink file the first 3 or 4 shrinks work but them I have this error :
Msg 0, Level 11, State 0, Line 0
A severe error occurred on the current command. The results, if any,
should be discarded.
The script :
declare #db_name as varchar(30)
declare #db_recorvery_model as varchar(30)
declare #db_files_name as varchar(250)
declare #db_files_physical_name as varchar(250)
declare get_files cursor for
select b.name, a.name
from sys.master_files as a,
sys.databases as b
where a.database_id = b.database_id
order by b.name
open get_files
fetch next from get_files into #db_files_name, #db_files_physical_name
set #db_files_name = (select #db_files_name)
set #db_files_physical_name = (select #db_files_physical_name)
DECLARE #Command as nvarchar(max)
set #Command=''
while(##FETCH_STATUS=0)
BEGIN
if (#db_files_name='master' or #db_files_name='msdb' or #db_files_name='tempdb' or #db_files_name='model')
BEGIN
print 'Bases de dados do sql server: '+#db_files_name
END
ELSE
BEGIN
set #Command = 'USE ' + '[' + #db_files_name + '] DBCC SHRINKFILE ("'+#db_files_physical_name+'", 1 )'
EXEC sp_executesql #Command
print #Command
END
fetch next from get_files into #db_files_name, #db_files_physical_name
set #db_files_name = (select #db_files_name)
set #db_files_physical_name = (select #db_files_physical_name)
END
close get_files
deallocate get_files
Does anyone have any ideas ?
PS: I know that I shouldn't shrink but is a very special environment and not productive.
Can you determine which database flags the error? Can you try running your script on the single database that has the problem and see if it is consistently the same database that triggers the error? Perhaps it's a special database that you've missed that cannot be shrunk that way.
I have a similar environment containing temp databases that are not for long term storage, and I use the following script which has worked perfectly for hundreds of databases:
CREATE procedure [dbo].[ShrinkLog]
#DB varchar(200)
as
declare #LogFile varchar(200)
declare #Sql varchar(500)
SELECT #LogFile = name
FROM sys.master_files
where type_desc = 'LOG'
and db_name(database_id) = #DB
set #Sql = '
Use [' + #DB + ']
DBCC SHRINKFILE([' + #LogFile + '], 1)
'
print(#sql)
exec(#sql)
Keep in mind also that you don't want to run this command unless your server has plenty of hard drive/memory space as well.
Best regards,
If you want to do a log shrink, this will be the best code. I am using it for a while and it never crash to me.
declare #SQL nvarchar(max)
select #SQL = coalesce(#SQL + char(13) + char(10),'') + N'
Use ' + QUOTENAME(d.[name]) + ';' + CHAR(13) + '
ALTER DATABASE ' + QUOTENAME(d.[name]) + ' SET RECOVERY SIMPLE;
DBCC SHRINKFILE (' + quotename(mf.[name],'''') + ', 1);
ALTER DATABASE ' + QUOTENAME(d.[name]) + ' SET RECOVERY FULL;'
FROM sys.databases d
INNER JOIN sys.master_files mf ON [d].[database_id] = [mf].[database_id]
WHERE
d.[database_id] > 4 --no sys dbs
AND d.recovery_model = 1
AND d.is_read_only = 0
AND mf.[type] = 1 --log files
ORDER BY d.name
--print #SQL
execute (#SQL)