query sql server in multiple database - sql

i would like to execute this query 'select count(*) from Aircraft' on multiple database. We have 50 database and all of those have the same table. i'm using sql server 2019.
I know there is a possibilty to loop this query so that's why i'm asking you.
I found some old reply but not recently.
I used this query but it didn't work
SELECT #Query = COALESCE(#Query + ' UNION ALL ', '') + 'select * from [' + TABLE_CATALOG+'].dbo.[Aircraft]'
FROM information_schema.tables
SET #Query = STUFF(#Query, CHARINDEX('UNION ALL', #Query), 10, '')
PRINT #Query
EXEC(#Query)

If all target databases are located in one instance, this can be done using the string_agg function in the following way:
Declare #schema_name sysname = N'dbo'
Declare #table_name sysname = N'Aircraft'
Declare #max nVarChar(max) = ''
Declare #QueryText nVarChar(max)
Select #QueryText = String_Agg(Concat(#max, N'Select * From ',
QuoteName([name]), N'.',
QuoteName(#schema_name),N'.',
QuoteName(#table_name), Char(10)),
Concat(N'UNION ALL',Char(10)))
From master.dbo.sysdatabases
Where OBJECT_ID(Concat(QuoteName([name]),'.',
QuoteName(#schema_name),'.',
QuoteName(#table_name))) Is Not Null
Print #QueryText
Exec sp_executesql #QueryText

You can achieve this by taking advantage of the undocumented stored procedure in SQL Server i.e. sp_MSforeachdb
DECLARE #query NVARCHAR(1000)
SET #query =
'USE ?
IF DB_NAME() NOT IN (''master'', ''tempdb'', ''model'', ''msdb'')
SELECT COUNT(*) AS Count FROM ?.dbo.fin_MemberAccount'
EXEC sp_MSforeachdb #command1 = #query
-- ? : this means the current db while iterating through db by stored procedure

Related

Variable in SQL query syntax

I want to make a simple SQL query like:
SELECT * FROM table WHERE $variable_2 = $variable_1
instead of a default one:
SELECT * FROM table WHERE column_name = $variable_1
It seems like first example doesn't work at all. Is it even possible to modify SQL query syntax in such way?
as this reference answer for:
in link:
Use Variable as SQL column Name in query
answer1:
declare #ColumnName varchar(50)
declare #sql nvarchar(max)
set #ColumnName = 'SalesData_' + convert(varchar(2),datepart(dd,getdate()))
set #sql = 'select ' + #ColumnName + ' from SalesTable'
print #sql
EXEC sp_sqlexec #sql
answer 2:
declare #ColumnName varchar(50)
declare #sql nvarchar(max)
set #ColumnName = 'SalesData_' + convert(varchar(2),datepart(dd,getdate()))
set #sql = 'select ' + #ColumnName + ' from yourschema.SalesTable'
print #sql

Union of multiple sp_MSforeachdb result sets

I can successfully query the same table in multiple databases as follows:
DECLARE #command varchar(1000)
SELECT #command = 'select * from table'
EXEC sp_MSforeachdb #command
However, all of these results are, as expected, returned in different result windows. What's the easiest way to perform a union of all of these results?
Please stop using sp_MSforeachdb. For anything. Seriously. It's undocumented, unsupported, and spectacularly broken:
Making a more reliable and flexible sp_MSforeachdb
Execute a Command in the Context of Each Database in SQL Server
If you know that all databases have the same table (and that they all have the same structure!), you can do this:
DECLARE #sql NVARCHAR(MAX);
SET #sql = N'';
SELECT #sql = #sql + N'UNION ALL SELECT col1,col2 /*, etc. */
FROM ' + QUOTENAME(name) + '.dbo.tablename'
FROM sys.databases WHERE database_id > 4 AND state = 0;
SET #sql = STUFF(#sql, 1, 10, '');
EXEC sp_executesql #sql;
This ignores system databases and doesn't attempt to access any databases that are currently not ONLINE.
Now, you may want to filter this further, e.g. not include any databases that don't have a table called tablename. You'll need to nest dynamic SQL in this case, e.g.:
DECLARE #sql NVARCHAR(MAX);
SET #sql = N'DECLARE #cmd NVARCHAR(MAX);
SET #cmd = N'''';';
SELECT #sql = #sql + N'
SELECT #cmd = #cmd + N''UNION ALL
SELECT col1,col2 /*, etc. */ FROM '
+ QUOTENAME(name) + '.dbo.tablename ''
WHERE EXISTS (SELECT 1 FROM ' + QUOTENAME(name)
+ '.sys.tables AS t
INNER JOIN ' + QUOTENAME(name) + '.sys.schemas AS s
ON t.[schema_id] = s.[schema_id]
WHERE t.name = N''tablename''
AND s.name = N''dbo'');'
FROM sys.databases WHERE database_id > 4 AND state = 0;
SET #sql = #sql + N';
SET #cmd = STUFF(#cmd, 1, 10, '''');
PRINT #cmd;
--EXEC sp_executesql #cmd;';
PRINT #sql;
EXEC sp_executesql #sql;
This doesn't validate the column structure is compatible, but you'll find that out pretty quickly.
Another way to skin this cat is to use dynamic SQL:
DECLARE #sql varchar(max);
SELECT #sql = Coalesce(#sql + ' UNION ALL ', '') + 'SELECT list, of, columns FROM ' + QuoteName(name) + '.schema.table'
FROM sys.databases
;
PRINT #sql
--EXEC (#sql);
Had some collation issues and had to use
AND COLLATION_NAME = 'SQL_Latin1_General_CP1_CI_AS'

Can we pass database name in a SQL query as parameter?

Consider the following queries, where only database name differs (on same server)
Select * from sampledev.dbo.Sample
Select * from sampleqa.dbo.Sample
The above queries are part of a procedure. Every time I have to run the procedure, I have to make sure it references the correct database (and do rename, if it is not).
I want to pass the database name as a parameter to the stored procedure. The question is, is it possible? If yes, how?
You can accomplish this using sp_executesql
DECLARE #Database NVARCHAR(255),
#Query NVARCHAR(MAX)
SET #Database = 'Database'
SET #Query = N'SELECT * FROM ' + #Database + '.dbo.Table'
EXEC sp_executesql #Query
Something as simple as: ?
CREATE PROC GetData
(
#DatabaseName VARCHAR(255)
)
AS
BEGIN
IF #DatabaseName = 'sampledev'
SELECT * FROM sampledev.dbo.Sample
ELSE IF #DatabaseName = 'sampleqa'
SELECT * FROM sampleqa.dbo.Sample
END
Use:
exec GetData 'sampledev'
Results
dev data
(1 row(s) affected)
exec GetData 'sampleqa'
Results
qa data
(1 row(s) affected)
Just like the leading answer but without SQL injection vulnerability.
First you must query the sys.databases in order to be sure to get the real Database name while not counting on the users text:
SELECT #Database = [name]
FROM sys.databases
WHERE [name] = #Database;
Now perform the query using sp_executesql:
DECLARE #Query nvarchar(200);
SET #Query = N'SELECT * FROM ' + #DBName + '.dbo.sample';
EXEC sp_executesql #Query
Full Stored procedure:
CREATE PROCEDURE [MyScheme].[MyStoredProcedure]
(
#DBName sysname
)
AS
BEGIN
SET NOCOUNT ON;
SELECT #DBName = [name]
FROM sys.databases
WHERE [name] = #DBName;
DECLARE #Query nvarchar(200);
SET #Query = N'SELECT * FROM ' + #DBName + '.dbo.sample';
EXEC sp_executesql #Query
END
GO

How to execute store procedure for another DB?

I have a stored procedure that should be able to be executed on any table of any database on my MS Sql Server. Most of the combination of EXEC and USE statements didn't result in anything. Here is the stored procedure:
CREATE PROCEDURE [dbo].[usp_TrimAndLowerCaseVarcharFields]
(
#Database VARCHAR(200),
#TableSchema VARCHAR(200),
#TableName VARCHAR(200)
)
AS
BEGIN
DECLARE #sSql VARCHAR(MAX)
SET #Database = '[' + #Database + ']'
SET #sSql = ''
-- Create first part of a statement to update all columns that have type varchar
SELECT #sSql = #sSql + COLUMN_NAME + ' = LOWER(RTRIM(' + COLUMN_NAME + ')), '
FROM INFORMATION_SCHEMA.COLUMNS
WHERE DATA_TYPE = 'varchar'
AND TABLE_CATALOG = #Database
AND TABLE_SCHEMA = #TableSchema
AND TABLE_NAME = #TableName
SET #sSql = 'UPDATE ' + #Database + '.' + #TableSchema + '.' + #TableName + ' SET ' + #sSql
-- Delete last two symbols (', ')
SET #sSql = LEFT(#sSql, LEN(#sSql) - 1)
EXEC(#sSql)
END
Please, advice what I have to do to execute it on [OtherDB].[TargetTable].
You can fully qualify both tables and stored procedures. In other words you can do this:
UPDATE [OtherDB].[Schema].[targetTable] SET ...
It appears you are doing this in your proc already.
You can also EXEC a stored procedure using the Fully Qualified name - e.g.
EXEC [OtherDB].[dbo].[usp_TrimAndLowerCaseVarcharFields]
Honestly, your proc looks fine, are you receiving any error messages? If so please post them. Also, make sure your user has access to the other DB.
The table name in the query you used is wrong, it is looking up into same database, but you do need to look up from different database. So the query will be as below:
SELECT #sSql = #sSql + COLUMN_NAME + ' = LOWER(RTRIM(' + COLUMN_NAME + ')), '
FROM [TargetDB].INFORMATION_SCHEMA.COLUMNS
WHERE DATA_TYPE = 'varchar'
AND TABLE_CATALOG = #Database
AND TABLE_SCHEMA = #TableSchema
AND TABLE_NAME = #TableName
-- [TargetDB] = #Database
The TargetDB will be same as your passing database (#Database)
If you want to use [TargetDB] dynamically then you need to generate sql(#sSql) and the execute the sql string.
In this case I sugest to use the 2 sp's in SQL Server:
sp_MSforeachtable
sp_MSforeachdb
for more information, please read the article in here.
hope this help.
exec #RETURN_VALUE=sp_MSforeachtable #command1, #replacechar, #command2,
#command3, #whereand, #precommand, #postcommand
exec #RETURN_VALUE = sp_MSforeachdb #command1, #replacechar,
#command2, #command3, #precommand, #postcommand
Complete script:
declare #DatabaseName varchar(max), #DatabaseCharParam nchar(1)
declare #TableSchema varchar(max)
declare #TableName varchar(max), #TableCharParam nchar(1)
set #DatabaseName='DATABASENAME'; set #DatabaseCharParam='?'
set #TableSchema='dbo'
set #TableName='TABLENAME'; set #TableCharParam='$'
-- Exemple Script to execute in each table in each database
-- Create first part of a statement to update all columns that have type varchar
DECLARE #sSql VARCHAR(MAX)
set #sSql=''
SELECT #sSql = isnull(#sSql,'') + COLUMN_NAME + ' = LOWER(RTRIM(' + COLUMN_NAME + ')),'
FROM INFORMATION_SCHEMA.COLUMNS
WHERE DATA_TYPE = 'varchar'
AND TABLE_CATALOG = #DatabaseName
AND TABLE_SCHEMA = #TableSchema
AND TABLE_NAME = #TableName
declare #EachTablecmd1 varchar(2000)
--Prepare #EachTablecmd1 the script to execution in each table using sp_MSforeachtable (ATENTION: #Command1 are limited to varchar(2000) )
--in sp_MSforeachtable #TableCharParam will be subtituted with owner i.e:[dbo].[TABLENAME]
set #sSql='update '+#TableCharParam+' set '+ left(#sSql,LEN(#sSql)-1)
set #EachTablecmd1='if '''''+ #TableCharParam +'''''=''''['+#TableSchema+'].['+#TableName+']'''' '+#sSql
--i.e.: if 'table1'='table1' update table1 set column1=LOWER(RTRIM(column1)),....
-- the #sSql for each table in a database
set #sSql ='exec sp_MSforeachtable #command1='''+#EachTablecmd1+''' ,#replacechar='''+#TableCharParam+''''
declare #EachBDcmd1 varchar(2000)
--Prepare the execution to each database using sp_MSforeachdb (ATENTION: #Command1 are limited to varchar(2000) )
set #EachBDcmd1='if '''+#DatabaseCharParam+'''='''+#DatabaseName+''' '+ #sSql
--print #EachBDcmd1
exec sp_MSforeachdb #command1=#EachBDcmd1,#replacechar=#DatabaseCharParam

How to secure dynamic SQL stored procedure?

I have a stored procedure that takes in the name of a table as a parameter and uses dynamic sql to perform the select. I tried to pass #TableName as a parameter and use sp_executesql but that threw an error. I decided to go with straight dynamic sql without using sp_executesql.
Is there anything else I should be doing to secure the #TableName parameter to avoid sql injection attacks?
Stored procedure below:
CREATE PROCEDURE dbo.SP_GetRecords
(
#TableName VARCHAR(128) = NULL
)
AS
BEGIN
/* Secure the #TableName Parameter */
SET #TableName = REPLACE(#TableName, ' ','')
SET #TableName = REPLACE(#TableName, ';','')
SET #TableName = REPLACE(#TableName, '''','')
DECLARE #query NVARCHAR(MAX)
/* Validation */
IF #TableName IS NULL
BEGIN
RETURN -1
END
SET #query = 'SELECT * FROM ' + #TableName
EXEC(#query)
END
This failed when using sp_executesql instead:
SET #query = 'SELECT * FROM #TableName'
EXEC sp_executesql #query, N'#TableName VARCHAR(128)', #TableName
ERROR: Must declare the table variable
"#TableName".
See here:
How should I pass a table name into a stored proc?
you of course can look at the sysobjects table and ensure that it exists
Select id from sysobjects where xType = 'U' and [name] = #TableName
Further (more complete example):
DECLARE #TableName nVarChar(255)
DECLARE #Query nVarChar(512)
SET #TableName = 'YourTable'
SET #Query = 'Select * from ' + #TableName
-- Check if #TableName is valid
IF NOT (Select id from sysobjects where xType = 'U' and [name] = #TableName) IS NULL
exec(#Query)